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</code> 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</code> for a range of characters. It also provides
68 * methods for incremental editing of paragraphs.
69 * <p>
70 * A <code>TextMeasurer</code> 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</code>
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</code> defines the index
79 * past the last character. These values define the range of indexes to
80 * use in calls to the <code>TextMeasurer</code>. 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</code> to use the beginning index and end
87 * index of the <code>AttributedCharacterIterator</code> passed in those calls.
88 * <p>
89 * Most clients will use the more convenient <code>LineBreakMeasurer</code>,
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 = new Float(s);
109 EST_LINES = f.floatValue();
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</code> 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) {
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</code> and possible
544 * measuring up to <code>maxAdvance</code> in graphical width.
545 *
546 * @param start the character index at which to start measuring.
547 * <code>start</code> 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</code>, which is not longer
552 * than <code>maxAdvance</code> in graphical width
553 * @throws IllegalArgumentException if <code>start</code> 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</code>
571 * and including characters up to <code>limit</code>.
572 * <code>start</code> and <code>limit</code> 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</code>
578 * and including characters up to <code>limit</code>
579 * @throws IndexOutOfBoundsException if <code>limit</code> is less
580 * than <code>start</code>
581 * @throws IllegalArgumentException if <code>start</code> or
582 * <code>limit</code> 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</code> 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</code>
602 * @return a <code>TextLayout</code> for the characters beginning at
603 * <code>start</code> up to (but not including) <code>limit</code>
604 * @throws IndexOutOfBoundsException if <code>limit</code> is less
605 * than <code>start</code>
606 * @throws IllegalArgumentException if <code>start</code> or
607 * <code>limit</code> 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</code> after a single character has
641 * been inserted
642 * into the paragraph currently represented by this
643 * <code>TextMeasurer</code>. After this call, this
644 * <code>TextMeasurer</code> is equivalent to a new
645 * <code>TextMeasurer</code> created from the text; however, it will
646 * usually be more efficient to update an existing
647 * <code>TextMeasurer</code> 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</code>, and must be less than the end of
654 * <code>newParagraph</code>.
655 * @throws IndexOutOfBoundsException if <code>insertPos</code> is less
656 * than the start of <code>newParagraph</code> or greater than
657 * or equal to the end of <code>newParagraph</code>
658 * @throws NullPointerException if <code>newParagraph</code> is
659 * <code>null</code>
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
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</code> after a single character has
707 * been deleted
708 * from the paragraph currently represented by this
709 * <code>TextMeasurer</code>. After this call, this
710 * <code>TextMeasurer</code> is equivalent to a new <code>TextMeasurer</code>
711 * created from the text; however, it will usually be more efficient
712 * to update an existing <code>TextMeasurer</code> 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</code>, and must not be greater than the
720 * end of <code>newParagraph</code>.
721 * @throws IndexOutOfBoundsException if <code>deletePos</code> is
722 * less than the start of <code>newParagraph</code> or greater
723 * than the end of <code>newParagraph</code>
724 * @throws NullPointerException if <code>newParagraph</code> is
725 * <code>null</code>
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;
|
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 = new Float(s);
109 EST_LINES = f.floatValue();
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) {
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
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;
|