本节介绍文本API 的用法,以及他们的渲染能力。至今为止,您已经有了基本的Java 2D 文本API ,同时知道如何设置字体和位置,以及绘制文本。
本节扩展了这些知识,同时更深入的介绍了Java 2D 文本展示的知识。
这些主题在以下几个部分中讨论。
l 选择字体
本节介绍如何使用Font 类中的方法决定系统中存在哪些字体,创建一个Font 对象,同时从字体家族中获得信息。
l 测量字体
本节介绍了如何使用FontMetrics 类的实例测量文本。
l 高级文本展示
本节解释如何定位和渲染一段有风格的文本,如何展示抗锯齿的文本,以及如何使用文本属性定义文本风格。
Java 2D 定义了以下五个逻辑字体家族:
l Dialog
l DialogInput
l Monospaced
l Serif
l SansSerif
这些字体在任何Java 平台都可得,同时可以当做底层平台中有相应属性字体的别名。Serif 字体是和Times New Roman 类似的字体,通常用来打印。Sans Serif 字体通常在屏幕中使用。
这些字体定制成用户的本地化信息。同时,这些字体支持大范围的代码点(unicode 字符集)。
除了家族之外,字体还有其他属性,最重要的是风格和大小。风格是粗体和斜体。
默认的Java 2D 字体是12pt Dialog 。这个字体是通常用在普通的72–120 DPI 现实设备中的字体大小。应用程序可以通过下面的方式创建字体:
Font font = new Font("Dialog", Font.PLAIN, 12); |
除了逻辑字体之外,Java 软件支持访问系统中已经安装的其他字体。所有可找到的字体的可以通过以下方式得到:
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); String []fontFamilies = ge.getAvailableFontFamilyNames(); |
FontSelector 示例程序展示了如何定位和选择这些字体。您可以使用这个例子查看Sans Serif 在您的系统中是什么。其他字体都称为物理字体。
有时,应用程序不能依赖于已经安装到系统中的字体,通常因为这些是自定义字体,在系统中不存在。这种情况下,应用程序必须包含字体。本科展示如何获取TrueType 字体,这是现代操作系统中最常用的字体,将它转换成Font 对象。
您可以使用这些方法之一:
Font java.awt.Font.createFont(int fontFormat, InputStream in); Font java.awt.Font.createFont(int fontFormat, File fontFile); |
要识别一个TrueType 字体,fontFormat 必须是常量Font.TRUETYPE_FONT 。
Font font = Font.createFont(Font.TRUETYPE_FONT, new File("A.ttf")); |
在这种情况下从文件中访问直接访问字体必须是很方便的。然而,如果您的代码不能访问文件系统资源,又或者字体被打包在应用程序或applet 的JAR 文件中,可能需要InputStream 。
被返回的Font 实体可以和Font.deriveFont(..) 方法一起使用,派生一个合适的字体。例如
try { /* Returned font is of pt size 1 */ Font font = Font.createFont(Font.TRUETYPE_FONT, new File("A.ttf"));
/* derive and return a 12 pt version : need to use float otherwise * it would be interpreted as style */ return font.deriveFont(12f);
} catch (IOException ioe); } catch (FontFormatException ffe); } |
使用deriveFont() 时很重要的,因为字体是由应用程序创建的,不是底层操作系统的一部分。因为deriveFont 作用于最初创建的字体,就没有这个限制。
这个问题的解决方案是在图形环境中注册新建的字体,例如:
try { GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); ge.registerFont(Font.createFont(Font.TRUETYPE_FONT, new File("A.ttf")); } catch (IOException ioe); } catch (FontFormatException ffe); } |
当这个步骤完成后,字体可以通过getAvailableFontFamilyNames() 得到,同时也可以在字体的构造函数中使用。
要合适的策略文本,你需要了解一些方法和避免一些错误。字体规格是测量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 组建的大小:
注意以下几点:
l 规格从Graphics 类中获得,因为这个类封装了FontRenderContext ,它是用来精确测量字体的。在屏幕的分辨率下,为了便于阅读,会对字体进行微调。随着文本大小的增加,这种调整不是线性的。所以,在20 pt 下,字体的展示不会精确的和10pt 的两倍完全相同。除了文本自身和字体之外,另一个测量文本的重要信息是FontRenderContext 。这个方法包含从用户空间到设备点阵的转换,可以用来测量文本。
l 高度可以在不引用任何文本中字符串的时候得到。它是很有用的,例如,在文本编辑器中,你想要每行中的文本都保持同样大小的间隔。
l stringWidth() 可以返回文本的整体。整体是文本的起始位置到最终完成渲染的位置的距离。
当使用这些方法测量文本时,注意,文本可以扩展到矩形外的任何位置,这由字体的高度和字符串的整体宽度。
通常,最简单的解决方案是保证文本不被截断,例如,使用组件包装文本。添加一些填充防止字符串的可能剪切。
如果这些解决方案不能满足要求,Java 2D 软件中的其他文本测量API 可以返回矩形边框的范围。这些边框说明了以像素为单位的特定文本的宽度。
Java 2D API 提供了支持复杂文本布局的机制。本节介绍高级文件展示的这些特性。
l 使用渲染提示展示抗锯齿的文本
本节描述如何使用渲染提示控制渲染质量。
l 使用文本属性设计文本样式
本节展示如何使用TextAttribute 类为文本添加下划线或删除线。
l 绘制多行文本
本节展示如何使用TextLayout 和LineBreakMeasurer 类定位和渲染一段文本的样式。
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 可以用它创建不是很模糊的抗锯齿文本,但在小字体时又能清晰展示。要使用这种绘制方式,需要使用以下代码:
graphics2D.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, (RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB); |
下面的代码展示了以下三种抗锯齿功能:
1. 抗锯齿关闭。
2. 抗锯齿开启。
3. 使用TEXT_ANTIALIAS_GASP 提示。
4. 使用TEXT_ANTIALIAS_LCD_HRGB 提示。
应用程序通常需要应用以下文本属性:
l 下划线:文本下面绘制一条线
l 删除线:绘制贯穿文本的一条线。
l 上标和下标:出现在线上或线下很小的文本或字符。
l 字距调整:调整字符间距
可以使用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 ); |
下面的应用程序展示了以下文本属性:
1. 普通字符串(不使用任何属性)
2. 字距调整
3. 字距调整和下划线
4. 字距调整,下划线和删除线
5. 字距调整,下划线,删除线和颜色。
如果你有一个需要适应特定宽度的文本,你可以使用LineBreakMeasurer 类。这个类让文本有断行的风格,这样可以适应特定的宽度。每行作为一个TextLayout 对象返回,它们是不可修改的,有风格的字符数据。然而,这个类也可以访问布局信息。TextLayout 的getAscent 和getDescent 方法返回在组件中放置行的字体。为本作为AttributedCharacterIterator 对象保存,所以文本和点大小可以保存在文本中。
下面的applet 在组建中放置了一段有风格的文本,使用LineBreakMeasurer ,TextLayout和AttributedCharacterIterator 。
下面的代码创建了字符串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 的部分。