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