LineBreakMeasurer
类允许将带有样式的文本分成适合特定视觉前进的行(或段)。这对希望显示适合特定宽度(称为换行宽度)的文本段落的客户端非常有用。
LineBreakMeasurer
通过对样式文本的迭代器进行构造。迭代器的范围应该是文本中的单个段落。LineBreakMeasurer
在文本中维护一个位置,用于下一个文本段的开始。最初,此位置是文本的开头。根据双向格式化规则,段落被分配一个整体方向(从左到右或从右到左)。从段落获取的所有段具有与段落相同的方向。
通过调用方法nextLayout
获取文本段,该方法返回一个代表适合在换行宽度内的文本的TextLayout
。 nextLayout
方法将当前位置移动到从nextLayout
返回的布局的末尾。
LineBreakMeasurer
实现了最常用的断行策略:适合在换行宽度内的每个单词都放在同一行上。如果第一个单词不适合,则将适合在换行宽度内的所有字符放在同一行上。每行至少放置一个字符。
LineBreakMeasurer
返回的TextLayout
实例将制表符视为0宽度空格。希望获取制表符分隔的段以进行定位的客户端应使用带有文本中限制偏移量的nextLayout
重载。限制偏移量应为制表符后的第一个字符。从此方法返回的TextLayout
对象在提供的限制处结束(或在当前位置和限制之间的文本无法完全适合在换行宽度内时提前结束)。
在将制表符分隔的文本布局时,客户端在将第一个段放在一行后需要稍微不同的断行策略。与将剩余空间中的部分单词放在一行上不同,他们应该将不适合在剩余空间中完全放在下一行上的单词放在下一行上。可以在带有boolean
参数的nextLayout
重载中请求此更改的策略。如果此参数为true
,则如果第一个单词不适合给定空间,则nextLayout
将返回null
。请参阅下面的制表符示例。
一般来说,如果用于构造LineBreakMeasurer
的文本发生更改,则必须构造新的LineBreakMeasurer
以反映更改。(旧的LineBreakMeasurer
仍然可以正常运行,但它不会意识到文本更改。)然而,如果文本更改是插入或删除单个字符,则可以通过调用insertChar
或deleteChar
来“更新”现有的LineBreakMeasurer
。更新现有的LineBreakMeasurer
比创建新的要快得多。基于用户输入修改文本的客户端应该利用这些方法。
示例:
在组件中呈现段落
public void paint(Graphics graphics) { float dx = 0f, dy = 5f; Graphics2D g2d = (Graphics2D)graphics; FontRenderContext frc = g2d.getFontRenderContext(); AttributedString text = new AttributedString("....."); AttributedCharacterIterator paragraph = text.getIterator(); LineBreakMeasurer measurer = new LineBreakMeasurer(paragraph, frc); measurer.setPosition(paragraph.getBeginIndex()); float wrappingWidth = (float)getSize().width; while (measurer.getPosition() < paragraph.getEndIndex()) { TextLayout layout = measurer.nextLayout(wrappingWidth); dy += (layout.getAscent()); float dx = layout.isLeftToRight() ? 0 : (wrappingWidth - layout.getAdvance()); layout.draw(graphics, dx, dy); dy += layout.getDescent() + layout.getLeading(); } }
使用制表符呈现文本。为简单起见,假定整体文本方向为从左到右
public void paint(Graphics graphics) { float leftMargin = 10, rightMargin = 310; float[] tabStops = { 100, 250 }; // assume styledText is an AttributedCharacterIterator, and the number // of tabs in styledText is tabCount int[] tabLocations = new int[tabCount+1]; int i = 0; for (char c = styledText.first(); c != styledText.DONE; c = styledText.next()) { if (c == '\t') { tabLocations[i++] = styledText.getIndex(); } } tabLocations[tabCount] = styledText.getEndIndex() - 1; // Now tabLocations has an entry for every tab's offset in // the text. For convenience, the last entry is tabLocations // is the offset of the last character in the text. LineBreakMeasurer measurer = new LineBreakMeasurer(styledText); int currentTab = 0; float verticalPos = 20; while (measurer.getPosition() < styledText.getEndIndex()) { // Lay out and draw each line. All segments on a line // must be computed before any drawing can occur, since // we must know the largest ascent on the line. // TextLayouts are computed and stored in a Vector; // their horizontal positions are stored in a parallel // Vector. // lineContainsText is true after first segment is drawn boolean lineContainsText = false; boolean lineComplete = false; float maxAscent = 0, maxDescent = 0; float horizontalPos = leftMargin; Vector layouts = new Vector(1); Vector penPositions = new Vector(1); while (!lineComplete) { float wrappingWidth = rightMargin - horizontalPos; TextLayout layout = measurer.nextLayout(wrappingWidth, tabLocations[currentTab]+1, lineContainsText); // layout can be null if lineContainsText is true if (layout != null) { layouts.addElement(layout); penPositions.addElement(Float.valueOf(horizontalPos)); horizontalPos += layout.getAdvance(); maxAscent = Math.max(maxAscent, layout.getAscent()); maxDescent = Math.max(maxDescent, layout.getDescent() + layout.getLeading()); } else { lineComplete = true; } lineContainsText = true; if (measurer.getPosition() == tabLocations[currentTab]+1) { currentTab++; } if (measurer.getPosition() == styledText.getEndIndex()) lineComplete = true; else if (horizontalPos >= tabStops[tabStops.length-1]) lineComplete = true; if (!lineComplete) { // move to next tab stop int j; for (j=0; horizontalPos >= tabStops[j]; j++) {} horizontalPos = tabStops[j]; } } verticalPos += maxAscent; Enumeration layoutEnum = layouts.elements(); Enumeration positionEnum = penPositions.elements(); // now iterate through layouts and draw them while (layoutEnum.hasMoreElements()) { TextLayout nextLayout = (TextLayout) layoutEnum.nextElement(); Float nextPosition = (Float) positionEnum.nextElement(); nextLayout.draw(graphics, nextPosition.floatValue(), verticalPos); } verticalPos += maxDescent; } }
- 参见:
-
Constructor Summary
ConstructorDescription为指定的文本构造一个LineBreakMeasurer
。LineBreakMeasurer
(AttributedCharacterIterator text, BreakIterator breakIter, FontRenderContext frc) 为指定的文本构造一个LineBreakMeasurer
。 -
Method Summary
Modifier and TypeMethodDescriptionvoid
deleteChar
(AttributedCharacterIterator newParagraph, int deletePos) 在从文本中删除单个字符后更新此LineBreakMeasurer
,并将当前位置设置为段落的开头。int
返回此LineBreakMeasurer
的当前位置。void
insertChar
(AttributedCharacterIterator newParagraph, int insertPos) 在文本中插入单个字符后更新此LineBreakMeasurer
,并将当前位置设置为段落的开头。nextLayout
(float wrappingWidth) 返回下一个布局,并更新当前位置。nextLayout
(float wrappingWidth, int offsetLimit, boolean requireNextWord) 返回下一个布局,并更新当前位置。int
nextOffset
(float wrappingWidth) 返回下一个布局的末尾位置。int
nextOffset
(float wrappingWidth, int offsetLimit, boolean requireNextWord) 返回下一个布局的末尾位置。void
setPosition
(int newPosition) 设置此LineBreakMeasurer
的当前位置。
-
Constructor Details
-
LineBreakMeasurer
为指定的文本构造一个LineBreakMeasurer
。- 参数:
-
text
- 该LineBreakMeasurer
用于生成TextLayout
对象的文本;文本必须至少包含一个字符;如果通过iter
可用的文本发生更改,则进一步调用此LineBreakMeasurer
实例的行为是未定义的(除非在此之后调用insertChar
或deleteChar
- 请参见下文) -
frc
- 包含有关需要正确测量文本的图形设备的信息;文本测量可能会因设备分辨率和属性(如抗锯齿)而略有不同;此参数不指定LineBreakMeasurer
和用户空间之间的转换 - 参见:
-
LineBreakMeasurer
public LineBreakMeasurer(AttributedCharacterIterator text, BreakIterator breakIter, FontRenderContext frc) 为指定的文本构造一个LineBreakMeasurer
。- 参数:
-
text
- 为此LineBreakMeasurer
生成TextLayout
对象的文本;文本必须至少包含一个字符;如果通过iter
可用的文本发生更改,则对此LineBreakMeasurer
实例的进一步调用是未定义的(除非在之后调用insertChar
或deleteChar
- 请参见下文) -
breakIter
- 定义换行的BreakIterator
-
frc
- 包含有关图形设备的信息,需要正确测量文本;文本测量可能会因设备分辨率和属性(如抗锯齿)而略有不同;此参数不指定LineBreakMeasurer
和用户空间之间的转换 - 抛出:
-
IllegalArgumentException
- 如果文本少于一个字符 - 参见:
-
-
Method Details
-
nextOffset
public int nextOffset(float wrappingWidth) 返回下一个布局结束的位置。不更新此LineBreakMeasurer
的当前位置。- 参数:
-
wrappingWidth
- 下一个布局中文本允许的最大可见前进 - 返回:
-
表示下一个
TextLayout
的限制的文本中的偏移量。
-
nextOffset
public int nextOffset(float wrappingWidth, int offsetLimit, boolean requireNextWord) 返回下一个布局结束的位置。不更新此LineBreakMeasurer
的当前位置。- 参数:
-
wrappingWidth
- 下一个布局中文本允许的最大可见前进 -
offsetLimit
- 下一个布局中不能包括的第一个字符,即使在限制后的文本适合于换行宽度;offsetLimit
必须大于当前位置 -
requireNextWord
- 如果为true
,并且如果整个下一个单词不适合于wrappingWidth
,则返回当前位置。如果为false
,则返回的偏移量至少比当前位置大一 - 返回:
-
表示下一个
TextLayout
的限制的文本中的偏移量
-
nextLayout
返回下一个布局,并更新当前位置。- 参数:
-
wrappingWidth
- 下一个布局中文本允许的最大可见前进 - 返回:
-
一个
TextLayout
,从当前位置开始,表示适合于wrappingWidth
内的下一行
-
nextLayout
返回下一个布局,并更新当前位置。- 参数:
-
wrappingWidth
- 下一个布局中文本允许的最大可见前进 -
offsetLimit
- 下一个布局中不能包括的第一个字符,即使在限制后的文本适合于换行宽度;offsetLimit
必须大于当前位置 -
requireNextWord
- 如果为true
,并且如果当前位置的整个单词不适合于换行宽度,则返回null
。如果为false
,则返回一个有效的布局,其中至少包括当前位置的字符 - 返回:
-
一个
TextLayout
,从当前位置开始,表示适合于wrappingWidth
内的下一行。如果当前位置位于此LineBreakMeasurer
使用的文本的末尾,则返回null
-
getPosition
public int getPosition()返回此LineBreakMeasurer
的当前位置。- 返回:
-
此
LineBreakMeasurer
的当前位置 - 参见:
-
setPosition
public void setPosition(int newPosition) 设置此LineBreakMeasurer
的当前位置。- 参数:
-
newPosition
- 此LineBreakMeasurer
的当前位置;位置应在用于构造此LineBreakMeasurer
的文本中(或最近传递给insertChar
或deleteChar
的文本中) - 参见:
-
insertChar
在向文本插入单个字符后更新此LineBreakMeasurer
,并将当前位置设置为段落的开头。- 参数:
-
newParagraph
- 插入后的文本 -
insertPos
- 插入字符的文本中的位置 - 抛出:
-
IndexOutOfBoundsException
- 如果insertPos
小于newParagraph
的开头或大于或等于newParagraph
的结尾 -
NullPointerException
- 如果newParagraph
为null
- 参见:
-
deleteChar
在从文本中删除单个字符后更新此LineBreakMeasurer
,并将当前位置设置为段落的开头。- 参数:
-
newParagraph
- 删除后的文本 -
deletePos
- 删除字符的文本中的位置 - 抛出:
-
IndexOutOfBoundsException
- 如果deletePos
小于newParagraph
的开头或大于newParagraph
的结尾 -
NullPointerException
- 如果newParagraph
为null
- 参见:
-