History

My first initial commit with all of mine experimental changes.

Dmitry Kabak (userdima committed 13 years ago

Commit d37b88d1 parent 3f5b46618352 files · +2598093 −-8690Browse files

Showing the changes to java/pkg/java/awt/font/LineBreakMeasurer.java (1 of 18352 files). Show all

Unified · Split

java/pkg/java/awt/font/LineBreakMeasurer.java+533−0
@@ -0,0 +1,533 @@
1+/*
2+ * Copyright (c) 1998, 2001, Oracle and/or its affiliates. All rights reserved.
3+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+ *
5+ * This code is free software; you can redistribute it and/or modify it
6+ * under the terms of the GNU General Public License version 2 only, as
7+ * published by the Free Software Foundation. Oracle designates this
8+ * particular file as subject to the "Classpath" exception as provided
9+ * by Oracle in the LICENSE file that accompanied this code.
10+ *
11+ * This code is distributed in the hope that it will be useful, but WITHOUT
12+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+ * version 2 for more details (a copy is included in the LICENSE file that
15+ * accompanied this code).
16+ *
17+ * You should have received a copy of the GNU General Public License version
18+ * 2 along with this work; if not, write to the Free Software Foundation,
19+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+ *
21+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+ * or visit www.oracle.com if you need additional information or have any
23+ * questions.
24+ */
25+
26+/*
27+ * (C) Copyright Taligent, Inc. 1996 - 1997, All Rights Reserved
28+ * (C) Copyright IBM Corp. 1996 - 1998, All Rights Reserved
29+ *
30+ * The original version of this source code and documentation is
31+ * copyrighted and owned by Taligent, Inc., a wholly-owned subsidiary
32+ * of IBM. These materials are provided under terms of a License
33+ * Agreement between Taligent and Sun. This technology is protected
34+ * by multiple US and International patents.
35+ *
36+ * This notice and attribution to Taligent may not be removed.
37+ * Taligent is a registered trademark of Taligent, Inc.
38+ *
39+ */
40+
41+package java.awt.font;
42+
43+import java.text.BreakIterator;
44+import java.text.CharacterIterator;
45+import java.text.AttributedCharacterIterator;
46+import java.awt.font.FontRenderContext;
47+
48+/**
49+ * The <code>LineBreakMeasurer</code> class allows styled text to be
50+ * broken into lines (or segments) that fit within a particular visual
51+ * advance. This is useful for clients who wish to display a paragraph of
52+ * text that fits within a specific width, called the <b>wrapping
53+ * width</b>.
54+ * <p>
55+ * <code>LineBreakMeasurer</code> is constructed with an iterator over
56+ * styled text. The iterator's range should be a single paragraph in the
57+ * text.
58+ * <code>LineBreakMeasurer</code> maintains a position in the text for the
59+ * start of the next text segment. Initially, this position is the
60+ * start of text. Paragraphs are assigned an overall direction (either
61+ * left-to-right or right-to-left) according to the bidirectional
62+ * formatting rules. All segments obtained from a paragraph have the
63+ * same direction as the paragraph.
64+ * <p>
65+ * Segments of text are obtained by calling the method
66+ * <code>nextLayout</code>, which returns a {@link TextLayout}
67+ * representing the text that fits within the wrapping width.
68+ * The <code>nextLayout</code> method moves the current position
69+ * to the end of the layout returned from <code>nextLayout</code>.
70+ * <p>
71+ * <code>LineBreakMeasurer</code> implements the most commonly used
72+ * line-breaking policy: Every word that fits within the wrapping
73+ * width is placed on the line. If the first word does not fit, then all
74+ * of the characters that fit within the wrapping width are placed on the
75+ * line. At least one character is placed on each line.
76+ * <p>
77+ * The <code>TextLayout</code> instances returned by
78+ * <code>LineBreakMeasurer</code> treat tabs like 0-width spaces. Clients
79+ * who wish to obtain tab-delimited segments for positioning should use
80+ * the overload of <code>nextLayout</code> which takes a limiting offset
81+ * in the text.
82+ * The limiting offset should be the first character after the tab.
83+ * The <code>TextLayout</code> objects returned from this method end
84+ * at the limit provided (or before, if the text between the current
85+ * position and the limit won't fit entirely within the wrapping
86+ * width).
87+ * <p>
88+ * Clients who are laying out tab-delimited text need a slightly
89+ * different line-breaking policy after the first segment has been
90+ * placed on a line. Instead of fitting partial words in the
91+ * remaining space, they should place words which don't fit in the
92+ * remaining space entirely on the next line. This change of policy
93+ * can be requested in the overload of <code>nextLayout</code> which
94+ * takes a <code>boolean</code> parameter. If this parameter is
95+ * <code>true</code>, <code>nextLayout</code> returns
96+ * <code>null</code> if the first word won't fit in
97+ * the given space. See the tab sample below.
98+ * <p>
99+ * In general, if the text used to construct the
100+ * <code>LineBreakMeasurer</code> changes, a new
101+ * <code>LineBreakMeasurer</code> must be constructed to reflect
102+ * the change. (The old <code>LineBreakMeasurer</code> continues to
103+ * function properly, but it won't be aware of the text change.)
104+ * Nevertheless, if the text change is the insertion or deletion of a
105+ * single character, an existing <code>LineBreakMeasurer</code> can be
106+ * 'updated' by calling <code>insertChar</code> or
107+ * <code>deleteChar</code>. Updating an existing
108+ * <code>LineBreakMeasurer</code> is much faster than creating a new one.
109+ * Clients who modify text based on user typing should take advantage
110+ * of these methods.
111+ * <p>
112+ * <strong>Examples</strong>:<p>
113+ * Rendering a paragraph in a component
114+ * <blockquote>
115+ * <pre>
116+ * public void paint(Graphics graphics) {
117+ *
118+ * Point2D pen = new Point2D(10, 20);
119+ * Graphics2D g2d = (Graphics2D)graphics;
120+ * FontRenderContext frc = g2d.getFontRenderContext();
121+ *
122+ * // let styledText be an AttributedCharacterIterator containing at least
123+ * // one character
124+ *
125+ * LineBreakMeasurer measurer = new LineBreakMeasurer(styledText, frc);
126+ * float wrappingWidth = getSize().width - 15;
127+ *
128+ * while (measurer.getPosition() < fStyledText.length()) {
129+ *
130+ * TextLayout layout = measurer.nextLayout(wrappingWidth);
131+ *
132+ * pen.y += (layout.getAscent());
133+ * float dx = layout.isLeftToRight() ?
134+ * 0 : (wrappingWidth - layout.getAdvance());
135+ *
136+ * layout.draw(graphics, pen.x + dx, pen.y);
137+ * pen.y += layout.getDescent() + layout.getLeading();
138+ * }
139+ * }
140+ * </pre>
141+ * </blockquote>
142+ * <p>
143+ * Rendering text with tabs. For simplicity, the overall text
144+ * direction is assumed to be left-to-right
145+ * <blockquote>
146+ * <pre>
147+ * public void paint(Graphics graphics) {
148+ *
149+ * float leftMargin = 10, rightMargin = 310;
150+ * float[] tabStops = { 100, 250 };
151+ *
152+ * // assume styledText is an AttributedCharacterIterator, and the number
153+ * // of tabs in styledText is tabCount
154+ *
155+ * int[] tabLocations = new int[tabCount+1];
156+ *
157+ * int i = 0;
158+ * for (char c = styledText.first(); c != styledText.DONE; c = styledText.next()) {
159+ * if (c == '\t') {
160+ * tabLocations[i++] = styledText.getIndex();
161+ * }
162+ * }
163+ * tabLocations[tabCount] = styledText.getEndIndex() - 1;
164+ *
165+ * // Now tabLocations has an entry for every tab's offset in
166+ * // the text. For convenience, the last entry is tabLocations
167+ * // is the offset of the last character in the text.
168+ *
169+ * LineBreakMeasurer measurer = new LineBreakMeasurer(styledText);
170+ * int currentTab = 0;
171+ * float verticalPos = 20;
172+ *
173+ * while (measurer.getPosition() < styledText.getEndIndex()) {
174+ *
175+ * // Lay out and draw each line. All segments on a line
176+ * // must be computed before any drawing can occur, since
177+ * // we must know the largest ascent on the line.
178+ * // TextLayouts are computed and stored in a Vector;
179+ * // their horizontal positions are stored in a parallel
180+ * // Vector.
181+ *
182+ * // lineContainsText is true after first segment is drawn
183+ * boolean lineContainsText = false;
184+ * boolean lineComplete = false;
185+ * float maxAscent = 0, maxDescent = 0;
186+ * float horizontalPos = leftMargin;
187+ * Vector layouts = new Vector(1);
188+ * Vector penPositions = new Vector(1);
189+ *
190+ * while (!lineComplete) {
191+ * float wrappingWidth = rightMargin - horizontalPos;
192+ * TextLayout layout =
193+ * measurer.nextLayout(wrappingWidth,
194+ * tabLocations[currentTab]+1,
195+ * lineContainsText);
196+ *
197+ * // layout can be null if lineContainsText is true
198+ * if (layout != null) {
199+ * layouts.addElement(layout);
200+ * penPositions.addElement(new Float(horizontalPos));
201+ * horizontalPos += layout.getAdvance();
202+ * maxAscent = Math.max(maxAscent, layout.getAscent());
203+ * maxDescent = Math.max(maxDescent,
204+ * layout.getDescent() + layout.getLeading());
205+ * } else {
206+ * lineComplete = true;
207+ * }
208+ *
209+ * lineContainsText = true;
210+ *
211+ * if (measurer.getPosition() == tabLocations[currentTab]+1) {
212+ * currentTab++;
213+ * }
214+ *
215+ * if (measurer.getPosition() == styledText.getEndIndex())
216+ * lineComplete = true;
217+ * else if (horizontalPos >= tabStops[tabStops.length-1])
218+ * lineComplete = true;
219+ *
220+ * if (!lineComplete) {
221+ * // move to next tab stop
222+ * int j;
223+ * for (j=0; horizontalPos >= tabStops[j]; j++) {}
224+ * horizontalPos = tabStops[j];
225+ * }
226+ * }
227+ *
228+ * verticalPos += maxAscent;
229+ *
230+ * Enumeration layoutEnum = layouts.elements();
231+ * Enumeration positionEnum = penPositions.elements();
232+ *
233+ * // now iterate through layouts and draw them
234+ * while (layoutEnum.hasMoreElements()) {
235+ * TextLayout nextLayout = (TextLayout) layoutEnum.nextElement();
236+ * Float nextPosition = (Float) positionEnum.nextElement();
237+ * nextLayout.draw(graphics, nextPosition.floatValue(), verticalPos);
238+ * }
239+ *
240+ * verticalPos += maxDescent;
241+ * }
242+ * }
243+ * </pre>
244+ * </blockquote>
245+ * @see TextLayout
246+ */
247+
248+public final class LineBreakMeasurer {
249+
250+ private BreakIterator breakIter;
251+ private int start;
252+ private int pos;
253+ private int limit;
254+ private TextMeasurer measurer;
255+ private CharArrayIterator charIter;
256+
257+ /**
258+ * Constructs a <code>LineBreakMeasurer</code> for the specified text.
259+ *
260+ * @param text the text for which this <code>LineBreakMeasurer</code>
261+ * produces <code>TextLayout</code> objects; the text must contain
262+ * at least one character; if the text available through
263+ * <code>iter</code> changes, further calls to this
264+ * <code>LineBreakMeasurer</code> instance are undefined (except,
265+ * in some cases, when <code>insertChar</code> or
266+ * <code>deleteChar</code> are invoked afterward - see below)
267+ * @param frc contains information about a graphics device which is
268+ * needed to measure the text correctly;
269+ * text measurements can vary slightly depending on the
270+ * device resolution, and attributes such as antialiasing; this
271+ * parameter does not specify a translation between the
272+ * <code>LineBreakMeasurer</code> and user space
273+ * @see LineBreakMeasurer#insertChar
274+ * @see LineBreakMeasurer#deleteChar
275+ */
276+ public LineBreakMeasurer(AttributedCharacterIterator text, FontRenderContext frc) {
277+ this(text, BreakIterator.getLineInstance(), frc);
278+ }
279+
280+ /**
281+ * Constructs a <code>LineBreakMeasurer</code> for the specified text.
282+ *
283+ * @param text the text for which this <code>LineBreakMeasurer</code>
284+ * produces <code>TextLayout</code> objects; the text must contain
285+ * at least one character; if the text available through
286+ * <code>iter</code> changes, further calls to this
287+ * <code>LineBreakMeasurer</code> instance are undefined (except,
288+ * in some cases, when <code>insertChar</code> or
289+ * <code>deleteChar</code> are invoked afterward - see below)
290+ * @param breakIter the {@link BreakIterator} which defines line
291+ * breaks
292+ * @param frc contains information about a graphics device which is
293+ * needed to measure the text correctly;
294+ * text measurements can vary slightly depending on the
295+ * device resolution, and attributes such as antialiasing; this
296+ * parameter does not specify a translation between the
297+ * <code>LineBreakMeasurer</code> and user space
298+ * @throws IllegalArgumentException if the text has less than one character
299+ * @see LineBreakMeasurer#insertChar
300+ * @see LineBreakMeasurer#deleteChar
301+ */
302+ public LineBreakMeasurer(AttributedCharacterIterator text,
303+ BreakIterator breakIter,
304+ FontRenderContext frc) {
305+ if (text.getEndIndex() - text.getBeginIndex() < 1) {
306+ throw new IllegalArgumentException("Text must contain at least one character.");
307+ }
308+
309+ this.breakIter = breakIter;
310+ this.measurer = new TextMeasurer(text, frc);
311+ this.limit = text.getEndIndex();
312+ this.pos = this.start = text.getBeginIndex();
313+
314+ charIter = new CharArrayIterator(measurer.getChars(), this.start);
315+ this.breakIter.setText(charIter);
316+ }
317+
318+ /**
319+ * Returns the position at the end of the next layout. Does NOT
320+ * update the current position of this <code>LineBreakMeasurer</code>.
321+ *
322+ * @param wrappingWidth the maximum visible advance permitted for
323+ * the text in the next layout
324+ * @return an offset in the text representing the limit of the
325+ * next <code>TextLayout</code>.
326+ */
327+ public int nextOffset(float wrappingWidth) {
328+ return nextOffset(wrappingWidth, limit, false);
329+ }
330+
331+ /**
332+ * Returns the position at the end of the next layout. Does NOT
333+ * update the current position of this <code>LineBreakMeasurer</code>.
334+ *
335+ * @param wrappingWidth the maximum visible advance permitted for
336+ * the text in the next layout
337+ * @param offsetLimit the first character that can not be included
338+ * in the next layout, even if the text after the limit would fit
339+ * within the wrapping width; <code>offsetLimit</code> must be
340+ * greater than the current position
341+ * @param requireNextWord if <code>true</code>, the current position
342+ * that is returned if the entire next word does not fit within
343+ * <code>wrappingWidth</code>; if <code>false</code>, the offset
344+ * returned is at least one greater than the current position
345+ * @return an offset in the text representing the limit of the
346+ * next <code>TextLayout</code>
347+ */
348+ public int nextOffset(float wrappingWidth, int offsetLimit,
349+ boolean requireNextWord) {
350+
351+ int nextOffset = pos;
352+
353+ if (pos < limit) {
354+ if (offsetLimit <= pos) {
355+ throw new IllegalArgumentException("offsetLimit must be after current position");
356+ }
357+
358+ int charAtMaxAdvance =
359+ measurer.getLineBreakIndex(pos, wrappingWidth);
360+
361+ if (charAtMaxAdvance == limit) {
362+ nextOffset = limit;
363+ }
364+ else if (Character.isWhitespace(measurer.getChars()[charAtMaxAdvance-start])) {
365+ nextOffset = breakIter.following(charAtMaxAdvance);
366+ }
367+ else {
368+ // Break is in a word; back up to previous break.
369+
370+ // NOTE: I think that breakIter.preceding(limit) should be
371+ // equivalent to breakIter.last(), breakIter.previous() but
372+ // the authors of BreakIterator thought otherwise...
373+ // If they were equivalent then the first branch would be
374+ // unnecessary.
375+ int testPos = charAtMaxAdvance + 1;
376+ if (testPos == limit) {
377+ breakIter.last();
378+ nextOffset = breakIter.previous();
379+ }
380+ else {
381+ nextOffset = breakIter.preceding(testPos);
382+ }
383+
384+ if (nextOffset <= pos) {
385+ // first word doesn't fit on line
386+ if (requireNextWord) {
387+ nextOffset = pos;
388+ }
389+ else {
390+ nextOffset = Math.max(pos+1, charAtMaxAdvance);
391+ }
392+ }
393+ }
394+ }
395+
396+ if (nextOffset > offsetLimit) {
397+ nextOffset = offsetLimit;
398+ }
399+
400+ return nextOffset;
401+ }
402+
403+ /**
404+ * Returns the next layout, and updates the current position.
405+ *
406+ * @param wrappingWidth the maximum visible advance permitted for
407+ * the text in the next layout
408+ * @return a <code>TextLayout</code>, beginning at the current
409+ * position, which represents the next line fitting within
410+ * <code>wrappingWidth</code>
411+ */
412+ public TextLayout nextLayout(float wrappingWidth) {
413+ return nextLayout(wrappingWidth, limit, false);
414+ }
415+
416+ /**
417+ * Returns the next layout, and updates the current position.
418+ *
419+ * @param wrappingWidth the maximum visible advance permitted
420+ * for the text in the next layout
421+ * @param offsetLimit the first character that can not be
422+ * included in the next layout, even if the text after the limit
423+ * would fit within the wrapping width; <code>offsetLimit</code>
424+ * must be greater than the current position
425+ * @param requireNextWord if <code>true</code>, and if the entire word
426+ * at the current position does not fit within the wrapping width,
427+ * <code>null</code> is returned. If <code>false</code>, a valid
428+ * layout is returned that includes at least the character at the
429+ * current position
430+ * @return a <code>TextLayout</code>, beginning at the current
431+ * position, that represents the next line fitting within
432+ * <code>wrappingWidth</code>. If the current position is at the end
433+ * of the text used by this <code>LineBreakMeasurer</code>,
434+ * <code>null</code> is returned
435+ */
436+ public TextLayout nextLayout(float wrappingWidth, int offsetLimit,
437+ boolean requireNextWord) {
438+
439+ if (pos < limit) {
440+ int layoutLimit = nextOffset(wrappingWidth, offsetLimit, requireNextWord);
441+ if (layoutLimit == pos) {
442+ return null;
443+ }
444+
445+ TextLayout result = measurer.getLayout(pos, layoutLimit);
446+ pos = layoutLimit;
447+
448+ return result;
449+ } else {
450+ return null;
451+ }
452+ }
453+
454+ /**
455+ * Returns the current position of this <code>LineBreakMeasurer</code>.
456+ *
457+ * @return the current position of this <code>LineBreakMeasurer</code>
458+ * @see #setPosition
459+ */
460+ public int getPosition() {
461+ return pos;
462+ }
463+
464+ /**
465+ * Sets the current position of this <code>LineBreakMeasurer</code>.
466+ *
467+ * @param newPosition the current position of this
468+ * <code>LineBreakMeasurer</code>; the position should be within the
469+ * text used to construct this <code>LineBreakMeasurer</code> (or in
470+ * the text most recently passed to <code>insertChar</code>
471+ * or <code>deleteChar</code>
472+ * @see #getPosition
473+ */
474+ public void setPosition(int newPosition) {
475+ if (newPosition < start || newPosition > limit) {
476+ throw new IllegalArgumentException("position is out of range");
477+ }
478+ pos = newPosition;
479+ }
480+
481+ /**
482+ * Updates this <code>LineBreakMeasurer</code> after a single
483+ * character is inserted into the text, and sets the current
484+ * position to the beginning of the paragraph.
485+ *
486+ * @param newParagraph the text after the insertion
487+ * @param insertPos the position in the text at which the character
488+ * is inserted
489+ * @throws IndexOutOfBoundsException if <code>insertPos</code> is less
490+ * than the start of <code>newParagraph</code> or greater than
491+ * or equal to the end of <code>newParagraph</code>
492+ * @throws NullPointerException if <code>newParagraph</code> is
493+ * <code>null</code>
494+ * @see #deleteChar
495+ */
496+ public void insertChar(AttributedCharacterIterator newParagraph,
497+ int insertPos) {
498+
499+ measurer.insertChar(newParagraph, insertPos);
500+
501+ limit = newParagraph.getEndIndex();
502+ pos = start = newParagraph.getBeginIndex();
503+
504+ charIter.reset(measurer.getChars(), newParagraph.getBeginIndex());
505+ breakIter.setText(charIter);
506+ }
507+
508+ /**
509+ * Updates this <code>LineBreakMeasurer</code> after a single
510+ * character is deleted from the text, and sets the current
511+ * position to the beginning of the paragraph.
512+ * @param newParagraph the text after the deletion
513+ * @param deletePos the position in the text at which the character
514+ * is deleted
515+ * @throws IndexOutOfBoundsException if <code>deletePos</code> is
516+ * less than the start of <code>newParagraph</code> or greater
517+ * than the end of <code>newParagraph</code>
518+ * @throws NullPointerException if <code>newParagraph</code> is
519+ * <code>null</code>
520+ * @see #insertChar
521+ */
522+ public void deleteChar(AttributedCharacterIterator newParagraph,
523+ int deletePos) {
524+
525+ measurer.deleteChar(newParagraph, deletePos);
526+
527+ limit = newParagraph.getEndIndex();
528+ pos = start = newParagraph.getBeginIndex();
529+
530+ charIter.reset(measurer.getChars(), start);
531+ breakIter.setText(charIter);
532+ }
533+}