使用 Double 向 Literals

本节讨论如何使用java.awtjava.awt.font软件包中的类处理 Double 向文本。这些类使您可以使用 Unicode 标准支持的任何语言或脚本绘制样式化的文本:Unicode 标准:一种用于处理各种现代,古典和历史语言的全局字符编码系统。绘制文本时,必须考虑文本的读取方向,以便正确显示字符串 中的所有单词。这些类保持文本的方向并正确绘制文本,而不管字符串 是从左到右,从右到左还是 Double 向(Double 向)运行。Double 向文本提出了一些有趣的问题,用于正确定位插入符号,准确定位选择内容和正确显示多行。同样,Double 向文本和从右至左文本也存在类似的问题,这些问题是响应于左右箭头按键向正确方向移动插入符号。

涵盖以下主题:

如果您打算使用 Swing 组件,请参阅使用 JTextComponent 类处理 Double 向文本使用 Literals 组件以获取更多信息。

Ordering Text

Java SE 以逻辑 Sequences 将文本存储在内存中,这是字符和单词的读写 Sequences。逻辑 Sequences 不必与视觉 Sequences 相同,视觉 Sequences 是显示相应字形的 Sequences。

即使语言混合在一起,书写系统的视觉 Sequences 也必须保持在 Double 向文本中。下图对此进行了说明,该图显示了嵌入英语句子中的阿拉伯语短语。

注意: 在本示例及后续示例中,阿拉伯语和希伯来语文本由大写字母表示,空格由下划线表示。每个插图都包含两个部分:代表存储在存储器中的字符(按逻辑 Sequences 排列的字符),然后代表这些字符的显示方式(按视觉 Sequences 排列的字符)。字符框下方的数字表示插入offset量。

嵌入英语句子中的阿拉伯语短语

即使它们是英语句子的一部分,阿拉伯语单词也按照从右到左的阿拉伯语脚本 Sequences 显示。因为斜体阿拉伯字在逻辑上在纯文本中在阿拉伯语之后,所以它在视觉上在纯文本的左侧。

当显示包含从左到右和从右到左的混合文本的行时,“基本方向”是重要的。基本方向是主要书写系统的脚本 Sequences。例如,如果文本主要是英语,并带有一些嵌入的阿拉伯语,则基本方向是从左到右。如果文本主要是阿拉伯语,带有一些嵌入的英语或数字,则基本方向是从右到左。

基本方向确定具有共同方向的文本段的显示 Sequences。在上图所示的示例中,基本方向是从左到右。在此示例中,有三个定向运行:句子开头的英语文本从左至右运行,阿拉伯语文本从右至左运行,句号从左至右运行。

图形通常嵌入文本流中。就它们如何影响文本流和换行而言,这些内联图形的行为类似于字形。需要使用相同的 Double 向布局算法来定位此类内联图形,以便它们出现在字符流中的正确位置。

Java SE 使用UnicodeDouble 向算法,该算法用于对一行中的字形进行排序,从而确定 Double 向文本的方向性。在大多数情况下,您无需包括任何其他信息即可使该算法获得正确的显示 Sequences。

处理 Double 向 Literals

要允许用户编辑 Double 向文本,您必须能够执行以下操作:

Displaying Carets

在可编辑文本中,“尖号”用于图形表示当前的插入点,即文本中将插入新字符的位置。通常,插入符号显示为两个字形之间闪烁的垂直条。新字符将插入并显示在插入符号的位置。

计算插入符的位置可能会很复杂,尤其是对于 Double 向文本而言。方向边界上的插入offset具有两个可能的插入符号位置,因为对应于字符offset的两个字形不会彼此相邻显示。下图对此进行了说明。在此图中,插入符号显示为方括号,以指示插入符号所对应的字形。

Dual carets

字符offset量 8 对应于下划线之后* A 之前的位置。如果用户 Importing 阿拉伯字符,则其字形显示在 A *的右侧(之前);如果用户 Importing 英 Literals 符,则其字形显示在下划线(之后)的右侧。

为了处理这种情况,某些系统显示 Double 插入号,强(主)插入号和弱(辅助)插入号。尖号表示当插入字符的方向与文本的基本方向相同时,该字符将在何处显示。当字符的方向与基本方向相反时,弱插入号会显示插入字符的显示位置,TextLayout自动支持 Double 插入号。

使用 Double 向文本时,不能简单地在字符offset量之前添加字形的宽度来计算插入符号的位置。如果这样做,则插入符将被绘制在错误的位置,如下图所示:

插入符号绘制错误

为了使插入符正确定位,需要添加offset量左侧的字形宽度,并考虑当前上下文。除非考虑上下文,否则字形 Metrics 将不一定与显示匹配。 (上下文会影响所使用的字形.)

Moving Carets

所有文本编辑器都允许用户使用箭头键移动插入符号。用户期望插入符号沿所按箭头键的方向移动。在从左到右的文本中,移动插入offset量很简单:右箭头键将插入offset量增加 1,而左箭头键将插入offset量减少 1.在 Double 向文本或带有连字的文本中,此行为将导致插入符号在方向边界处跨字形跳跃,并在不同的方向行中沿相反方向移动。

为了使插入符在 Double 向文本中平滑移动,您需要考虑文本运行的方向。您不能简单地在按下右箭头键时增加插入offset量,而在按下左箭头键时减少插入offset量。如果当前插入offset量在从右到左的字符范围内,则右箭头键应减小插入offset量,而左箭头键应增大插入offset量。

跨方向插入符号的插入更加复杂。下图说明了当用户使用箭头键导航时超过方向边界时发生的情况。在显示的文本中向右移动三个位置对应于移动到字符offset量 7、19 和 18.

Caret movement

某些字形之间绝对不应插入尖号。取而代之的是,插入符号应移动,就像字形代表单个字符一样。例如,如果* o *和变音符号由两个单独的字符表示,则绝对不应出现脱字符号。

TextLayout类提供的方法(getNextRightHitgetNextLeftHit)使您可以轻松地通过 Double 向文本平滑地插入插入符号。

Hit Testing

通常,设备空间中的位置必须转换为文本offset量。例如,当用户在可选文本上单击鼠标时,鼠标的位置将转换为文本offset量并用作选择范围的一端。从逻辑上讲,这是插入符号的相反方向。

当使用 Double 向文本时,显示中的单个视觉位置可以对应于源文本中的两个不同offset量,如下图所示:

命中测试 Double 向文本

因为单个视觉位置可以对应于两个不同的offset量,所以命中测试 Double 向文本不仅仅是测量字形宽度,直到找到正确位置的字形,然后将该位置 Map 回字符offset量为止。检测击球所在的一侧有助于区分这两种选择。

您可以使用TextLayout.hitTestChar执行命中测试。匹配信息封装在TextHitInfo对象中,并包含有关匹配所在一侧的信息。

Highlighting Selections

选定的字符范围由高亮区域以图形方式表示,高亮区域是显示字形的区域,该字形以反向视频或不同的背景色显示。

高亮区域(如插入符号)对于 Double 向文本而言要比单向文本复杂得多。在 Double 向文本中,显示时连续的字符范围可能没有连续的突出显示区域。相反,显示视觉上连续的字形范围的突出显示区域可能不对应于单个连续的字符范围。

这将导致两种策略来突出显示 Double 向文本中的选择:

  • 逻辑突出显示:使用逻辑突出显示时,所选字符在文本模型中始终是连续的,并且突出显示区域允许是不连续的。以下是逻辑突出显示的示例:

逻辑突出显示的插图(连续字符)

  • 视觉突出显示:使用视觉突出显示时,所选字符可能不止一个范围,但是突出显示区域始终是连续的。以下是视觉突出显示的示例:

视觉突出显示的插图(连续的突出显示区域)

逻辑突出显示更易于实现,因为所选字符在文本中始终是连续的。

samplesSelectionSample.java演示了逻辑突出显示:

选择 sample;逻辑突出显示

在 Java 应用程序中执行文本布局

根据您使用的 Java API,可以根据需要对文本布局进行尽可能少的控制:

通常,您不需要自己执行文本布局操作。对于大多数应用程序,JTextComponent是显示静态和可编辑文本的最佳解决方案。但是,JTextComponent不支持在 Double 向文本中显示 Double 插入符号或不连续的选择。如果您的应用程序需要这些功能,或者您希望实现自己的文本编辑例程,则可以使用 Java 2D 文本布局 API。

使用 TextLayout 类 管理 文本布局

TextLayout类支持包含来自不同书写系统(包括阿拉伯语和希伯来语)的多种样式和字符的文本。 (阿拉伯语和希伯来语特别难以显示,因为您必须重新设置文本的形状和 Sequences 以实现可接受的表示形式.)

TextLayout简化了显示和测量文本的过程,即使您仅使用英文文本也是如此。通过使用TextLayout,您可以毫不费力地实现高质量的排版。

TextLayout的设计使其在用于显示简单的单向文本时不会对性能产生重大影响。当使用TextLayout来显示阿拉伯或希伯来语文本时,会有一些额外的处理开销。但是,通常每个字符大约为微秒,并且以执行常规绘图代码为主。

TextLayout类为您 管理 字形的位置和 Sequences。您可以使用TextLayout执行以下操作:

使用 TextLayout 类布置文本

TextLayout自动以正确的形状和 Sequences 对文本进行布局,包括 Double 向文本。为了正确地塑造和排列代表一行文本的字形,TextLayout必须知道文本的完整上下文:

  • 如果文本适合一行,例如按钮的单个单词标签或对话框中的一行,则可以直接从文本构造TextLayout

  • 如果您的文本超出一行的大小,或者想将一行的文本分成多个选项卡式段,则不能直接构造TextLayout。您必须使用LineBreakMeasurer来提供足够的上下文。有关更多信息,请参见绘制多行文本

文本的基本方向通常由文本上的属性(样式)设置。如果缺少该属性,则TextLayout遵循 UnicodeDouble 向算法,并从段落中的初始字符中得出基本方向。

使用 TextLayout 类显示 Double 班

TextLayout维护插入符号信息,例如插入符号Shape,位置和角度。您可以使用此信息轻松地在单向和 Double 向文本中显示插入符号。在绘制 Double 向文本的插入符号时,使用TextLayout可确保插入符号的位置正确。

TextLayout提供默认插入符Shapes并自动支持 Double 插入符。对于斜体和斜字形,TextLayout会产生成角度的尖号,如下图所示。这些插入标记的位置也用作突出显示和命中测试的字形之间的边界,这有助于产生一致的用户体验。

Angled carets

给定插入offset量,getCaretShapes方法将返回包含Shape个对象的两元素数组:元素 0 包含强插入符,元素 1 包含弱插入符(如果存在)。要显示 Double 重插入符号,只需绘制两个插入符号Shape对象;插入符号将自动呈现在正确的位置。

如果要使用自定义插入标记,则可以从TextLayout检索插入标记的位置和角度并自己绘制。

samplesHitTestSample.java演示了 Double 插入符。

单击* o 侧面朝向希伯来语文本的 o 会记录final用户在 o (英语文本的一部分)之后单击的情况。这会将弱(黑色)插入符号置于 o 旁边,将强插入号(红色)置于 H *前面:

点击“测试 samples”,单击希伯来 Literals 旁边的“ o”

单击* o 右边的空格会记录final用户单击了该空格,这是希伯来语文本的一部分。这会将强(红色)插入符号放在 o 旁边,将弱插入号(黑色)放在 H *前面:

点击“测试 samples”,单击“ o”右侧的空格

使用 TextLayout 类移动插入符号

您还可以使用TextLayout类来确定当用户按下向左或向右箭头键时产生的插入offset量。给定一个代表当前插入offset的TextHitInfo对象,如果按下了右箭头键,则getNextRightHit方法将返回一个代表正确插入offset的TextHitInfo对象。 getNextLeftHit方法为左箭头键提供相同的信息。

下面的 samplesArrowKeySample.java摘录演示了如何在用户按下向左或向右箭头键时确定插入的offset量:

public class ArrowKeySample extends JPanel implements KeyListener {

  // ...

  private static void createAndShowGUI() {
    // Create and set up the window.
    ArrowKey demo = new ArrowKey();
    frame = new JFrame("Arrow Key Sample");
    frame.addKeyListener(demo);
    // ...
  }

  private void handleArrowKey(boolean rightArrow) {
    TextHitInfo newPosition;
    if (rightArrow) {
      newPosition = textLayout.getNextRightHit(insertionIndex);
    } else {
      newPosition = textLayout.getNextLeftHit(insertionIndex);
    }

    // getNextRightHit() / getNextLeftHit() will return null if
    // there is not a caret position to the right (left) of the
    // current position.
    if (newPosition != null) {
      // Update insertionIndex.
      insertionIndex = newPosition.getInsertionIndex();
      // Repaint the Component so the new caret(s) will be displayed.
      frame.repaint();
    }
  }

  // ...

  @Override
  public void keyPressed(KeyEvent e) {
    int keyCode = e.getKeyCode();
    if (keyCode == KeyEvent.VK_LEFT || keyCode == KeyEvent.VK_RIGHT) {
      handleArrowKey(keyCode == KeyEvent.VK_RIGHT);
    }
  }
}

使用 TextLayout 类进行命中测试

TextLayout类提供了一种用于命中测试文本的简单机制。 hitTextChar方法使用鼠标的* x y *坐标作为参数,并返回TextHitInfo对象。 TextHitInfo包含指定位置的插入offset以及击中所在的一侧。插入offset量是最接近匹配点的offset量:如果匹配点超过了行尾,则返回行尾的offset量。

HitTestSample.java的以下摘录是通过单击鼠标来获取offset量的:

private class HitTestMouseListener extends MouseAdapter {
    public void mouseClicked(MouseEvent e) {
      Point2D origin = computeLayoutOrigin();
      // Compute the mouse click location relative to
      // textLayout's origin.
      float clickX = (float) (e.getX() - origin.getX());
      float clickY = (float) (e.getY() - origin.getY());
      // Get the character position of the mouse click.
      TextHitInfo currentHit = textLayout.hitTestChar(clickX, clickY);
      insertionIndex = currentHit.getInsertionIndex();
      // Repaint the Component so the new caret(s) will be displayed.
      repaint();
    }
  }

使用 TextLayout 类突出显示选择

您可以从TextLayout获得一个Shape代表突出显示区域。 TextLayout在计算突出显示区域的尺寸时会自动考虑上下文。 TextLayout支持逻辑和视觉突出显示。

以下来自SelectionSample.java的摘录演示了一种显示突出显示的文本的方法:

public void paint(Graphics g) {

    // ...

    boolean haveCaret = anchorEnd == activeEnd;

    if (!haveCaret) {
      // Retrieve highlight region for selection range.
      Shape highlight = 
          textLayout.getLogicalHighlightShape(anchorEnd, activeEnd);
      // Fill the highlight region with the highlight color.
      graphics2D.setColor(HIGHLIGHT_COLOR);
      graphics2D.fill(highlight);
    }

    // ...

  }

  // ...

  private class SelectionMouseMotionListener extends MouseMotionAdapter {
    public void mouseDragged(MouseEvent e) {
      Point2D origin = computeLayoutOrigin();
      // Compute the mouse location relative to
      // textLayout's origin.
      float clickX = (float) (e.getX() - origin.getX());
      float clickY = (float) (e.getY() - origin.getY());
      // Get the character position of the mouse location.
      TextHitInfo position = textLayout.hitTestChar(clickX, clickY);
      int newActiveEnd = position.getInsertionIndex();
      // If newActiveEnd is different from activeEnd, update activeEnd
      // and repaint the Panel so the new selection will be displayed.
      if (activeEnd != newActiveEnd) {
        activeEnd = newActiveEnd;
        frame.repaint();
      }
    }
  }

  private class SelectionMouseListener extends MouseAdapter {
    public void mousePressed(MouseEvent e) {
      Point2D origin = computeLayoutOrigin();
      // Compute the mouse location relative to
      // TextLayout's origin.
      float clickX = (float) (e.getX() - origin.getX());
      float clickY = (float) (e.getY() - origin.getY());
      // Set the anchor and active ends of the selection
      // to the character position of the mouse location.
      TextHitInfo position = textLayout.hitTestChar(clickX, clickY);
      anchorEnd = position.getInsertionIndex();
      activeEnd = anchorEnd;
      // Repaint the Panel so the new selection will be displayed.
      frame.repaint();
    }
  }

方法SelectionMouseListener.mousePressed指定变量anchorEnd,它是文本中单击鼠标的位置。方法SelectionMouseMotionListener.mouseDragged指定变量activeEnd,这是文本中鼠标拖动到的位置。 paint方法检索表示所选文本(位置anchorEndactiveEnd之间的文本)的Shape对象。 paint方法然后用突出显示颜色填充Shape对象。