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