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
41package java.awt.font;
42
43import java.awt.Font;
44
45import java.text.AttributedCharacterIterator;
46import java.text.AttributedCharacterIterator.Attribute;
47import java.text.AttributedString;
48import java.text.Bidi;
49import java.text.BreakIterator;
50import java.text.CharacterIterator;
51
52import java.awt.font.FontRenderContext;
53
54import java.util.Hashtable;
55import java.util.Map;
56
57import sun.font.AttributeValues;
58import sun.font.BidiUtils;
59import sun.font.TextLineComponent;
60import sun.font.TextLabelFactory;
61import 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
98public 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}
765