1 /*
2  * Copyright (c) 1997, 2011, 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.awt.Font;
44 
45 import java.text.AttributedCharacterIterator;
46 import java.text.AttributedCharacterIterator.Attribute;
47 import java.text.AttributedString;
48 import java.text.Bidi;
49 import java.text.BreakIterator;
50 import java.text.CharacterIterator;
51 
52 import java.awt.font.FontRenderContext;
53 
54 import java.util.Hashtable;
55 import java.util.Map;
56 
57 import sun.font.AttributeValues;
58 import sun.font.BidiUtils;
59 import sun.font.TextLineComponent;
60 import sun.font.TextLabelFactory;
61 import sun.font.FontResolver;
62 
63 /**
64  * The {@code TextMeasurer} class provides the primitive operations
65  * needed for line break: measuring up to a given advance, determining the
66  * advance of a range of characters, and generating a
67  * {@code TextLayout} for a range of characters. It also provides
68  * methods for incremental editing of paragraphs.
69  * <p>
70  * A {@code TextMeasurer} object is constructed with an
71  * {@link java.text.AttributedCharacterIterator AttributedCharacterIterator}
72  * representing a single paragraph of text.  The value returned by the
73  * {@link AttributedCharacterIterator#getBeginIndex() getBeginIndex}
74  * method of {@code AttributedCharacterIterator}
75  * defines the absolute index of the first character.  The value
76  * returned by the
77  * {@link AttributedCharacterIterator#getEndIndex() getEndIndex}
78  * method of {@code AttributedCharacterIterator} defines the index
79  * past the last character.  These values define the range of indexes to
80  * use in calls to the {@code TextMeasurer}.  For example, calls to
81  * get the advance of a range of text or the line break of a range of text
82  * must use indexes between the beginning and end index values.  Calls to
83  * {@link #insertChar(java.text.AttributedCharacterIterator, int) insertChar}
84  * and
85  * {@link #deleteChar(java.text.AttributedCharacterIterator, int) deleteChar}
86  * reset the {@code TextMeasurer} to use the beginning index and end
87  * index of the {@code AttributedCharacterIterator} passed in those calls.
88  * <p>
89  * Most clients will use the more convenient {@code LineBreakMeasurer},
90  * which implements the standard line break policy (placing as many words
91  * as will fit on each line).
92  *
93  * @author John Raley
94  * @see LineBreakMeasurer
95  * @since 1.3
96  */
97 
98 public final class TextMeasurer implements Cloneable {
99 
100     // Number of lines to format to.
101     private static float EST_LINES = (float) 2.1;
102 
103     /*
104     static {
105         String s = System.getProperty("estLines");
106         if (s != null) {
107             try {
108                 Float f = Float.valueOf(s);
109                 EST_LINES = f.floatValue();
110             }
111             catch(NumberFormatException e) {
112             }
113         }
114         //System.out.println("EST_LINES="+EST_LINES);
115     }
116     */
117 
118     private FontRenderContext fFrc;
119 
120     private int fStart;
121 
122     // characters in source text
123     private char[] fChars;
124 
125     // Bidi for this paragraph
126     private Bidi fBidi;
127 
128     // Levels array for chars in this paragraph - needed to reorder
129     // trailing counterdirectional whitespace
130     private byte[] fLevels;
131 
132     // line components in logical order
133     private TextLineComponent[] fComponents;
134 
135     // index where components begin
136     private int fComponentStart;
137 
138     // index where components end
139     private int fComponentLimit;
140 
141     private boolean haveLayoutWindow;
142 
143     // used to find valid starting points for line components
144     private BreakIterator fLineBreak = null;
145     private CharArrayIterator charIter = null;
146     int layoutCount = 0;
147     int layoutCharCount = 0;
148 
149     // paragraph, with resolved fonts and styles
150     private StyledParagraph fParagraph;
151 
152     // paragraph data - same across all layouts
153     private boolean fIsDirectionLTR;
154     private byte fBaseline;
155     private float[] fBaselineOffsets;
156     private float fJustifyRatio = 1;
157 
158     /**
159      * Constructs a {@code TextMeasurer} from the source text.
160      * The source text should be a single entire paragraph.
161      * @param text the source paragraph.  Cannot be null.
162      * @param frc the information about a graphics device which is needed
163      *       to measure the text correctly.  Cannot be null.
164      */
TextMeasurer(AttributedCharacterIterator text, FontRenderContext frc)165     public TextMeasurer(AttributedCharacterIterator text, FontRenderContext frc) {
166 
167         fFrc = frc;
168         initAll(text);
169     }
170 
clone()171     protected Object clone() {
172         TextMeasurer other;
173         try {
174             other = (TextMeasurer) super.clone();
175         }
176         catch(CloneNotSupportedException e) {
177             throw new Error();
178         }
179         if (fComponents != null) {
180             other.fComponents = fComponents.clone();
181         }
182         return other;
183     }
184 
invalidateComponents()185     private void invalidateComponents() {
186         fComponentStart = fComponentLimit = fChars.length;
187         fComponents = null;
188         haveLayoutWindow = false;
189     }
190 
191     /**
192      * Initialize state, including fChars array, direction, and
193      * fBidi.
194      */
initAll(AttributedCharacterIterator text)195     private void initAll(AttributedCharacterIterator text) {
196 
197         fStart = text.getBeginIndex();
198 
199         // extract chars
200         fChars = new char[text.getEndIndex() - fStart];
201 
202         int n = 0;
203         for (char c = text.first();
204              c != CharacterIterator.DONE;
205              c = text.next())
206         {
207             fChars[n++] = c;
208         }
209 
210         text.first();
211 
212         fBidi = new Bidi(text);
213         if (fBidi.isLeftToRight()) {
214             fBidi = null;
215         }
216 
217         text.first();
218         Map<? extends Attribute, ?> paragraphAttrs = text.getAttributes();
219         NumericShaper shaper = AttributeValues.getNumericShaping(paragraphAttrs);
220         if (shaper != null) {
221             shaper.shape(fChars, 0, fChars.length);
222         }
223 
224         fParagraph = new StyledParagraph(text, fChars);
225 
226         // set paragraph attributes
227         {
228             // If there's an embedded graphic at the start of the
229             // paragraph, look for the first non-graphic character
230             // and use it and its font to initialize the paragraph.
231             // If not, use the first graphic to initialize.
232             fJustifyRatio = AttributeValues.getJustification(paragraphAttrs);
233 
234             boolean haveFont = TextLine.advanceToFirstFont(text);
235 
236             if (haveFont) {
237                 Font defaultFont = TextLine.getFontAtCurrentPos(text);
238                 int charsStart = text.getIndex() - text.getBeginIndex();
239                 LineMetrics lm = defaultFont.getLineMetrics(fChars, charsStart, charsStart+1, fFrc);
240                 fBaseline = (byte) lm.getBaselineIndex();
241                 fBaselineOffsets = lm.getBaselineOffsets();
242             }
243             else {
244                 // hmmm what to do here?  Just try to supply reasonable
245                 // values I guess.
246 
247                 GraphicAttribute graphic = (GraphicAttribute)
248                                 paragraphAttrs.get(TextAttribute.CHAR_REPLACEMENT);
249                 fBaseline = TextLayout.getBaselineFromGraphic(graphic);
250                 Hashtable<Attribute, ?> fmap = new Hashtable<>(5, (float)0.9);
251                 Font dummyFont = new Font(fmap);
252                 LineMetrics lm = dummyFont.getLineMetrics(" ", 0, 1, fFrc);
253                 fBaselineOffsets = lm.getBaselineOffsets();
254             }
255             fBaselineOffsets = TextLine.getNormalizedOffsets(fBaselineOffsets, fBaseline);
256         }
257 
258         invalidateComponents();
259     }
260 
261     /**
262      * Generate components for the paragraph.  fChars, fBidi should have been
263      * initialized already.
264      */
generateComponents(int startingAt, int endingAt)265     private void generateComponents(int startingAt, int endingAt) {
266 
267         if (collectStats) {
268             formattedChars += (endingAt-startingAt);
269         }
270         int layoutFlags = 0; // no extra info yet, bidi determines run and line direction
271         TextLabelFactory factory = new TextLabelFactory(fFrc, fChars, fBidi, layoutFlags);
272 
273         int[] charsLtoV = null;
274 
275         if (fBidi != null) {
276             fLevels = BidiUtils.getLevels(fBidi);
277             int[] charsVtoL = BidiUtils.createVisualToLogicalMap(fLevels);
278             charsLtoV = BidiUtils.createInverseMap(charsVtoL);
279             fIsDirectionLTR = fBidi.baseIsLeftToRight();
280         }
281         else {
282             fLevels = null;
283             fIsDirectionLTR = true;
284         }
285 
286         try {
287             fComponents = TextLine.getComponents(
288                 fParagraph, fChars, startingAt, endingAt, charsLtoV, fLevels, factory);
289         }
290         catch(IllegalArgumentException e) {
291             System.out.println("startingAt="+startingAt+"; endingAt="+endingAt);
292             System.out.println("fComponentLimit="+fComponentLimit);
293             throw e;
294         }
295 
296         fComponentStart = startingAt;
297         fComponentLimit = endingAt;
298         //debugFormatCount += (endingAt-startingAt);
299     }
300 
calcLineBreak(final int pos, final float maxAdvance)301     private int calcLineBreak(final int pos, final float maxAdvance) {
302 
303         // either of these statements removes the bug:
304         //generateComponents(0, fChars.length);
305         //generateComponents(pos, fChars.length);
306 
307         int startPos = pos;
308         float width = maxAdvance;
309 
310         int tlcIndex;
311         int tlcStart = fComponentStart;
312 
313         for (tlcIndex = 0; tlcIndex < fComponents.length; tlcIndex++) {
314             int gaLimit = tlcStart + fComponents[tlcIndex].getNumCharacters();
315             if (gaLimit > startPos) {
316                 break;
317             }
318             else {
319                 tlcStart = gaLimit;
320             }
321         }
322 
323         // tlcStart is now the start of the tlc at tlcIndex
324 
325         for (; tlcIndex < fComponents.length; tlcIndex++) {
326 
327             TextLineComponent tlc = fComponents[tlcIndex];
328             int numCharsInGa = tlc.getNumCharacters();
329 
330             int lineBreak = tlc.getLineBreakIndex(startPos - tlcStart, width);
331             if (lineBreak == numCharsInGa && tlcIndex < fComponents.length) {
332                 width -= tlc.getAdvanceBetween(startPos - tlcStart, lineBreak);
333                 tlcStart += numCharsInGa;
334                 startPos = tlcStart;
335             }
336             else {
337                 return tlcStart + lineBreak;
338             }
339         }
340 
341         if (fComponentLimit < fChars.length) {
342             // format more text and try again
343             //if (haveLayoutWindow) {
344             //    outOfWindow++;
345             //}
346 
347             generateComponents(pos, fChars.length);
348             return calcLineBreak(pos, maxAdvance);
349         }
350 
351         return fChars.length;
352     }
353 
354     /**
355      * According to the Unicode Bidirectional Behavior specification
356      * (Unicode Standard 2.0, section 3.11), whitespace at the ends
357      * of lines which would naturally flow against the base direction
358      * must be made to flow with the line direction, and moved to the
359      * end of the line.  This method returns the start of the sequence
360      * of trailing whitespace characters to move to the end of a
361      * line taken from the given range.
362      */
trailingCdWhitespaceStart(int startPos, int limitPos)363     private int trailingCdWhitespaceStart(int startPos, int limitPos) {
364 
365         if (fLevels != null) {
366             // Back up over counterdirectional whitespace
367             final byte baseLevel = (byte) (fIsDirectionLTR? 0 : 1);
368             for (int cdWsStart = limitPos; --cdWsStart >= startPos;) {
369                 if ((fLevels[cdWsStart] % 2) == baseLevel ||
370                         Character.getDirectionality(fChars[cdWsStart]) != Character.DIRECTIONALITY_WHITESPACE) {
371                     return ++cdWsStart;
372                 }
373             }
374         }
375 
376         return startPos;
377     }
378 
makeComponentsOnRange(int startPos, int limitPos)379     private TextLineComponent[] makeComponentsOnRange(int startPos,
380                                                       int limitPos) {
381 
382         // sigh I really hate to do this here since it's part of the
383         // bidi algorithm.
384         // cdWsStart is the start of the trailing counterdirectional
385         // whitespace
386         final int cdWsStart = trailingCdWhitespaceStart(startPos, limitPos);
387 
388         int tlcIndex;
389         int tlcStart = fComponentStart;
390 
391         for (tlcIndex = 0; tlcIndex < fComponents.length; tlcIndex++) {
392             int gaLimit = tlcStart + fComponents[tlcIndex].getNumCharacters();
393             if (gaLimit > startPos) {
394                 break;
395             }
396             else {
397                 tlcStart = gaLimit;
398             }
399         }
400 
401         // tlcStart is now the start of the tlc at tlcIndex
402 
403         int componentCount;
404         {
405             boolean split = false;
406             int compStart = tlcStart;
407             int lim=tlcIndex;
408             for (boolean cont=true; cont; lim++) {
409                 int gaLimit = compStart + fComponents[lim].getNumCharacters();
410                 if (cdWsStart > Math.max(compStart, startPos)
411                             && cdWsStart < Math.min(gaLimit, limitPos)) {
412                     split = true;
413                 }
414                 if (gaLimit >= limitPos) {
415                     cont=false;
416                 }
417                 else {
418                     compStart = gaLimit;
419                 }
420             }
421             componentCount = lim-tlcIndex;
422             if (split) {
423                 componentCount++;
424             }
425         }
426 
427         TextLineComponent[] components = new TextLineComponent[componentCount];
428         int newCompIndex = 0;
429         int linePos = startPos;
430 
431         int breakPt = cdWsStart;
432 
433         int subsetFlag;
434         if (breakPt == startPos) {
435             subsetFlag = fIsDirectionLTR? TextLineComponent.LEFT_TO_RIGHT :
436                                           TextLineComponent.RIGHT_TO_LEFT;
437             breakPt = limitPos;
438         }
439         else {
440             subsetFlag = TextLineComponent.UNCHANGED;
441         }
442 
443         while (linePos < limitPos) {
444 
445             int compLength = fComponents[tlcIndex].getNumCharacters();
446             int tlcLimit = tlcStart + compLength;
447 
448             int start = Math.max(linePos, tlcStart);
449             int limit = Math.min(breakPt, tlcLimit);
450 
451             components[newCompIndex++] = fComponents[tlcIndex].getSubset(
452                                                                 start-tlcStart,
453                                                                 limit-tlcStart,
454                                                                 subsetFlag);
455             linePos += (limit-start);
456             if (linePos == breakPt) {
457                 breakPt = limitPos;
458                 subsetFlag = fIsDirectionLTR? TextLineComponent.LEFT_TO_RIGHT :
459                                               TextLineComponent.RIGHT_TO_LEFT;
460             }
461             if (linePos == tlcLimit) {
462                 tlcIndex++;
463                 tlcStart = tlcLimit;
464             }
465         }
466 
467         return components;
468     }
469 
makeTextLineOnRange(int startPos, int limitPos)470     private TextLine makeTextLineOnRange(int startPos, int limitPos) {
471 
472         int[] charsLtoV = null;
473         byte[] charLevels = null;
474 
475         if (fBidi != null) {
476             Bidi lineBidi = fBidi.createLineBidi(startPos, limitPos);
477             charLevels = BidiUtils.getLevels(lineBidi);
478             int[] charsVtoL = BidiUtils.createVisualToLogicalMap(charLevels);
479             charsLtoV = BidiUtils.createInverseMap(charsVtoL);
480         }
481 
482         TextLineComponent[] components = makeComponentsOnRange(startPos, limitPos);
483 
484         return new TextLine(fFrc,
485                             components,
486                             fBaselineOffsets,
487                             fChars,
488                             startPos,
489                             limitPos,
490                             charsLtoV,
491                             charLevels,
492                             fIsDirectionLTR);
493 
494     }
495 
ensureComponents(int start, int limit)496     private void ensureComponents(int start, int limit) {
497 
498         if (start < fComponentStart || limit > fComponentLimit) {
499             generateComponents(start, limit);
500         }
501     }
502 
makeLayoutWindow(int localStart)503     private void makeLayoutWindow(int localStart) {
504 
505         int compStart = localStart;
506         int compLimit = fChars.length;
507 
508         // If we've already gone past the layout window, format to end of paragraph
509         if (layoutCount > 0 && !haveLayoutWindow) {
510             float avgLineLength = Math.max(layoutCharCount / layoutCount, 1);
511             compLimit = Math.min(localStart + (int)(avgLineLength*EST_LINES), fChars.length);
512         }
513 
514         if (localStart > 0 || compLimit < fChars.length) {
515             if (charIter == null) {
516                 charIter = new CharArrayIterator(fChars);
517             }
518             else {
519                 charIter.reset(fChars);
520             }
521             if (fLineBreak == null) {
522                 fLineBreak = BreakIterator.getLineInstance();
523             }
524             fLineBreak.setText(charIter);
525             if (localStart > 0) {
526                 if (!fLineBreak.isBoundary(localStart)) {
527                     compStart = fLineBreak.preceding(localStart);
528                 }
529             }
530             if (compLimit < fChars.length) {
531                 if (!fLineBreak.isBoundary(compLimit)) {
532                     compLimit = fLineBreak.following(compLimit);
533                 }
534             }
535         }
536 
537         ensureComponents(compStart, compLimit);
538         haveLayoutWindow = true;
539     }
540 
541     /**
542      * Returns the index of the first character which will not fit on
543      * on a line beginning at {@code start} and possible
544      * measuring up to {@code maxAdvance} in graphical width.
545      *
546      * @param start the character index at which to start measuring.
547      *  {@code start} is an absolute index, not relative to the
548      *  start of the paragraph
549      * @param maxAdvance the graphical width in which the line must fit
550      * @return the index after the last character that will fit
551      *  on a line beginning at {@code start}, which is not longer
552      *  than {@code maxAdvance} in graphical width
553      * @throws IllegalArgumentException if {@code start} is
554      *          less than the beginning of the paragraph.
555      */
getLineBreakIndex(int start, float maxAdvance)556     public int getLineBreakIndex(int start, float maxAdvance) {
557 
558         int localStart = start - fStart;
559 
560         if (!haveLayoutWindow ||
561                 localStart < fComponentStart ||
562                 localStart >= fComponentLimit) {
563             makeLayoutWindow(localStart);
564         }
565 
566         return calcLineBreak(localStart, maxAdvance) + fStart;
567     }
568 
569     /**
570      * Returns the graphical width of a line beginning at {@code start}
571      * and including characters up to {@code limit}.
572      * {@code start} and {@code limit} are absolute indices,
573      * not relative to the start of the paragraph.
574      *
575      * @param start the character index at which to start measuring
576      * @param limit the character index at which to stop measuring
577      * @return the graphical width of a line beginning at {@code start}
578      *   and including characters up to {@code limit}
579      * @throws IndexOutOfBoundsException if {@code limit} is less
580      *         than {@code start}
581      * @throws IllegalArgumentException if {@code start} or
582      *          {@code limit} is not between the beginning of
583      *          the paragraph and the end of the paragraph.
584      */
getAdvanceBetween(int start, int limit)585     public float getAdvanceBetween(int start, int limit) {
586 
587         int localStart = start - fStart;
588         int localLimit = limit - fStart;
589 
590         ensureComponents(localStart, localLimit);
591         TextLine line = makeTextLineOnRange(localStart, localLimit);
592         return line.getMetrics().advance;
593         // could cache line in case getLayout is called with same start, limit
594     }
595 
596     /**
597      * Returns a {@code TextLayout} on the given character range.
598      *
599      * @param start the index of the first character
600      * @param limit the index after the last character.  Must be greater
601      *   than {@code start}
602      * @return a {@code TextLayout} for the characters beginning at
603      *  {@code start} up to (but not including) {@code limit}
604      * @throws IndexOutOfBoundsException if {@code limit} is less
605      *         than {@code start}
606      * @throws IllegalArgumentException if {@code start} or
607      *          {@code limit} is not between the beginning of
608      *          the paragraph and the end of the paragraph.
609      */
getLayout(int start, int limit)610     public TextLayout getLayout(int start, int limit) {
611 
612         int localStart = start - fStart;
613         int localLimit = limit - fStart;
614 
615         ensureComponents(localStart, localLimit);
616         TextLine textLine = makeTextLineOnRange(localStart, localLimit);
617 
618         if (localLimit < fChars.length) {
619             layoutCharCount += limit-start;
620             layoutCount++;
621         }
622 
623         return new TextLayout(textLine,
624                               fBaseline,
625                               fBaselineOffsets,
626                               fJustifyRatio);
627     }
628 
629     private int formattedChars = 0;
630     private static boolean wantStats = false;/*"true".equals(System.getProperty("collectStats"));*/
631     private boolean collectStats = false;
632 
printStats()633     private void printStats() {
634         System.out.println("formattedChars: " + formattedChars);
635         //formattedChars = 0;
636         collectStats = false;
637     }
638 
639     /**
640      * Updates the {@code TextMeasurer} after a single character has
641      * been inserted
642      * into the paragraph currently represented by this
643      * {@code TextMeasurer}.  After this call, this
644      * {@code TextMeasurer} is equivalent to a new
645      * {@code TextMeasurer} created from the text;  however, it will
646      * usually be more efficient to update an existing
647      * {@code TextMeasurer} than to create a new one from scratch.
648      *
649      * @param newParagraph the text of the paragraph after performing
650      * the insertion.  Cannot be null.
651      * @param insertPos the position in the text where the character was
652      * inserted.  Must not be less than the start of
653      * {@code newParagraph}, and must be less than the end of
654      * {@code newParagraph}.
655      * @throws IndexOutOfBoundsException if {@code insertPos} is less
656      *         than the start of {@code newParagraph} or greater than
657      *         or equal to the end of {@code newParagraph}
658      * @throws NullPointerException if {@code newParagraph} is
659      *         {@code null}
660      */
insertChar(AttributedCharacterIterator newParagraph, int insertPos)661     public void insertChar(AttributedCharacterIterator newParagraph, int insertPos) {
662 
663         if (collectStats) {
664             printStats();
665         }
666         if (wantStats) {
667             collectStats = true;
668         }
669 
670         fStart = newParagraph.getBeginIndex();
671         int end = newParagraph.getEndIndex();
672         if (end - fStart != fChars.length+1) {
673             initAll(newParagraph);
674         }
675 
676         char[] newChars = new char[end-fStart];
677         int newCharIndex = insertPos - fStart;
678         System.arraycopy(fChars, 0, newChars, 0, newCharIndex);
679 
680         char newChar = newParagraph.setIndex(insertPos);
681         newChars[newCharIndex] = newChar;
682         System.arraycopy(fChars,
683                          newCharIndex,
684                          newChars,
685                          newCharIndex+1,
686                          end-insertPos-1);
687         fChars = newChars;
688 
689         if (fBidi != null || Bidi.requiresBidi(newChars, newCharIndex, newCharIndex + 1) ||
690                 newParagraph.getAttribute(TextAttribute.BIDI_EMBEDDING) != null) {
691 
692             fBidi = new Bidi(newParagraph);
693             if (fBidi.isLeftToRight()) {
694                 fBidi = null;
695             }
696         }
697 
698         fParagraph = StyledParagraph.insertChar(newParagraph,
699                                                 fChars,
700                                                 insertPos,
701                                                 fParagraph);
702         invalidateComponents();
703     }
704 
705     /**
706      * Updates the {@code TextMeasurer} after a single character has
707      * been deleted
708      * from the paragraph currently represented by this
709      * {@code TextMeasurer}.  After this call, this
710      * {@code TextMeasurer} is equivalent to a new {@code TextMeasurer}
711      * created from the text;  however, it will usually be more efficient
712      * to update an existing {@code TextMeasurer} than to create a new one
713      * from scratch.
714      *
715      * @param newParagraph the text of the paragraph after performing
716      * the deletion.  Cannot be null.
717      * @param deletePos the position in the text where the character was removed.
718      * Must not be less than
719      * the start of {@code newParagraph}, and must not be greater than the
720      * end of {@code newParagraph}.
721      * @throws IndexOutOfBoundsException if {@code deletePos} is
722      *         less than the start of {@code newParagraph} or greater
723      *         than the end of {@code newParagraph}
724      * @throws NullPointerException if {@code newParagraph} is
725      *         {@code null}
726      */
deleteChar(AttributedCharacterIterator newParagraph, int deletePos)727     public void deleteChar(AttributedCharacterIterator newParagraph, int deletePos) {
728 
729         fStart = newParagraph.getBeginIndex();
730         int end = newParagraph.getEndIndex();
731         if (end - fStart != fChars.length-1) {
732             initAll(newParagraph);
733         }
734 
735         char[] newChars = new char[end-fStart];
736         int changedIndex = deletePos-fStart;
737 
738         System.arraycopy(fChars, 0, newChars, 0, deletePos-fStart);
739         System.arraycopy(fChars, changedIndex+1, newChars, changedIndex, end-deletePos);
740         fChars = newChars;
741 
742         if (fBidi != null) {
743             fBidi = new Bidi(newParagraph);
744             if (fBidi.isLeftToRight()) {
745                 fBidi = null;
746             }
747         }
748 
749         fParagraph = StyledParagraph.deleteChar(newParagraph,
750                                                 fChars,
751                                                 deletePos,
752                                                 fParagraph);
753         invalidateComponents();
754     }
755 
756     /**
757      * NOTE:  This method is only for LineBreakMeasurer's use.  It is package-
758      * private because it returns internal data.
759      */
getChars()760     char[] getChars() {
761 
762         return fChars;
763     }
764 }
765