2 * (C) Copyright IBM Corp. 1998-2007. All Rights Reserved.
\r
4 * The program is provided "as is" without any warranty express or
\r
5 * implied, including the warranty of non-infringement and the implied
\r
6 * warranties of merchantibility and fitness for a particular purpose.
\r
7 * IBM will not be liable for any damages suffered by you as a result
\r
8 * of using the Program. In no event will IBM be liable for any
\r
9 * special, indirect or consequential damages or lost profits even if
\r
10 * IBM has been advised of the possibility of their occurrence. IBM
\r
11 * will not be liable for any third party claims against you.
\r
14 // Revision: 70 1.38 richtext/AsyncFormatter.java, richtext, richtext
\r
16 package com.ibm.richtext.textformat;
\r
18 import java.awt.Graphics;
\r
19 import java.awt.Rectangle;
\r
20 import java.awt.Point;
\r
21 import java.awt.Color;
\r
23 import java.text.BreakIterator;
\r
24 import java.text.CharacterIterator;
\r
26 import java.util.Hashtable;
\r
28 import com.ibm.richtext.textlayout.attributes.AttributeMap;
\r
29 import com.ibm.richtext.textlayout.attributes.TextAttribute;
\r
31 import com.ibm.richtext.styledtext.MConstText;
\r
33 import com.ibm.richtext.textlayout.Graphics2DConversion;
\r
36 import java.awt.Graphics2D;
\r
37 import java.awt.font.LineBreakMeasurer;
\r
38 import java.awt.font.FontRenderContext;
\r
42 import com.ibm.richtext.textlayout.Graphics2D;
\r
43 import com.ibm.richtext.textlayout.LineBreakMeasurer;
\r
44 import com.ibm.richtext.textlayout.FontRenderContext;
\r
53 Fixed bug in textOffsetToPoint (fPixHeight wasn't added to negative heights). {jbr}
\r
56 Removed references to JustificationStyle constants, and moved them into MFormatter {sfb}
\r
59 Modified findLineAt and getLineContaining - added optimization in search loop. Also,
\r
60 they were failing when fLTPosEnd+1 == fLTNegStart. Fixed.
\r
63 Moved FormatDaemon stuff into this class.
\r
66 Shortened line returned from textOffsetToPoint by 1 pixel
\r
69 textOffsetToPoint line length restored (see above). drawText() now draws only lines
\r
70 which fall in rectangle param.
\r
73 whitespace at end of line is used for caret positioning
\r
76 Added static TextBox method.
\r
79 Line 1300 - less than changed to less than or equal in textOffsetToPoint. Watch for
\r
80 hangs in formatText.
\r
83 Changed sync. model. fFormatInBackground is used to start/stop bg formatting
\r
86 Added new flags: fLineInc, fFillInc for bidi support. updateFormat, pointToTextOffset, getBoundingRect,
\r
87 and findNewInsertionOffset should now function correctly for non-Roman documents. Nothing else has been
\r
88 modified to support intl text.
\r
91 Pushed paragraph formatting and caret positioning into LineLayout. In process of pushing
\r
92 highlighting into LineLayout.
\r
95 Now getting paragraph styles from paragraph buffer.
\r
98 Up-arrow doesn't move to beginning of text if you're on the first line.
\r
101 No longer intersecting damaged rect with view rect in updateFormat.
\r
105 * This class implements MFormatter. It maintains a table of
\r
106 * <tt>LayoutInfo</tt> instances which contain layout information
\r
107 * for each line in the text. This class formats lines on demand,
\r
108 * and creates a low-priority thread to format text in the background.
\r
109 * Note that, at times, some text may not have been formatted, especially
\r
110 * if the text is large.
\r
112 * The line table is an array of <tt>LayoutInfo</tt> objects, which expands as needed to hold
\r
113 * all computed lines in the text. The line table consists of three
\r
114 * regions: a "positive" region, a "gap," and a "negative" region.
\r
115 * In the positive region, character and graphics offsets are positive
\r
116 * integers, computed from the beginning of the text / display. In the
\r
117 * gap, line table entries are null. New lines may be inserted into the gap.
\r
118 * In the negative region, character and graphics offsets are negative;
\r
119 * their absolute values indicate distances from the end of the text / display.
\r
120 * The <tt>fLTPosEnd</tt> member gives the index in the line table of the
\r
121 * last positive entry. The <tt>fLTNegStart</tt> gives the index of first
\r
122 * negative entry. If there are no negative entries, <tt>fLTNegStart</tt> is
\r
123 * equal to <tt>fLTSize</tt>, the size of the line table.
\r
125 * Changes to the line table occur only in the <tt>formatText()</tt> method.
\r
126 * This method calls <tt>LineLayout.layout()</tt> for each line to format.
\r
128 * @author John Raley
\r
132 * @see LayoutContext
\r
136 final class AsyncFormatter extends MFormatter implements Runnable
\r
138 static final String COPYRIGHT =
\r
139 "(C) Copyright IBM Corp. 1998-1999 - All Rights Reserved";
\r
143 private MConstText fText;
\r
148 private AttributeMap fDefaultValues;
\r
153 private FontResolver fFontResolver;
\r
156 * Default character metric - used to set height of empty paragraphs
\r
158 private DefaultCharacterMetric fDefaultCharMetric;
\r
161 * Length to which lines are formatted.
\r
163 private int fLineDim;
\r
166 * Table of formatted lines.
\r
168 private LayoutInfo fLineTable[];
\r
171 * Size of line table.
\r
173 private int fLTSize = 10; // initial size must be > 0
\r
176 * Index of last positive entry in line table.
\r
178 private int fLTPosEnd;
\r
181 * Index of first negative entry in line table.
\r
183 private int fLTNegStart;
\r
186 * Length of text on which negative line offsets are based.
\r
189 private int fLTCurTextLen;
\r
192 * Length of formatted text in fill direction, in pixels.
\r
194 private int fPixHeight;
\r
197 * Length of formatted text including pseudoline.
\r
199 private int fFullPixHeight;
\r
205 * <tt>true</tt> if lines should be formatted to fit line dimension.
\r
207 private boolean fWrap;
\r
210 * <tt>true</tt> if characters run horizontally.
\r
212 private boolean fHLine = true;
\r
215 * <tt>true</tt> if characters run from from low to high coordinates on line.
\r
217 private boolean fLineInc = true;
\r
220 * <tt>true</tt> if lines run from low to high coordinates within page.
\r
222 private boolean fFillInc = true;
\r
225 * Value returned from <tt>findLineAt()</tt>
\r
226 * if pixel height precedes topmost line.
\r
228 private static final int kBeforeFirstLine = -2;
\r
231 * Value returned from <tt>findLineAt()</tt> and <tt>getLineContaining()</tt>
\r
232 * if offset / pixel height is after all existing lines.
\r
234 private static final int kAfterLastLine = -1;
\r
237 * Thread which invokes formatter in the background.
\r
239 private Thread fDaemon;
\r
242 * FontRenderContext to measure with. Currently not settable after
\r
245 private FontRenderContext fFontRenderContext;
\r
248 * Controls whether background formatting can run.
\r
250 private boolean fBgFormatAllowed = false;
\r
253 * Cached line break object.
\r
255 private BreakIterator fLineBreak = null;
\r
258 * Cached LineBreakMeasurer.
\r
260 private LineBreakMeasurer fCachedMeasurer = null;
\r
261 private int fCachedMeasurerStart;
\r
262 private int fCachedMeasurerLimit;
\r
264 // Some JDK's (Sun's 1.2.2) throw exceptions from
\r
265 // LineBreakMeasurer.insertChar and deleteChar. This class
\r
266 // detects this condition and doesn't use these two methods
\r
267 // if they throw exceptions.
\r
268 private static boolean fgCacheMeasurers = true;
\r
271 * Current text time stamp. Used to maintain modification invariant.
\r
273 private int fCurTimeStamp;
\r
276 * Cache of ParagraphRenderers.
\r
278 private Hashtable fRendererCache = new Hashtable();
\r
281 * Draw text inside a rectangle. Does not cache any formatting information.
\r
282 * This is convenient for small amounts of text; comparable to TETextBox on the Mac.
\r
284 * @param text the text to draw
\r
285 * @param g Graphics on which to draw
\r
286 * @param drawRect rectangle in which text will be drawn
\r
287 * @param fillInc if true, lines run from low to high coordinates in page.
\r
288 * @param hLine if true, characters run horizontally within a line.
\r
291 private static boolean isParagraphSeparator(char ch) {
\r
293 return ch == '\n' || ch == '\u2029';
\r
297 * Create an <tt>AsyncFormatter</tt>.
\r
298 * @param text the text to format
\r
299 * @param lineBound length to which lines are foramtted
\r
300 * @param wrap <tt>true</tt> if text should be "line wrapped" (formatted to fit destination area)
\r
302 AsyncFormatter(MConstText text,
\r
303 AttributeMap defaultValues,
\r
309 fDefaultValues = defaultValues;
\r
310 fFontResolver = new FontResolver(fDefaultValues);
\r
312 fLineDim = lineBound;
\r
314 Graphics2D g2d = Graphics2DConversion.getGraphics2D(g);
\r
315 fFontRenderContext = g2d.getFontRenderContext();
\r
317 fDefaultCharMetric = new DefaultCharacterMetric(fFontResolver,
\r
318 fFontRenderContext);
\r
319 fLTCurTextLen = text.length();
\r
322 fDaemon = new Thread(this);
\r
326 public AttributeMap getDefaultValues() {
\r
328 return fDefaultValues;
\r
331 public void checkTimeStamp() {
\r
332 String admonition = "Probably, you modified " +
\r
333 "the text before calling stopBackgroundFormatting().";
\r
335 if (fText.getTimeStamp() != fCurTimeStamp) {
\r
336 throw new Error("Time stamp is out of sync. " + admonition);
\r
338 if (fText.length() != fLTCurTextLen) {
\r
339 throw new Error("Length changed unexpectedly. " +
\r
340 "fText.length()="+fText.length()+"; "+
\r
341 "fLTCurTextLen="+fLTCurTextLen+"; "+
\r
342 "formatter="+this+"; "+
\r
348 * Specify whether to wrap lines using the line dimension.
\r
349 * @param wrap if <tt>true</tt> lines will be wrapped; otherwise new lines will only be
\r
350 * started when a newline is encountered.
\r
352 public synchronized void setWrap(boolean wrap)
\r
354 if (wrap != fWrap) {
\r
361 * Return true if lines are wrapped using the line dimension.
\r
364 public synchronized boolean wrap()
\r
370 * Specify the lineBound in pixels. If line wrapping is on, lines
\r
371 * will be wrapped to this value.
\r
374 * @param lineBound the distance, in pixels, used to wrap lines.
\r
376 public synchronized void setLineBound(int lineBound)
\r
378 if (fLineDim != lineBound) {
\r
379 fLineDim = lineBound;
\r
387 * Return the number of pixels along the line dimension.
\r
389 public synchronized int lineBound()
\r
395 // * Return <tt>true</tt> if characters run from low to high coordinates on line.
\r
397 // private boolean lineInc()
\r
399 // return fLineInc;
\r
403 // * Return <tt>true</tt> if lines run from low to high coordinates on page.
\r
405 // private boolean fillInc()
\r
407 // return fFillInc;
\r
411 * Remove all lines in the line table. Used after an operation that
\r
412 * invalidates all existing lines, such as changing line wrapping or the
\r
415 private synchronized void removeAllLines()
\r
417 fCurTimeStamp = fText.getTimeStamp();
\r
418 stopBackgroundFormatting();
\r
423 fLineTable = new LayoutInfo[fLTSize]; // fLTSize must be > 0
\r
424 fLTNegStart = fLTSize;
\r
427 fLineTable[0] = pseudoLineInfo(null, 0);
\r
429 fPixHeight = fLineTable[0].getHeight(); // ??? or should it be zero?
\r
430 fFullPixHeight = fPixHeight;
\r
432 // format at least one line:
\r
433 formatToHeight(fPixHeight + 1);
\r
439 * Fill the layout info with information appropriate to the pseudoline.
\r
441 private synchronized LayoutInfo pseudoLineInfo(LayoutInfo info, int offset)
\r
443 AttributeMap st = fText.paragraphStyleAt(fLTCurTextLen); // ??? if text is empty or this is the end of the text, what happens?
\r
444 ParagraphRenderer renderer = getRendererFor(st);
\r
445 info = renderer.layout(fText,
\r
447 (LineBreakMeasurer)null,
\r
448 fFontRenderContext,
\r
458 * Return the index of the last valid line in the line table.
\r
460 private int lastLine()
\r
462 return (fLTNegStart == fLTSize) ? fLTPosEnd : fLTSize - 1;
\r
466 * Shift line table such that <tt>lastPos</tt> is the last positive
\r
467 * entry in the table. <b>NOTE: <tt>lastPos</tt> must be a valid line!</b>
\r
469 * @param lastPos the index of the line which will become the last positive
\r
470 * entry in the line table
\r
472 private void shiftTableTo(int lastPos)
\r
476 while (lastPos < fLTPosEnd) { // shift +'s to -'s
\r
477 li = fLineTable[fLTPosEnd];
\r
478 fLineTable[fLTPosEnd--] = null;
\r
480 li.makeRelativeToEnd(fLTCurTextLen, fPixHeight);
\r
482 fLineTable[--fLTNegStart] = li;
\r
485 while (lastPos >= fLTNegStart) { // shift -'s to +'s
\r
486 li = fLineTable[fLTNegStart];
\r
487 fLineTable[fLTNegStart++] = null;
\r
489 li.makeRelativeToBeginning(fLTCurTextLen, fPixHeight);
\r
491 fLineTable[++fLTPosEnd] = li;
\r
496 * Increase the size of the line table.
\r
498 private void expandLineTable()
\r
500 // This just doubles the size of the line table.
\r
502 LayoutInfo newLineTable[] = new LayoutInfo[fLineTable.length * 2];
\r
503 int newNegStart = newLineTable.length - (fLineTable.length - fLTNegStart);
\r
505 System.arraycopy(fLineTable, 0, newLineTable, 0, fLTPosEnd + 1);
\r
506 System.arraycopy(fLineTable, fLTNegStart, newLineTable, newNegStart, fLTSize - fLTNegStart);
\r
508 fLTNegStart = newNegStart;
\r
509 fLTSize = newLineTable.length;
\r
510 fLineTable = newLineTable;
\r
514 * Return the index of the line containing the pixel position <tt>fillCoord</tt>.
\r
515 * If fillCoord exceeds the bottom of the text, return kAfterLastLine.
\r
516 * If fillCoord is less than the top of the text, return kBeforeFirstLine.
\r
518 * @param fillCoord "height" of line to locate.
\r
520 private int findLineAt(int fillCoord)
\r
522 int low, high, mid;
\r
523 int lowStart, highStart, midStart;
\r
525 if (fillCoord >= fPixHeight)
\r
526 return kAfterLastLine;
\r
527 else if (fillCoord < 0)
\r
528 return kBeforeFirstLine;
\r
530 if ((fLTNegStart < fLTSize) && (fillCoord >= fLineTable[fLTNegStart].getGraphicStart(fPixHeight))) {
\r
531 fillCoord -= fPixHeight;
\r
539 high = fLTPosEnd + 1;
\r
540 highStart = fLineTable[fLTPosEnd].getGraphicStart(0) + fLineTable[fLTPosEnd].getHeight();
\r
542 lowStart = fLineTable[low].getGraphicStart(0);
\r
545 if (lowStart == highStart)
\r
548 mid = low + (fillCoord - lowStart) / (highStart - lowStart) * (high - low);
\r
549 midStart = fLineTable[mid].getGraphicStart(0);
\r
551 if (midStart > fillCoord) {
\r
553 highStart = fLineTable[high].getGraphicStart(0);
\r
555 else if (midStart + fLineTable[mid].getHeight() <= fillCoord) {
\r
557 lowStart = fLineTable[low].getGraphicStart(0);
\r
562 } while (low < high);
\r
568 * Return the index of the first character in the line.
\r
569 * @param line the internal index of the line (direct index into linetable).
\r
571 private int lineCharStartInternal(int line)
\r
573 return fLineTable[line].getCharStart(fLTCurTextLen);
\r
577 * Return the index of the character following the last character in the line.
\r
578 * @param line the internal index of the line (direct index into linetable).
\r
580 private int lineCharLimitInternal(int line)
\r
582 return lineCharStartInternal(line) + fLineTable[line].getCharLength();
\r
586 * Return the graphic start of the line, unadjusted for fill direction.
\r
587 * @param line the internal index of the line (direct index into linetable).
\r
589 private int lineGraphicStartInternal(int line)
\r
591 return fLineTable[line].getGraphicStart(fPixHeight);
\r
595 * Return the graphic limit of the line, unadjusted for fill direction.
\r
596 * @param line the internal index of the line (direct index into linetable).
\r
598 private int lineGraphicLimitInternal(int line)
\r
600 return lineGraphicStartInternal(line) + fLineTable[line].getHeight();
\r
604 * Return the offset of the first character which has not been formatted.
\r
605 * If all text has been formatted, return the current text length.
\r
607 private int lastLineCharStop()
\r
609 return lineCharLimitInternal(lastLine());
\r
613 * Return a 'valid' line containing offset. This differs from getLineContaining in
\r
614 * this maps kAfterLastLine to lastLine(), so that the result is always a valid
\r
617 private int getValidLineContaining(TextOffset offset) {
\r
619 return getValidLineContaining(offset.fOffset, offset.fPlacement);
\r
623 * Return a 'valid' line containing offset. This differs from getLineContaining in
\r
624 * this maps kAfterLastLine to lastLine(), so that the result is always a valid
\r
627 private int getValidLineContaining(int insOffset, boolean placement)
\r
629 int line = getLineContaining(insOffset, placement);
\r
630 if (line == kAfterLastLine)
\r
632 else if (line == kBeforeFirstLine)
\r
633 throw new IllegalArgumentException("Debug: getLineContaining returned kBeforeFirstLine");
\r
639 * Return index of line containing <tt>offset</tt>.
\r
640 * ??? If offset is after last formatted line, returns kAfterLastLine. Is that good?
\r
642 * @param offset the offset whose line should be located
\r
643 * @returns line containing <tt>offset</tt>
\r
645 private int getLineContaining(TextOffset offset) {
\r
647 return getLineContaining(offset.fOffset, offset.fPlacement);
\r
650 private int getLineContaining(int insOffset, boolean placement)
\r
652 int pos = insOffset;
\r
653 if (placement == TextOffset.BEFORE_OFFSET && pos > 0)
\r
657 throw new IllegalArgumentException("Debug: getLineContaining offset < 0: " + pos);
\r
660 if (pos >= lastLineCharStop()) {
\r
661 return emptyParagraphAtEndOfText()? kAfterLastLine : lastLine();
\r
664 int low, high, mid;
\r
665 int lowStart, highStart, midStart;
\r
667 if ((fLTNegStart < fLTSize) && (pos >= fLineTable[fLTNegStart].getCharStart(fLTCurTextLen))) {
\r
668 pos -= fLTCurTextLen;
\r
676 high = fLTPosEnd + 1;
\r
677 highStart = fLineTable[fLTPosEnd].getCharStart(0) + fLineTable[fLTPosEnd].getCharLength();
\r
679 lowStart = fLineTable[low].getCharStart(0);
\r
682 if (highStart == lowStart) {
\r
686 mid = low + (pos - lowStart) / (highStart - lowStart) * (high - low);
\r
687 midStart = fLineTable[mid].getCharStart(0);
\r
689 if (midStart > pos) {
\r
691 highStart = fLineTable[high].getCharStart(0);
\r
693 else if (midStart + fLineTable[mid].getCharLength() <= pos) {
\r
695 lowStart = fLineTable[low].getCharStart(0);
\r
701 } while (low < high);
\r
707 * Display text in drawArea. Does not reformat text.
\r
709 * @param g the Graphics object in which to draw
\r
710 * @param drawArea the rectangle, in g's coordinate system, in which to draw
\r
711 * @param origin the top-left corner of the text, in g's coordinate system
\r
713 public synchronized void draw(Graphics g, Rectangle drawArea, Point origin)
\r
715 draw(g, drawArea, origin, null, null, null);
\r
718 public synchronized void draw(Graphics g, Rectangle drawArea, Point origin,
\r
719 TextOffset selStart, TextOffset selStop, Color highlight) {
\r
722 Graphics2D g2d = Graphics2DConversion.getGraphics2D(g);
\r
724 // Get starting and ending fill 'heights.'
\r
730 startFill = drawArea.y - origin.y;
\r
732 startFill = origin.y - (drawArea.y + drawArea.height);
\r
734 endFill = startFill + drawArea.height;
\r
736 // We're drawing one more line than necessary when we update because of a
\r
737 // selection change. But we're drawing the right amount of lines when we
\r
738 // refresh the whole display. This affects rendering speed significantly,
\r
739 // and creating a new paragraph renderer for each line doesn't help either.
\r
740 // For now, I'm going to subtract one from the fill height, on the theory
\r
741 // that we're picking up the extra line because of a one-pixel slop.
\r
742 // This seems to work, although perhaps if one pixel of a line at the
\r
743 // bottom should draw, it won't.
\r
747 // Format to ending fill height, so line table is valid for all lines we need to draw.
\r
749 formatToHeight(endFill);
\r
751 // Get starting and ending lines for fill height. If the start of the fill is after the last line,
\r
752 // or the end of the fill is before the first line, return.
\r
754 int curLine = findLineAt(startFill);
\r
755 if (curLine == kAfterLastLine)
\r
757 else if (curLine == kBeforeFirstLine)
\r
760 int lastLine = findLineAt(endFill);
\r
761 if (lastLine == kBeforeFirstLine)
\r
763 else if (lastLine == kAfterLastLine)
\r
764 lastLine = lastLine();
\r
766 // Get the base coordinates (lineX, lineY) for the starting line.
\r
770 int gStart = lineGraphicStartInternal(curLine);
\r
776 lineX = origin.x - fLineDim;
\r
778 lineY = origin.y + gStart;
\r
780 lineY = origin.y - (gStart + fLineTable[curLine].getHeight());
\r
786 lineY = origin.y - fLineDim;
\r
788 lineX = origin.x + gStart;
\r
790 lineX = origin.x - (gStart + fLineTable[curLine].getHeight());
\r
793 // Iterate through lines, drawing each one and incrementing the base coordinate by the line height.
\r
796 for (; curLine <= lastLine; curLine++) {
\r
797 // Adjust curLine around gap in line table.
\r
798 if ((curLine > fLTPosEnd) && (curLine < fLTNegStart))
\r
799 curLine = fLTNegStart;
\r
801 fLineTable[curLine].renderWithHighlight(fLTCurTextLen, g2d, fLineDim, lineX, lineY, selStart, selStop, highlight);
\r
803 // Increment line base for next iteration.
\r
804 int lineInc = fLineTable[curLine].getHeight();
\r
820 * Format text to given height.
\r
821 * @param height the height to which text will be formatted.
\r
823 public synchronized void formatToHeight(int reqHeight)
\r
826 if (reqHeight <= fPixHeight) // already formatted to this height
\r
829 if (fText.length() == lastLineCharStop()) // already formatted all the text
\r
832 // +++ should disable update thread here
\r
834 if (fLTNegStart < fLTSize)
\r
835 shiftTableTo(fLTSize - 1);
\r
837 formatText(0, 0, reqHeight, false);
\r
841 * Format text to given offset.
\r
842 * @param offset the offset to which text will be formatted.
\r
844 private void formatToOffset(TextOffset offset)
\r
846 formatToOffset(offset.fOffset, offset.fPlacement);
\r
849 private synchronized void formatToOffset(int offset, boolean placement) {
\r
852 int llcs = lastLineCharStop();
\r
853 if (llcs < fLTCurTextLen) {
\r
854 int limit = offset;
\r
855 if (placement == TextOffset.AFTER_OFFSET) // format to past character offset is associated with
\r
857 if (limit >= llcs) { // ??? would '>' be ok instead or '>='?
\r
858 if (limit > fLTCurTextLen)
\r
859 limit = fLTCurTextLen;
\r
861 shiftTableTo(lastLine());
\r
862 formatText(llcs, limit - llcs, Integer.MAX_VALUE, true);
\r
868 * Reformat text after a change.
\r
869 * After the formatter's text changes, call this method to reformat. Does
\r
871 * @param afStart the offset into the text where modification began; ie, the
\r
872 * first character in the text which is "different" in some way. Does not
\r
873 * have to be nonnegative.
\r
874 * @param afLength the number of new or changed characters in the text. Should never
\r
876 * @param viewRect the Rectangle in which the text will be displayed. This is needed for
\r
877 * returning the "damaged" area - the area of the screen in which the text must be redrawn.
\r
878 * @param origin the top-left corner of the text, in the display's coordinate system
\r
879 * @returns a <tt>Rectangle</tt> which specifies the area in which text must be
\r
880 * redrawn to reflect the change to the text.
\r
882 public Rectangle updateFormat(final int afStart,
\r
883 final int afLength,
\r
884 Rectangle viewRect,
\r
888 throw new IllegalArgumentException("Debug: updateFormat afStart < 0: " + afStart);
\r
890 if (fBgFormatAllowed) {
\r
891 throw new IllegalArgumentException("Background formatting should have been disabled");
\r
893 fCurTimeStamp = fText.getTimeStamp();
\r
895 int curLine = getValidLineContaining(afStart, TextOffset.AFTER_OFFSET);
\r
896 int lineStartPos = lineCharStartInternal(curLine);
\r
898 // optimize by finding out whether change occurred
\r
899 // after first word break on curline
\r
901 int firstPossibleBreak;
\r
903 if (lineStartPos < fText.length()) {
\r
905 if (fLineBreak == null) {
\r
906 fLineBreak = BreakIterator.getLineInstance();
\r
908 CharacterIterator charIter = fText.createCharacterIterator();
\r
909 charIter.setIndex(lineStartPos);
\r
910 fLineBreak.setText(charIter);
\r
912 firstPossibleBreak = fLineBreak.following(lineStartPos);
\r
915 firstPossibleBreak = afStart;
\r
917 if ((curLine > 0) && (firstPossibleBreak == BreakIterator.DONE || afStart <= firstPossibleBreak)) {
\r
919 if (curLine < fLTNegStart && curLine > fLTPosEnd)
\r
920 curLine = fLTPosEnd;
\r
923 shiftTableTo(curLine);
\r
925 int pixHeight; // after the formatText call, at least pixHeight text must be formatted
\r
929 pixHeight = viewRect.y + viewRect.height - origin.y;
\r
931 pixHeight = origin.y - viewRect.y;
\r
935 pixHeight = viewRect.x + viewRect.width - origin.x;
\r
937 pixHeight = origin.x - viewRect.x;
\r
940 Rectangle r = formatText(afStart, afLength, pixHeight, false);
\r
944 if ((fPixHeight < pixHeight) && (fLTNegStart < fLTSize) && (fLTCurTextLen > lastLineCharStop())) {
\r
945 shiftTableTo(lastLine());
\r
946 Rectangle s = formatText(0, 0, pixHeight, false);
\r
950 intlRect(origin, r);
\r
951 //System.out.println("Damaged rect: "+r+"; origin: "+origin);
\r
953 // don't need to synchronized here, b/c the daemon shouldn't be running when
\r
954 // this is executing
\r
956 if (fText.length() < lastLineCharStop())
\r
959 stopBackgroundFormatting();
\r
966 private LineBreakMeasurer makeMeasurer(int paragraphStart, int paragraphLimit) {
\r
968 MTextIterator iter = new MTextIterator(fText,
\r
972 LineBreakMeasurer measurer = new LineBreakMeasurer(iter, fFontRenderContext);
\r
973 if (fgCacheMeasurers) {
\r
974 fCachedMeasurerStart = paragraphStart;
\r
975 fCachedMeasurerLimit = paragraphLimit;
\r
976 fCachedMeasurer = measurer;
\r
982 * Compute text format. This method calculates text format; it can be
\r
983 * called for various purposes: to reformat text after an edit, to
\r
984 * format text to a particular height, or to format text up to a
\r
985 * particular offset.
\r
987 * The calling method must ensure that <tt>fLineTable</tt> has been shifted
\r
988 * such that the last positive line is where the formatting operation will
\r
991 * Called by: <tt>formatToHeight()</tt>, <tt>updateFormat()</tt>,
\r
992 * <tt>textOffsetToPoint()</tt>, <tt>getBoundingRect()</tt>
\r
993 * @param afStart the offset of the first character in the text which has changed
\r
994 * @param afLength the number of new or changed characters in the text
\r
995 * @param reqHeight the pixel height to which text must be formatted. Ignored
\r
996 * if <tt>formatAllNewText</tt> is <tt>true</tt>, or if old lines remain in the
\r
997 * line table after all changed text has been formatted.
\r
998 * @param seekOffsetAtEnd if <tt>true</tt>, formatting continues until the line
\r
999 * containing afStart+afLength has been formatted. If false, formatting may stop
\r
1000 * when reqHeight has been reached. This parameter should be <tt>true</tt> <b>only</b>
\r
1001 * if the object of the formatting operation is to extend formatting to a particular
\r
1002 * offset within the text; it should be <tt>false</tt> everywhere else.
\r
1003 * @returns a rectangle, relative to the top-left of the text, which encloses the
\r
1004 * screen area whose appearance has changed due to the reformatting.
\r
1007 private Rectangle formatText(int afStart, final int afLength, int reqHeight, boolean seekOffsetAtEnd)
\r
1009 /* assumes line table shifted such that first line to format is
\r
1010 last positive line */
\r
1012 if (afLength < 0) {
\r
1013 throw new IllegalArgumentException("afLength < 0. afLength=" + afLength);
\r
1016 int newTextEnd = afStart + afLength;
\r
1018 final int newCurTextLen = fText.length();
\r
1020 // variable not used int oldPixHeight = fPixHeight;
\r
1021 int oldFullPixHeight = fFullPixHeight;
\r
1022 fPixHeight -= fLineTable[fLTPosEnd].getHeight();
\r
1024 int curGraphicStart = fLineTable[fLTPosEnd].getGraphicStart(fPixHeight);
\r
1025 int curLineStart = fLineTable[fLTPosEnd].getCharStart(newCurTextLen);
\r
1027 int curParagraphStart = fText.paragraphStart(curLineStart);
\r
1028 int curParagraphLimit = Integer.MIN_VALUE; // dummy value
\r
1030 int damageStart = curGraphicStart;
\r
1032 ParagraphRenderer renderer = null;
\r
1033 LineBreakMeasurer measurer = null;
\r
1035 // try to use cached LineBreakMeasurer if possible
\r
1036 if (fCachedMeasurer != null &&
\r
1037 curParagraphStart == fCachedMeasurerStart) {
\r
1039 curParagraphLimit = fText.paragraphLimit(curParagraphStart);
\r
1042 if (newCurTextLen - fLTCurTextLen == 1 && afLength == 1) {
\r
1043 if (curParagraphLimit == fCachedMeasurerLimit+1) {
\r
1044 MTextIterator iter = new MTextIterator(fText,
\r
1046 curParagraphStart,
\r
1047 curParagraphLimit);
\r
1048 fCachedMeasurer.insertChar(iter, afStart);
\r
1049 fCachedMeasurerLimit += 1;
\r
1050 measurer = fCachedMeasurer;
\r
1053 else if (fLTCurTextLen - newCurTextLen == 1 && afLength == 0) {
\r
1054 if (fCachedMeasurerLimit > fCachedMeasurerStart + 1 &&
\r
1055 curParagraphLimit == fCachedMeasurerLimit-1) {
\r
1056 MTextIterator iter = new MTextIterator(fText,
\r
1058 curParagraphStart,
\r
1059 curParagraphLimit);
\r
1060 fCachedMeasurer.deleteChar(iter, afStart);
\r
1061 fCachedMeasurerLimit -= 1;
\r
1062 measurer = fCachedMeasurer;
\r
1066 catch(ArrayIndexOutOfBoundsException e) {
\r
1067 fCachedMeasurer = null;
\r
1068 fgCacheMeasurers = false;
\r
1071 if (measurer != null) {
\r
1072 // need to set up renderer since the paragraph update in the
\r
1073 // formatting loop will not happen
\r
1074 AttributeMap style = fText.paragraphStyleAt(curParagraphStart);
\r
1075 renderer = getRendererFor(style);
\r
1076 measurer.setPosition(curLineStart);
\r
1080 if (measurer == null) {
\r
1081 // trigger paragraph update at start of formatting loop
\r
1082 curParagraphLimit = curParagraphStart;
\r
1083 curParagraphStart = 0;
\r
1086 fLTCurTextLen = newCurTextLen;
\r
1089 // System.out.println("line: " + fLTPosEnd + ", cls: " + curLineStart);
\r
1092 if (curLineStart >= curParagraphLimit) {
\r
1093 curParagraphStart = curParagraphLimit;
\r
1094 curParagraphLimit = fText.paragraphLimit(curParagraphStart);
\r
1096 AttributeMap style = fText.paragraphStyleAt(curParagraphStart);
\r
1097 renderer = getRendererFor(style);
\r
1099 if (curParagraphStart < curParagraphLimit) {
\r
1100 measurer = makeMeasurer(curParagraphStart, curParagraphLimit);
\r
1101 measurer.setPosition(curLineStart);
\r
1109 boolean haveOldDirection = fLineTable[fLTPosEnd] != null;
\r
1110 boolean oldDirection = false; // dummy value for compiler
\r
1111 if (haveOldDirection) {
\r
1112 oldDirection = fLineTable[fLTPosEnd].isLeftToRight();
\r
1115 fLineTable[fLTPosEnd] = renderer.layout(fText,
\r
1116 fLineTable[fLTPosEnd],
\r
1118 fFontRenderContext,
\r
1119 curParagraphStart,
\r
1120 curParagraphLimit,
\r
1121 fWrap ? fLineDim : Integer.MAX_VALUE,
\r
1123 if (haveOldDirection) {
\r
1124 if (fLineTable[fLTPosEnd].isLeftToRight() != oldDirection) {
\r
1125 newTextEnd = Math.max(newTextEnd, curParagraphLimit);
\r
1131 LayoutInfo theLine = fLineTable[fLTPosEnd];
\r
1133 theLine.setGraphicStart(curGraphicStart);
\r
1134 curGraphicStart += theLine.getHeight();
\r
1136 fPixHeight += theLine.getHeight();
\r
1137 curLineStart += theLine.getCharLength();
\r
1140 int lineWidth = theLine.getTotalAdvance() + theLine.getLeadingMargin();
\r
1141 if (theLine.isLeftToRight()) {
\r
1142 if (fMaxX < lineWidth) {
\r
1143 fMaxX = lineWidth;
\r
1147 if (fLineDim-lineWidth < fMinX) {
\r
1148 fMinX = fLineDim-lineWidth;
\r
1154 Next, discard obsolete lines. A line is obsolete if it
\r
1155 contains new text or text which has been formatted.
\r
1158 while (fLTNegStart < fLTSize) {
\r
1159 int linePos = fLineTable[fLTNegStart].getCharStart(newCurTextLen);
\r
1160 if (linePos >= curLineStart && linePos >= newTextEnd)
\r
1163 // System.out.println("delete neg line: " + fLTNegStart);
\r
1164 fPixHeight -= fLineTable[fLTNegStart].getHeight();
\r
1165 fLineTable[fLTNegStart++] = null;
\r
1169 if (fLTNegStart < fLTSize)
\r
1170 stopAt = fLineTable[fLTNegStart].getCharStart(newCurTextLen);
\r
1172 stopAt = newCurTextLen;
\r
1175 Now, if exit conditions aren't met, create a new line.
\r
1178 if (seekOffsetAtEnd) {
\r
1179 if ((curLineStart >= newTextEnd) && (fLTNegStart == fLTSize)) {
\r
1180 // System.out.println("break 1");
\r
1185 if (curLineStart >= stopAt) {
\r
1186 // System.out.println("curLineStart: " + curLineStart + " >= stopAt: " + stopAt);
\r
1189 else if (fLTNegStart==fLTSize && fPixHeight >= reqHeight) {
\r
1190 // System.out.println("break 3");
\r
1195 if (fLTPosEnd + 1 == fLTNegStart)
\r
1196 expandLineTable();
\r
1198 fLineTable[++fLTPosEnd] = null; // will be created by Renderer
\r
1200 //System.out.print("\n");
\r
1202 if (newCurTextLen == 0) {
\r
1203 fLineTable[0] = pseudoLineInfo(fLineTable[0], 0);
\r
1204 fPixHeight = fLineTable[0].getHeight();
\r
1206 fFullPixHeight = fPixHeight;
\r
1208 if (isParaBreakBefore(newCurTextLen)) {
\r
1209 fFullPixHeight += lastCharHeight();
\r
1212 System.out.println("curLineStart: " + curLineStart +
\r
1213 ", fLTPosEnd: " + fLTPosEnd +
\r
1214 ", fLTNegStart: " + fLTNegStart +
\r
1215 ", fLTSize: " + fLTSize);
\r
1217 System.out.println("oldFullPixHeight: " + oldFullPixHeight + ", newFullPixHeight: " + fFullPixHeight);
\r
1220 if (fFullPixHeight == oldFullPixHeight) {
\r
1221 damageLength = fLineTable[fLTPosEnd].getGraphicStart(fPixHeight)
\r
1222 + fLineTable[fLTPosEnd].getHeight() - damageStart;
\r
1225 damageLength = Math.max(fFullPixHeight, oldFullPixHeight);
\r
1228 return new Rectangle(fMinX, damageStart, fMaxX-fMinX, damageLength);
\r
1231 // private void dumpLineTable()
\r
1235 // System.out.println("fLTCurTextLen=" + fLTCurTextLen + " " );
\r
1236 // for (i=0; i<= fLTPosEnd; i++)
\r
1237 // System.out.println("Line " + i + " starts at "
\r
1238 // + fLineTable[i].getCharStart(fLTCurTextLen)
\r
1239 // + " and extends " + fLineTable[i].getCharLength());
\r
1241 // for (i=fLTNegStart; i< fLTSize; i++)
\r
1242 // System.out.println("Line " + (i-fLTNegStart+fLTPosEnd+1) + " starts at "
\r
1243 // + fLineTable[i].getCharStart(fLTCurTextLen)
\r
1244 // + " and extends " + fLineTable[i].getCharLength());
\r
1247 public synchronized int minX() {
\r
1253 * Return the horizontal extent of the text, in pixels.
\r
1255 * This returns an approximation based on the currently formatted text.
\r
1257 public synchronized int maxX()
\r
1265 * Return the height of the last character in the text.
\r
1267 * This is used for the 'extra height' needed to display a caret at the end of the text when the
\r
1268 * text is empty or ends with a newline.
\r
1270 private int lastCharHeight()
\r
1272 int charIndex = lastLineCharStop() - 1;
\r
1273 AttributeMap st = fText.characterStyleAt(charIndex);
\r
1274 DefaultCharacterMetric.Metric metric = fDefaultCharMetric.getMetricForStyle(st);
\r
1276 int height = metric.getAscent();
\r
1277 height += metric.getDescent();
\r
1278 height += metric.getLeading();
\r
1284 * Return true if the character at pos is a paragraph separator.
\r
1286 private boolean isParaBreakBefore(int pos)
\r
1288 return pos > 0 && (fText.at(pos - 1) == '\u2029' || fText.at(pos - 1) == '\n');
\r
1289 // we really need to take look at this and determine what this function
\r
1290 // should be doing. What I've got here right now is a temporary implementation.
\r
1293 public synchronized int minY() {
\r
1299 * Return the vertical extent of the text, in pixels.
\r
1301 * This returns an approximation based on the currently formatted text.
\r
1303 public synchronized int maxY()
\r
1307 int numChars = lastLineCharStop();
\r
1309 int pixHeight = fPixHeight;
\r
1310 if (numChars == fLTCurTextLen && isParaBreakBefore(fLTCurTextLen)) {
\r
1311 pixHeight += lastCharHeight();
\r
1314 if (numChars != 0)
\r
1315 return pixHeight * fText.length() / numChars;
\r
1321 * Return the actual pixel length of the text which has been formatted.
\r
1323 public synchronized int formattedHeight()
\r
1326 return fPixHeight;
\r
1330 * There are two modes for dealing with carriage returns at the end of a line. In the 'infinite width'
\r
1331 * mode, the last character is considered to have infinite width. Thus if the point is past the 'real'
\r
1332 * end of the line, the offset is the position before that last character, and the offset is associated
\r
1333 * with that character (placement after). In the 'actual width' mode, the offset is positioned after
\r
1334 * that character, but still associated with it (placement before).
\r
1337 private TextOffset lineDimToOffset(TextOffset result, int line, int lineX, int lineY, TextOffset anchor, boolean infiniteMode)
\r
1339 // temporarily adjust line info to remove the negative char starts used in the line table.
\r
1340 // then call through to the paragraph renderer to get the offset. Don't put line end
\r
1341 // optimization here, let the renderer do it (perhaps it does fancy stuff with the margins).
\r
1343 LayoutInfo lineInfo = fLineTable[line];
\r
1345 result = lineInfo.pixelToOffset(fLTCurTextLen, result, fLineDim, lineX, lineY);
\r
1347 if (infiniteMode &&
\r
1348 (result.fOffset > lineInfo.getCharStart(fLTCurTextLen)) &&
\r
1349 isParaBreakBefore(result.fOffset) &&
\r
1350 (anchor == null || anchor.fOffset == result.fOffset - 1)) {
\r
1352 result.setOffset(result.fOffset - 1, TextOffset.AFTER_OFFSET);
\r
1359 * Given a screen location p, return the offset of the character in the text nearest to p.
\r
1361 public synchronized TextOffset pointToTextOffset(TextOffset result, int px, int py, Point origin, TextOffset anchor, boolean infiniteMode)
\r
1364 if (result == null)
\r
1365 result = new TextOffset();
\r
1370 fillD = py - origin.y;
\r
1372 fillD = px - origin.x;
\r
1378 result.setOffset(0, TextOffset.AFTER_OFFSET);
\r
1382 formatToHeight(fillD);
\r
1384 if (fillD >= fPixHeight) {
\r
1385 boolean bias = fLTCurTextLen==0? TextOffset.AFTER_OFFSET : TextOffset.BEFORE_OFFSET;
\r
1386 result.setOffset(fLTCurTextLen, bias);
\r
1390 int line = findLineAt(fillD); // always a valid line
\r
1391 int gStart = lineGraphicStartInternal(line);
\r
1393 int lineX, lineY; // upper-left corner of line
\r
1396 lineY = fFillInc? origin.y + gStart : origin.y - (gStart + fLineTable[line].getHeight());
\r
1400 lineX = fFillInc? origin.x + gStart : origin.x - (gStart + fLineTable[line].getHeight());
\r
1403 return lineDimToOffset(result, line, px - lineX, py - lineY, anchor, infiniteMode);
\r
1406 private boolean emptyParagraphAtEndOfText() {
\r
1408 return fLTCurTextLen > 0 &&
\r
1409 isParagraphSeparator(fText.at(fLTCurTextLen-1));
\r
1413 * Return true if the offset designates a point on the pseudoline following a paragraph
\r
1414 * separator at the end of text. This is true if the offset is the end of text
\r
1415 * and the last character in the text is a paragraph separator.
\r
1417 private boolean afterLastParagraph(TextOffset offset)
\r
1419 return offset.fOffset == fLTCurTextLen &&
\r
1420 emptyParagraphAtEndOfText();
\r
1424 * Given an offset, return the Rectangle bounding the caret at the offset.
\r
1425 * @param offset an offset into the text
\r
1426 * @param origin the top-left corner of the text, in the display's coordinate system
\r
1427 * @return a Rectangle bounding the caret.
\r
1429 public synchronized Rectangle getCaretRect(TextOffset offset, Point origin) {
\r
1431 Rectangle r = new Rectangle();
\r
1432 getCaretRect(r, offset, origin);
\r
1436 private void getCaretRect(Rectangle r, TextOffset offset, Point origin) {
\r
1439 formatToOffset(offset);
\r
1441 if (afterLastParagraph(offset)) {
\r
1442 int pseudoLineHeight = lastCharHeight();
\r
1444 int lineY = fFillInc ? origin.y + fPixHeight : origin.y - fPixHeight - pseudoLineHeight;
\r
1445 r.setBounds(origin.x, lineY, 0, pseudoLineHeight);
\r
1448 int lineX = fFillInc? origin.x + fPixHeight : origin.x - fPixHeight - pseudoLineHeight;
\r
1449 r.setBounds(lineX, origin.y, pseudoLineHeight, 0);
\r
1454 int line = getValidLineContaining(offset);
\r
1456 int gStart = lineGraphicStartInternal(line);
\r
1463 lineY = origin.y + gStart;
\r
1465 lineY = origin.y - (gStart + fLineTable[line].getHeight());
\r
1470 lineX = origin.x + gStart;
\r
1472 lineX = origin.x - (gStart + fLineTable[line].getHeight());
\r
1475 Rectangle bounds = fLineTable[line].caretBounds(fText, fLTCurTextLen, fLineDim, offset.fOffset, lineX, lineY);
\r
1477 r.setBounds(bounds);
\r
1481 * Draw the caret(s) associated with the given offset into the given Graphics.
\r
1482 * @param g the Graphics to draw into
\r
1483 * @param offset the offset in the text for which the caret is drawn
\r
1484 * @param origin the top-left corner of the text, in the display's coordinate system
\r
1485 * @param strongCaretColor the color of the strong caret
\r
1486 * @param weakCaretColor the color of the weak caret (if any)
\r
1488 public synchronized void drawCaret(Graphics g,
\r
1489 TextOffset offset,
\r
1491 Color strongCaretColor,
\r
1492 Color weakCaretColor) {
\r
1495 Graphics2D g2d = Graphics2DConversion.getGraphics2D(g);
\r
1496 formatToOffset(offset);
\r
1501 if (afterLastParagraph(offset)) {
\r
1502 gStart = fPixHeight;
\r
1503 line = pseudoLineInfo(null, offset.fOffset);
\r
1506 int lineIndex = getValidLineContaining(offset);
\r
1507 gStart = lineGraphicStartInternal(lineIndex);
\r
1508 line = fLineTable[lineIndex];
\r
1516 lineY = origin.y + gStart;
\r
1518 lineY = origin.y - (gStart + line.getHeight());
\r
1523 lineX = origin.x + gStart;
\r
1525 lineX = origin.x - (gStart + line.getHeight());
\r
1528 line.renderCaret(fText, fLTCurTextLen, g2d, fLineDim, lineX, lineY,
\r
1529 offset.fOffset, strongCaretColor, weakCaretColor);
\r
1533 * Given two offsets in the text, return a rectangle which encloses the lines containing the offsets.
\r
1534 * Offsets do not need to be ordered or nonnegative.
\r
1535 * @param offset1,offset2 offsets into the text
\r
1536 * @param origin the top-left corner of the text, in the display's coordinate system
\r
1537 * @returns a <tt>Rectangle</tt>, relative to <tt>origin</tt>, which encloses the lines containing the offsets
\r
1539 public synchronized Rectangle getBoundingRect(TextOffset offset1,
\r
1540 TextOffset offset2,
\r
1544 Rectangle r = new Rectangle();
\r
1545 getBoundingRect(r, offset1, offset2, origin, tight);
\r
1550 Transform r from "text" coordinates to "screen" coordinates.
\r
1553 private void intlRect(Point origin, Rectangle r) {
\r
1555 int lineOrig, fillOrig;
\r
1558 lineOrig = origin.x;
\r
1559 fillOrig = origin.y;
\r
1562 lineOrig = origin.y;
\r
1563 fillOrig = origin.x;
\r
1569 r.x = lineOrig - (r.x + r.width);
\r
1574 r.y = fillOrig - (r.y + r.height);
\r
1582 r.width = r.height;
\r
1588 public synchronized void getBoundingRect(Rectangle r,
\r
1589 TextOffset offset1,
\r
1590 TextOffset offset2,
\r
1595 if (offset1.equals(offset2)) {
\r
1596 getCaretRect(r, offset1, origin);
\r
1599 if (offset1.greaterThan(offset2)) {
\r
1600 TextOffset t; t = offset1; offset1 = offset2; offset2 = t;
\r
1603 formatToOffset(offset2);
\r
1605 int line = getValidLineContaining(offset1);
\r
1606 r.y = lineGraphicStartInternal(line);
\r
1609 boolean sameLine = false;
\r
1611 if (afterLastParagraph(offset2))
\r
1612 gLimit = fPixHeight + lastCharHeight();
\r
1614 int line2 = getValidLineContaining(offset2);
\r
1615 gLimit = lineGraphicLimitInternal(line2);
\r
1616 sameLine = (line == line2);
\r
1619 r.height = gLimit - r.y;
\r
1621 if (sameLine && tight==TIGHT) {
\r
1622 Rectangle rt = new Rectangle();
\r
1623 getCaretRect(rt, offset1, origin);
\r
1625 if (!offset1.equals(offset2)) {
\r
1626 getCaretRect(rt, offset2, origin);
\r
1632 r.width = fMaxX - fMinX;
\r
1633 intlRect(origin, r);
\r
1636 // System.out.print("gbr: " + r.x + ", " + r.y + ", " + r.width + ", " + r.height);
\r
1638 // System.out.println(" --> " + r.x + ", " + r.y + ", " + r.width + ", " + r.height);
\r
1642 * Compute the offset resulting from moving from a previous offset in direction dir.
\r
1644 * @param result the offset to modify and return. may be null, if so a new offset is allocated, modified, and returned.
\r
1645 * @param previousOffset the insertion offset prior to the arrow key press.
\r
1646 * @param direction the direction of the arrow key (eUp, eDown, eLeft, or eRight)
\r
1647 * @returns new offset based on direction and previous offset.
\r
1649 public synchronized TextOffset findInsertionOffset(TextOffset result, TextOffset prevOffset, short dir)
\r
1651 return findNewInsertionOffset(result, prevOffset, prevOffset, dir);
\r
1655 * Transform key direction: after this step, "left" means previous glyph, "right" means next glyph,
\r
1656 *"up" means previous line, "down" means next line
\r
1658 private short remapArrowKey(short dir) {
\r
1663 else if (dir == eRight)
\r
1670 else if (dir == eDown)
\r
1677 else if (dir == eRight)
\r
1679 else if (dir == eUp)
\r
1681 else if (dir == eDown)
\r
1689 * Compute the offset resulting from moving from a previous offset, starting at an original offset, in direction dir.
\r
1690 * For arrow keys. Use this for "smart" up/down keys.
\r
1691 * @param result the offset to modify and return. May be null, if so a new offset is allocated, modified, and returned.
\r
1692 * @param origOffset the offset at which an up-down arrow key sequence began.
\r
1693 * @param prevOffset the insertion offset prior to the arrow key press
\r
1694 * @param dir the direction of the arrow key (eUp, eDown, eLeft, or eRight)
\r
1695 * @returns new offset based on direction, original offset, and previous offset.
\r
1697 public synchronized TextOffset findNewInsertionOffset(TextOffset result, TextOffset origOffset, TextOffset prevOffset, short dir)
\r
1700 if (result == null)
\r
1701 result = new TextOffset();
\r
1703 dir = remapArrowKey(dir);
\r
1705 // assume that text at origOffset and prevOffset has already been formatted
\r
1707 if (dir == eLeft || dir == eRight) {
\r
1708 formatToOffset(prevOffset);
\r
1709 int line = getValidLineContaining(prevOffset);
\r
1711 result.fPlacement = TextOffset.AFTER_OFFSET;
\r
1712 result.fOffset = fLineTable[line].getNextOffset(fLTCurTextLen, prevOffset.fOffset, dir);
\r
1713 if (result.fOffset < 0) {
\r
1714 result.fOffset = 0;
\r
1716 else if (result.fOffset >= fLTCurTextLen) {
\r
1717 result.setOffset(fLTCurTextLen, TextOffset.BEFORE_OFFSET);
\r
1723 if (afterLastParagraph(origOffset))
\r
1726 int line = getValidLineContaining(origOffset);
\r
1728 distOnLine = fLineTable[line].strongCaretBaselinePosition(fLTCurTextLen, fLineDim, origOffset.fOffset);
\r
1731 // get prevOffset's line
\r
1733 if (afterLastParagraph(prevOffset))
\r
1734 line = lastLine() + 1;
\r
1736 line = getLineContaining(prevOffset);
\r
1738 if (dir == eDown && (line == kAfterLastLine || line == lastLine())
\r
1739 && (lastLineCharStop() < fText.length())) {
\r
1740 shiftTableTo(lastLine());
\r
1741 formatText(lastLineCharStop(), 1, Integer.MAX_VALUE, true);
\r
1742 line = getLineContaining(prevOffset);
\r
1745 if (line == kBeforeFirstLine)
\r
1747 else if (line == kAfterLastLine)
\r
1748 line = lastLine();
\r
1753 else if (dir == eDown)
\r
1756 throw new IllegalArgumentException("Debug: Illegal direction parameter in findNewInsertionOffset");
\r
1759 //result.setOffset(0, TextOffset.AFTER_OFFSET);
\r
1760 result.assign(prevOffset);
\r
1762 else if (line > lastLine()) {
\r
1763 result.setOffset(fLTCurTextLen, TextOffset.BEFORE_OFFSET);
\r
1766 if (fLineTable[line] == null)
\r
1767 line = (dir == eUp)? fLTPosEnd : fLTNegStart;
\r
1769 // anchor is null since we never want a position after newline. If we used the real anchor,
\r
1770 // we might not ignore the newline even though infiniteMode is true.
\r
1771 lineDimToOffset(result, line, distOnLine, 0, null, true);
\r
1775 // System.out.println("fnio prev: " + prevOffset + ", new: " + result);
\r
1780 public synchronized void stopBackgroundFormatting()
\r
1783 fBgFormatAllowed = false;
\r
1786 private synchronized void enableBGFormat()
\r
1789 fBgFormatAllowed = true;
\r
1792 catch (IllegalMonitorStateException e) {
\r
1796 private int lineIndexToNumber(int lineIndex) {
\r
1798 if (lineIndex <= fLTPosEnd) {
\r
1802 return lineIndex - (fLTNegStart-fLTPosEnd-1);
\r
1806 private int lineNumberToIndex(int lineNumber) {
\r
1808 if (lineNumber <= fLTPosEnd) {
\r
1809 return lineNumber;
\r
1812 return lineNumber + (fLTNegStart-fLTPosEnd-1);
\r
1816 private void formatToLineNumber(int lineNumber) {
\r
1818 while (lastLineCharStop() < fLTCurTextLen &&
\r
1819 lineNumber >= lineIndexToNumber(fLTSize)) {
\r
1820 // could be smarter and choose larger amounts for
\r
1821 // larger lines, but probably not worth the effort
\r
1822 formatToHeight(fPixHeight + kPixIncrement);
\r
1826 private static final boolean STRICT = true;
\r
1827 private static final boolean LENIENT = false;
\r
1830 * Insure that at least lineNumber lines exist, doing
\r
1831 * extra formatting if necessary.
\r
1832 * Throws exception if lineNumber is not valid.
\r
1833 * @param strict if STRICT, only lines [0...maxLineNumber()]
\r
1834 * are permitted. If LENIENT, maxLineNumber()+1 is
\r
1835 * the greatest valid value.
\r
1837 private void validateLineNumber(int lineNumber, boolean strict) {
\r
1839 formatToLineNumber(lineNumber);
\r
1841 int maxNumber = lineIndexToNumber(fLTSize);
\r
1842 if (strict == STRICT) {
\r
1846 if (lineNumber > maxNumber+1 ||
\r
1847 (lineNumber == maxNumber+1 && !emptyParagraphAtEndOfText())) {
\r
1848 throw new IllegalArgumentException("Invalid line number: " + lineNumber);
\r
1852 public synchronized int getLineCount() {
\r
1854 // format all text:
\r
1855 formatToHeight(Integer.MAX_VALUE);
\r
1857 int lineCount = lineIndexToNumber(fLTSize);
\r
1859 if (emptyParagraphAtEndOfText()) {
\r
1866 public synchronized int lineContaining(int charIndex) {
\r
1868 formatToOffset(charIndex, TextOffset.AFTER_OFFSET);
\r
1870 boolean placement = TextOffset.AFTER_OFFSET;
\r
1871 if (charIndex == fLTCurTextLen && charIndex > 0) {
\r
1872 placement = emptyParagraphAtEndOfText()? TextOffset.AFTER_OFFSET :
\r
1873 TextOffset.BEFORE_OFFSET;
\r
1876 return lineContaining(charIndex, placement);
\r
1879 public synchronized int lineContaining(TextOffset offset) {
\r
1881 formatToOffset(offset);
\r
1883 if (afterLastParagraph(offset)) {
\r
1884 return lineIndexToNumber(fLTSize);
\r
1887 return lineContaining(offset.fOffset, offset.fPlacement);
\r
1890 private int lineContaining(int off, boolean placement) {
\r
1892 int line = off==0? 0 : getLineContaining(off, placement);
\r
1894 if (line == kAfterLastLine) {
\r
1897 else if (line == kBeforeFirstLine) {
\r
1898 throw new Error("lineContaining got invalid result from getLineContaining().");
\r
1901 return lineIndexToNumber(line);
\r
1904 public synchronized int lineRangeLow(int lineNumber) {
\r
1906 validateLineNumber(lineNumber, STRICT);
\r
1907 int index = lineNumberToIndex(lineNumber);
\r
1909 if (index == fLTSize) {
\r
1910 if (emptyParagraphAtEndOfText()) {
\r
1911 return lastLineCharStop();
\r
1915 if (index >= fLTSize) {
\r
1916 throw new IllegalArgumentException("lineNumber is invalid.");
\r
1919 return lineCharStartInternal(index);
\r
1923 public synchronized int lineRangeLimit(int lineNumber) {
\r
1925 validateLineNumber(lineNumber, STRICT);
\r
1926 int index = lineNumberToIndex(lineNumber);
\r
1928 if (index == fLTSize) {
\r
1929 if (emptyParagraphAtEndOfText()) {
\r
1930 return lastLineCharStop();
\r
1934 if (index >= fLTSize) {
\r
1935 throw new IllegalArgumentException("lineNumber is invalid.");
\r
1938 return lineCharLimitInternal(index);
\r
1943 * Return the number of the line at the given graphic height.
\r
1944 * If height is greater than full height, return line count.
\r
1946 public synchronized int lineAtHeight(int height) {
\r
1948 if (height >= fPixHeight) {
\r
1950 int line = getLineCount();
\r
1951 if (height < fFullPixHeight) {
\r
1956 else if (height < 0) {
\r
1960 return lineIndexToNumber(findLineAt(height));
\r
1964 public synchronized int lineGraphicStart(int lineNumber) {
\r
1967 validateLineNumber(lineNumber, LENIENT);
\r
1969 int index = lineNumberToIndex(lineNumber);
\r
1971 if (index < fLTSize) {
\r
1972 return lineGraphicStartInternal(index);
\r
1975 if (index == fLTSize+1) {
\r
1976 return fFullPixHeight;
\r
1979 return fPixHeight;
\r
1984 public synchronized boolean lineIsLeftToRight(int lineNumber) {
\r
1986 validateLineNumber(lineNumber, STRICT);
\r
1988 int index = lineNumberToIndex(lineNumber);
\r
1990 if (index < fLTSize) {
\r
1991 return fLineTable[index].isLeftToRight();
\r
1994 AttributeMap st = fText.paragraphStyleAt(fLTCurTextLen);
\r
1995 return !TextAttribute.RUN_DIRECTION_RTL.equals(st.get(TextAttribute.RUN_DIRECTION));
\r
2000 * Number of pixels by which to advance formatting in the background.
\r
2002 private static final int kPixIncrement = 100;
\r
2005 * Time to sleep between background formatting operations.
\r
2007 private static final int kInterval = 100;
\r
2010 * Perform periodic background formatting.
\r
2015 synchronized (this) {
\r
2016 while(!fBgFormatAllowed) {
\r
2019 } catch(InterruptedException e) {
\r
2023 checkTimeStamp();
\r
2024 formatToHeight(fPixHeight + kPixIncrement);
\r
2026 if (lastLineCharStop() == fLTCurTextLen) {
\r
2027 stopBackgroundFormatting();
\r
2032 Thread.sleep(kInterval);
\r
2034 catch(InterruptedException e) {
\r
2039 private ParagraphRenderer getRendererFor(AttributeMap s) {
\r
2041 // Note: eventually we could let clients put their own renderers
\r
2043 BidiParagraphRenderer renderer = (BidiParagraphRenderer) fRendererCache.get(s);
\r
2044 if (renderer == null) {
\r
2045 renderer = new BidiParagraphRenderer(fDefaultValues.addAttributes(s), fDefaultCharMetric);
\r
2046 fRendererCache.put(s, renderer);
\r