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