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      */
 165     public TextMeasurer(AttributedCharacterIterator text, FontRenderContext frc) {
 166 
 167         fFrc = frc;
 168         initAll(text);
 169     }
 170 
 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 
 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      */
 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      */
 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 
 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      */
 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 
 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 
 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 
 496     private void ensureComponents(int start, int limit) {
 497 
 498         if (start < fComponentStart || limit > fComponentLimit) {
 499             generateComponents(start, limit);
 500         }
 501     }
 502 
 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      */
 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      */
 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      */
 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 
 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      */
 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      */
 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      */
 760     char[] getChars() {
 761 
 762         return fChars;
 763     }
 764 }