Java 中文官方教程 2022 版(三十一)(2)https://developer.aliyun.com/article/1487980
逻辑字体
Java SE 定义了以下五个逻辑字体系列:
Dialog
DialogInput
Monospaced
Serif
SansSerif
这些字体在任何 Java 平台上都可用,并且可以被视为某些具有其名称所暗示属性的基础字体的别名。Serif 字体类似于 Times New Roman,通常用于印刷。Sans Serif 字体更适合屏幕使用。
这些字体可以根据用户的语言环境进行定制。此外,这些字体支持最广泛的代码点(Unicode 字符)范围。
除了字体系列,字体还具有其他属性,其中最重要的是 样式 和 大小。样式有 Bold 和 Italic。
Java 2D API 使用的默认字体是 12 磅的 Dialog。这种字体是在普通 72-120 DPI 显示设备上阅读文本的典型字号。应用程序可以通过指定以下内容直接创建此字体的实例:
Font font = new Font("Dialog", Font.PLAIN, 12);
使用物理和逻辑字体的优缺点
物理字体使应用程序能够充分利用所有可用字体,实现不同的文本外观和最大的语言覆盖范围。然而,创建使用物理字体的应用程序要困难得多。
将物理字体与您的应用程序捆绑在一起,可以使您创建的应用程序在任何地方看起来都一样,并且可以完全控制您想要支持的应用程序。然而,捆绑的字体可能会很大,特别是如果您希望您的应用程序支持中文、日文和韩文。此外,您可能需要解决许可问题。
逻辑字体名称保证在任何地方都能正常工作,并且它们至少能够在主机操作系统本地化的语言中进行文本呈现(通常支持更广泛的语言范围)。然而,用于呈现文本的物理字体在不同的实现、主机操作系统和区域设置之间会有所不同,因此应用程序无法在任何地方实现相同的外观。此外,映射机制有时会限制可以呈现的字符范围。这在 JRE 版本 5.0 之前曾经是一个大问题:例如,日文文本只能在日本本地化的主机操作系统上呈现,而在其他本地化系统上即使安装了日文字体也无法呈现。对于使用 2D 字体呈现的应用程序,在 JRE 版本 5.0 及更高版本中,这个问题要少得多,因为映射机制现在通常会识别并使用所有支持的书写系统的字体(如果已安装)。
字体配置文件
Java SE 运行时环境使用字体配置文件将逻辑字体名称映射到物理字体。根据主机操作系统版本的不同映射,有几个文件支持不同的映射。这些文件位于 JRE 安装的 lib
目录中。您可以编辑或创建自己的字体配置文件,以调整映射到您特定系统设置的映射。有关更多信息,请参阅字体配置文件。
测量文本
原文:
docs.oracle.com/javase/tutorial/2d/text/measuringtext.html
要正确测量文本,您需要学习一些方法和一些要避免的错误。字体度量是由Font
对象呈现的文本的测量,例如字体中一行文本的高度。测量文本最常见的方法是使用封装了这些度量信息的FontMetrics
实例。例如:
// get metrics from the graphics FontMetrics metrics = graphics.getFontMetrics(font); // get the height of a line of text in this // font and render context int hgt = metrics.getHeight(); // get the advance of my text in this font // and render context int adv = metrics.stringWidth(text); // calculate the size of a box to hold the // text with some padding. Dimension size = new Dimension(adv+2, hgt+2);
对于许多应用程序来说,这种方式足以均匀间隔文本行或调整 Swing 组件的大小。
注意以下内容:
- 这些度量是从
Graphics
类中获取的,因为这个类封装了FontRenderContext
,这是准确测量文本所需的。在屏幕分辨率下,字体会根据易读性进行调整。随着文本大小的增加,这种调整并不是线性缩放的。因此,在 20 pt 时,字体显示的文本长度不会正好是在 10 pt 时的两倍。除了文本本身和字体之外,用于测量文本的另一个重要信息是FontRenderContext
。该方法包括从用户空间到设备像素的变换,用于测量文本。 - 高度报告时没有参考任何特定文本字符串。例如,在文本编辑器中,您希望每行文本之间具有相同的行间距时,这是有用的。
stringWidth()
返回文本的前进宽度。前进宽度是从文本原点到随后呈现的字符串位置的距离。
在使用这些方法测量文本时,请注意文本可以向字体高度和字符串前进的矩形定义之外的任何方向延伸。
通常,最简单的解决方案是确保文本不被裁剪,例如,由围绕文本的组件。在可能导致文本被裁剪的情况下添加填充。
如果此解决方案不足够,Java 2D 软件中的其他文本测量 API 可以返回矩形边界框。这些框考虑了要测量的特定文本的高度和像素化效果。
高级文本显示
Java 2D API 提供了支持复杂文本布局的机制。本节描述了高级文本显示的以下特性。
使用渲染提示显示抗锯齿文本
本节介绍如何通过使用渲染提示来控制渲染质量。
使用文本属性来设置文本样式
本节解释了如何使用TextAttribute
类来给文本添加下划线或删除线。
绘制多行文本
本节解释了如何使用TextLayout
和LineBreakMeasurer
类来定位和渲染一段样式化文本。
处理双向文本
本节讨论如何使用java.awt
和java.awt.font
包中的类处理双向文本。
使用渲染提示显示抗锯齿文本
原文:
docs.oracle.com/javase/tutorial/2d/text/renderinghints.html
Java 2D 文本渲染可能受渲染提示的影响。
请记住最重要的文本绘制方法如下:
Graphics.drawString(String s, int x, int y);
通常,该方法会使用纯色绘制文本字符串中的每个字形,而该字形中的每个“开启”像素都会设置为该颜色。这种绘制方式产生了最高对比度的文本,但有时会出现锯齿状(锯齿状)边缘。
文本抗锯齿是一种用于平滑屏幕上文本边缘的技术。Java 2D API 使应用程序能够指定是否应使用此技术以及通过将文本渲染提示应用于Graphics
来使用哪种算法。
最常见的渲染提示会将前景(文本)颜色与文本边缘的屏幕背景像素混合。要请求此提示,应用程序必须调用以下方法:
graphics2D.setRenderingHint( RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
以下图示说明了抗锯齿功能。
如果不当使用,此方法可能使文本显得过于模糊。在这种情况下,更好的提示是以下提示:
graphics2D.setRenderingHint( RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_GASP);
这种方法会自动使用字体本身的信息来决定是使用抗锯齿还是使用纯色。
LCD 显示器具有 Java 2D API 可以利用的属性,以产生不像典型抗锯齿那样模糊的文本,但在小尺寸下更易读的文本。要求使用典型 LCD 显示器的子像素 LCD 文本模式绘制文本,应用程序必须调用以下方法:
graphics2D.setRenderingHint( RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB);
下面所示的代码示例说明了抗锯齿功能的顺序:
- 抗锯齿已关闭。
- 抗锯齿已开启。
- 使用
TEXT_ANTIALIAS_GASP
提示进行抗锯齿。
- 注意: 因此,GASP 表指定仅在这些大小上使用提示,而不是“平滑”。因此,在许多情况下,结果文本显示等同于
VALUE_TEXT_ANTIALIAS_OFF
。
- 使用
TEXT_ANTIALIAS_LCD_HRGB
提示进行抗锯齿。
注意: 如果看不到 applet 运行,请至少安装Java SE Development Kit (JDK) 7版本。
此 applet 的完整代码在AntialiasedText.java
中。
使用文本属性来设置文本样式
原文:
docs.oracle.com/javase/tutorial/2d/text/textattributes.html
应用程序通常需要能够应用以下文本属性:
- 下划线 – 在文本下方绘制的线
- 删除线 – 通过文本绘制的水平线
- 上标或下标 – 文本或字母略高于一行或相应地低于一行
- 字间距 – 调整字符之间的空间
这些以及其他文本属性可以通过使用 Java 2D TextAttribute
类来应用。
要应用这些文本属性,请将它们添加到 Font
对象中。例如:
Map<TextAttribute, Object> map = new Hashtable<TextAttribute, Object>(); map.put(TextAttribute.KERNING, TextAttribute.KERNING_ON); font = font.deriveFont(map); graphics.setFont(font);
下面的代码示例显示了按以下顺序应用文本属性:
- 示例字符串(未应用文本属性)
- 字间距
- 字间距和下划线
- 字间距、下划线和删除线
- 字间距、下划线、删除线和颜色
注意: 如果您看不到 applet 运行,您需要安装至少 Java SE Development Kit (JDK) 7 版本。
此 applet 的完整代码在 AttributedText.java
中。
绘制多行文本
原文:
docs.oracle.com/javase/tutorial/2d/text/drawmulstring.html
如果您有一段带样式的文本,希望将其适应特定宽度,可以使用 LineBreakMeasurer
类。这个类使得带样式的文本可以被分成行,以便它们适应特定的视觉前进。每一行作为一个 TextLayout
对象返回,代表不可改变的、带样式的字符数据。然而,这个类也使得可以访问布局信息。TextLayout
的 getAscent
和 getDescent
方法返回有关用于在组件中定位行的字体的信息。文本被存储为 AttributedCharacterIterator
对象,以便字体和点大小属性可以与文本一起存储。
以下小程序使用 LineBreakMeasurer
、TextLayout
和 AttributedCharacterIterator
在组件中定位一段带样式的文本。
注意: 如果您看不到小程序运行,您需要安装至少 Java SE Development Kit (JDK) 7 版本。
这个小程序的完整代码在 LineBreakSample.java
中。
以下代码创建一个包含字符串 vanGogh
的迭代器。检索迭代器的开始和结束,并从迭代器创建一个新的 LineBreakMeasurer
。
AttributedCharacterIterator paragraph = vanGogh.getIterator(); paragraphStart = paragraph.getBeginIndex(); paragraphEnd = paragraph.getEndIndex(); FontRenderContext frc = g2d.getFontRenderContext(); lineMeasurer = new LineBreakMeasurer(paragraph, frc);
窗口的大小用于确定何处应该断行。同时为段落中的每一行创建一个 TextLayout
对象。
// Set break width to width of Component. float breakWidth = (float)getSize().width; float drawPosY = 0; // Set position to the index of the first // character in the paragraph. lineMeasurer.setPosition(paragraphStart); // Get lines from until the entire paragraph // has been displayed. while (lineMeasurer.getPosition() < paragraphEnd) { TextLayout layout = lineMeasurer.nextLayout(breakWidth); // Compute pen x position. If the paragraph // is right-to-left we will align the // TextLayouts to the right edge of the panel. float drawPosX = layout.isLeftToRight() ? 0 : breakWidth - layout.getAdvance(); // Move y-coordinate by the ascent of the // layout. drawPosY += layout.getAscent(); // Draw the TextLayout at (drawPosX,drawPosY). layout.draw(g2d, drawPosX, drawPosY); // Move y-coordinate in preparation for next // layout. drawPosY += layout.getDescent() + layout.getLeading(); }
TextLayout
类通常不会被应用程序直接创建。然而,当应用程序需要直接处理在文本中特定位置应用了样式(文本属性)的文本时,这个类就非常有用。例如,要在段落中画一个单词斜体,应用程序需要为每个子字符串执行测量并设置字体。如果文本是双向的,这个任务就不那么容易正确完成。通过从 AttributedString
对象创建一个 TextLayout
对象来处理这个问题。请参考 Java SE 规范了解更多关于 TextLayout
的信息。
处理双向文本
译文:
docs.oracle.com/javase/tutorial/2d/text/textlayoutbidirectionaltext.html
这一部分讨论如何使用java.awt
和java.awt.font
包中的类处理双向文本。这些类允许您以任何语言或脚本绘制样式化文本,这些语言或脚本受到 Unicode 标准的支持:这是一个全球字符编码系统,用于处理各种现代、古典和历史语言。在绘制文本时,必须考虑文本的阅读方向,以便字符串中的所有单词都能正确显示。这些类维护文本的方向,并正确绘制它,无论字符串是从左到右、从右到左还是双向运行。双向文本对于正确定位插入符、准确定位选择以及正确显示多行文本提出了有趣的问题。另外,双向和从右到左的文本对于根据右箭头和左箭头键的按压正确移动插入符也存在类似问题。
下列主题包括:
- 文本排序
- 操作双向文本
- 显示插入符
- 移动插入符
- 点击测试
- 突出显示选择
- 在 Java 应用程序中执行文本布局
- 使用 TextLayout 类管理文本布局
- 使用 TextLayout 类布局文本
- 使用 TextLayout 类显示双插入符
- 使用 TextLayout 类移动插入符
- 使用 TextLayout 类进行点击测试
- 使用 TextLayout 类突出显示选择
如果您计划使用 Swing 组件,请参阅使用 JTextComponent 类处理双向文本和使用文本组件获取更多信息。
文本排序
Java SE 在内存中以逻辑顺序存储文本,这是字符和单词读取和写入的顺序。逻辑顺序不一定与视觉顺序相同,后者是显示相应字形的顺序。
即使混合使用多种语言,双向文本中必须保持书写系统的视觉顺序。下图展示了一个嵌入在英语句子中的阿拉伯短语。
注意: 在这个和后续的示例中,阿拉伯语和希伯来语文本由大写字母表示,空格由下划线表示。每个示例包含两部分:存储在内存中的字符表示(逻辑顺序的字符)后跟这些字符如何显示的表示(视觉顺序的字符)。字符框下面的数字表示插入偏移量。
尽管它们是英语句子的一部分,但阿拉伯语单词以阿拉伯语书写顺序从右到左显示。因为斜体的阿拉伯语单词在逻辑上位于普通文本的阿拉伯语单词之后,所以在视觉上位于普通文本的左侧。
当显示混合左到右和右到左文本的行时,基本方向很重要。基本方向是主要书写系统的书写顺序。例如,如果文本主要是英语并带有一些嵌入的阿拉伯语,则基本方向是从左到右。如果文本主要是阿拉伯语并带有一些嵌入的英语或数字,则基本方向是从右到左。
基本方向确定具有共同方向的文本段的显示顺序。在前面图中显示的示例中,基本方向是从左到右。在这个示例中有三个方向性运行:句子开头的英语文本从左到右运行,阿拉伯文本从右到左运行,句号从左到右运行。
图形通常嵌入在文本流中。这些内联图形在影响文本流和换行方式方面类似于字形。这样的内联图形需要使用相同的双向布局算法定位,以便它们出现在字符流的适当位置。
Java SE 使用Unicode 双向算法,这是一种用于在一行内对字形进行排序的算法,从而确定双向文本的方向性。在大多数情况下,您无需包含任何额外信息,以便该算法获取正确的显示顺序。
操纵双向文本
为了允许用户编辑双向文本,您必须能够执行以下操作:
- 显示插入符
- 移动插入符
- 点击测试
- 突出显示选择
显示插入符
在可编辑文本中,插入符用于图形化表示当前插入点,即文本中新字符将被插入的位置。通常,插入符显示为两个字形之间闪烁的垂直条。新字符被插入并显示在插入符的位置。
计算插入符位置可能会很复杂,特别是对于双向文本。在方向边界上的插入偏移量有两个可能的插入符位置,因为对应于字符偏移量的两个字形不会相邻显示。如下图所示。在此图中,插入符显示为方括号,表示插入符对应的字形。
字符偏移量 8 对应于下划线后和A之前的位置。如果用户输入阿拉伯字符,其字形将显示在A的右侧;如果用户输入英文字符,其字形将显示在下划线的右侧。
为了处理这种情况,一些系统显示双插入符,一个强(主要)插入符和一个弱(次要)插入符。强插入符指示插入的字符的方向与文本基础方向相同时将显示在何处。弱插入符显示插入的字符的方向与基础方向相反时将显示在何处TextLayout
自动支持双插入符。
当处理双向文本时,不能简单地将字符偏移量之前的字形宽度相加以计算插入符位置。如果这样做,插入符将被绘制在错误的位置,如下图所示:
为了正确定位插入符,需要将偏移量左侧的字形宽度相加,并考虑当前上下文。除非考虑上下文,否则字形度量可能不会与显示匹配。(上下文可能会影响使用哪些字形。)
移动插入符
所有文本编辑器都允许用户使用箭头键移动插入符。用户期望插入符沿按下的箭头键方向移动。在从左到右的文本中,移动插入偏移量很简单:右箭头键将插入偏移量增加一,左箭头键将其减少一。在双向文本或带有连字的文本中,此行为会导致插入符跨越方向边界的字形并在不同方向运行内部反向移动。
要在双向文本中平滑移动插入符,需要考虑文本运行的方向。当按下右箭头键时不能简单地增加插入偏移量,当按下左箭头键时减少它。如果当前插入偏移量位于从右到左字符的运行内,右箭头键应减少插入偏移量,左箭头键应增加它。
在跨越方向边界时移动插入符更加复杂。下图说明了当用户使用箭头键导航时,当穿越方向边界时会发生什么。在显示的文本中向右移动三个位置对应于移动到字符偏移 7、19,然后是 18。
某些字形之间永远不应该有插入符;相反,插入符应该移动得像这些字形代表一个单一字符一样。例如,如果一个 o 和一个变音符由两个单独的字符表示,那么它们之间永远不应该有插入符。
TextLayout
类提供了方法(getNextRightHit
和 getNextLeftHit
),使您能够轻松地在双向文本中平滑地移动插入符。
命中测试
通常,设备空间中的位置必须转换为文本偏移量。例如,当用户在可选择文本上单击鼠标时,鼠标位置将转换为文本偏移量,并用作选择范围的一端。从逻辑上讲,这是放置插入符的逆过程。
当处理双向文本时,显示中的单个视觉位置可以对应源文本中的两个不同偏移量,如下图所示:
因为单个视觉位置可以对应两个不同的偏移量,所以命中测试双向文本不仅仅是测量字形宽度直到找到正确位置的字形,然后将该位置映射回字符偏移量。检测命中位置所在的一侧有助于区分这两种选择。
您可以使用 TextLayout.hitTestChar
进行命中测试。命中信息封装在 TextHitInfo
对象中,并包括有关命中位置所在一侧的信息。
突出显示选择
一段选定的字符范围通过一个高亮区域图形化表示,其中字形以反色或不同背景颜色显示。
高亮区域,就像插入符一样,在双向文本中比单向文本更复杂。在双向文本中,一段连续的字符范围在显示时可能没有连续的高亮区域。相反,显示为视觉上连续的一系列字形的高亮区域可能不对应单一、连续的字符范围。
这导致双向文本中突出选择的两种策略:
- 逻辑高亮:使用逻辑高亮,所选字符在文本模型中始终是连续的,而高亮区域允许是不连续的。以下是逻辑高亮的示例:
- 可视高亮:使用可视高亮,可能会有多个选定字符范围,但高亮区域始终是连续的。以下是可视高亮的示例:
逻辑高亮更容易实现,因为所选字符在文本中始终是连续的。
示例SelectionSample.java
演示了逻辑高亮:
在 Java 应用程序中执行文本布局
根据您使用的 Java API,您可以根据需要对文本布局进行精细或粗略的控制:
- 如果您只想显示一块文本或需要一个可编辑的文本控件,您可以使用
JTextComponent
,它将为您执行文本布局。JTextComponent
旨在处理大多数国际应用程序的需求,并支持双向文本。有关JTextComponent
的更多信息,请参见使用 JTextComponent 类处理双向文本和使用文本组件。 - 如果您想显示一个简单的文本字符串,您可以调用方法
Graphics2D.drawString
,让 Java 2D 为您布局字符串。您还可以使用Graphics2D.drawString
来呈现带样式的字符串和包含双向文本的字符串。有关通过Graphics2D
呈现文本的更多信息,请参见使用文本 API。 - 如果您想实现自己的文本编辑例程,可以使用
TextLayout
来管理文本布局、高亮显示和点击检测。TextLayout
提供的功能处理了大多数常见情况,包括具有混合字体、混合语言和双向文本的文本字符串。有关使用 TextLayout 的更多信息,请参见管理文本布局。 - 如果您想完全控制文本的形状和位置,可以使用
Font
类构建自己的GlyphVector
实例,然后通过Graphics2D
类进行渲染。
通常,您不需要自己执行文本布局操作。对于大多数应用程序,JTextComponent
是显示静态和可编辑文本的最佳解决方案。但是,JTextComponent
不支持双插入符或双向文本中的不连续选择的显示。如果您的应用程序需要这些功能,或者您更喜欢实现自己的文本编辑例程,可以使用 Java 2D 文本布局 API。
使用 TextLayout 类管理文本布局
TextLayout
类支持包含多种样式和来自不同书写系统(包括阿拉伯文和希伯来文)的字符的文本。(阿拉伯文和希伯来文特别难以显示,因为您必须重新排列和重新排序文本以获得可接受的表示。)
即使您只处理英文文本,TextLayout
也简化了显示和测量文本的过程。通过使用 TextLayout
,您可以在不费额外努力的情况下实现高质量的排版。
TextLayout
被设计为在显示简单的单向文本时不会产生显著的性能影响。当使用 TextLayout
显示阿拉伯文或希伯来文时,会有一些额外的处理开销。但是,这通常是每个字符的微秒数量级,并且被正常绘图代码的执行所主导。
TextLayout
类为您管理字形的定位和排序。您可以使用TextLayout
来执行以下操作:
- 使用 TextLayout 类进行文本布局
- 使用 TextLayout 类显示双光标
- 使用 TextLayout 类移动光标
- 使用 TextLayout 类进行点击测试
- 使用 TextLayout 类突出显示选择内容
使用 TextLayout 类进行文本布局
TextLayout
自动布局文本,包括双向文本,具有正确的形状和顺序。为了正确形状和排序表示一行文本的字形,TextLayout
必须了解文本的完整上下文:
- 如果文本适合单行,例如按钮的单词标签或对话框中的一行文本,您可以直接从文本构造一个
TextLayout
。 - 如果您有更多文本无法适合单行或想要在单行文本上分隔制表符段,您不能直接构造一个
TextLayout
。您必须使用LineBreakMeasurer
提供足够的上下文。有关更多信息,请参见绘制多行文本。
文本的基本方向通常由文本上的属性(样式)设置。如果该属性缺失,TextLayout
将遵循 Unicode 双向算法,并从段落中的初始字符推导基本方向。
使用 TextLayout 类显示双光标
TextLayout
保留光标信息,如光标Shape
、位置和角度。您可以使用此信息轻松地在单向和双向文本中显示光标。在为双向文本绘制光标时,使用TextLayout
可确保光标位置正确。
TextLayout
提供默认插入符Shapes
并自动支持双插入符。对于斜体和倾斜字形,TextLayout
会产生倾斜插入符,如下图所示。这些插入符位置也用作高亮和命中测试之间的字形边界,有助于产生一致的用户体验。
给定插入偏移量,getCaretShapes
方法返回一个包含两个Shape
对象的数组:元素 0 包含强插入符,元素 1 包含弱插入符(如果存在)。要显示双插入符,只需绘制两个插入符Shape
对象;插入符将自动呈现在正确的位置。
如果您想使用自定义插入符,可以从TextLayout
中检索插入符的位置和角度,并自行绘制它们。
示例HitTestSample.java
演示了双插入符。
点击希伯来文本旁边的o会记录用户在o之后点击的位置,这部分属于英文文本。这将使弱(黑色)插入符位于o旁边,而强插入符(红色)位于H之前:
点击o右侧的空格会记录用户点击了空格,这部分属于希伯来文本。这将使强(红色)插入符位于o旁边,而弱插入符(黑色)位于H之前:
Java 中文官方教程 2022 版(三十一)(4)https://developer.aliyun.com/article/1488001