]> gitweb.fperrin.net Git - Dictionary.git/blob - jars/icu4j-4_2_1-src/src/com/ibm/richtext/textformat/AsyncFormatter.java
icu4jsrc
[Dictionary.git] / jars / icu4j-4_2_1-src / src / com / ibm / richtext / textformat / AsyncFormatter.java
1 /*\r
2  * (C) Copyright IBM Corp. 1998-2007.  All Rights Reserved.\r
3  *\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
12  */\r
13 // Requires Java2\r
14 // Revision: 70 1.38 richtext/AsyncFormatter.java, richtext, richtext\r
15 \r
16 package com.ibm.richtext.textformat;\r
17 \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
22 \r
23 import java.text.BreakIterator;\r
24 import java.text.CharacterIterator;\r
25 \r
26 import java.util.Hashtable;\r
27 \r
28 import com.ibm.richtext.textlayout.attributes.AttributeMap;\r
29 import com.ibm.richtext.textlayout.attributes.TextAttribute;\r
30 \r
31 import com.ibm.richtext.styledtext.MConstText;\r
32 \r
33 import com.ibm.richtext.textlayout.Graphics2DConversion;\r
34 \r
35 ///*JDK12IMPORTS\r
36 import java.awt.Graphics2D;\r
37 import java.awt.font.LineBreakMeasurer;\r
38 import java.awt.font.FontRenderContext;\r
39 //JDK12IMPORTS*/\r
40 \r
41 /*JDK11IMPORTS\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
45 JDK11IMPORTS*/\r
46 \r
47 /*\r
48     Change history:\r
49 \r
50     7/25/96 -\r
51 \r
52     8/15/96\r
53         Fixed bug in textOffsetToPoint (fPixHeight wasn't added to negative heights). {jbr}\r
54 \r
55     8/19/96\r
56         Removed references to JustificationStyle constants, and moved them into MFormatter {sfb}\r
57 \r
58     8/23/96\r
59         Modified findLineAt and getLineContaining - added optimization in search loop.  Also,\r
60             they were failing when fLTPosEnd+1 == fLTNegStart.  Fixed.\r
61 \r
62     8/26/96\r
63         Moved FormatDaemon stuff into this class.\r
64 \r
65     9/11/96\r
66         Shortened line returned from textOffsetToPoint by 1 pixel\r
67 \r
68     9/23/96\r
69         textOffsetToPoint line length restored (see above).  drawText() now draws only lines\r
70         which fall in rectangle param.\r
71 \r
72     9/26/96\r
73         whitespace at end of line is used for caret positioning\r
74 \r
75     10/4/96\r
76         Added static TextBox method.\r
77 \r
78     10/8/96\r
79         Line 1300 - less than changed to less than or equal in textOffsetToPoint.  Watch for\r
80             hangs in formatText.\r
81 \r
82     10/9/96\r
83         Changed sync. model.  fFormatInBackground is used to start/stop bg formatting\r
84 \r
85     10/17/96\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
89 \r
90     10/21/96\r
91         Pushed paragraph formatting and caret positioning into LineLayout.  In process of pushing\r
92         highlighting into LineLayout.\r
93 \r
94     10/24/96\r
95         Now getting paragraph styles from paragraph buffer.\r
96 \r
97     7/7/97\r
98         Up-arrow doesn't move to beginning of text if you're on the first line.\r
99 \r
100     7/1/98\r
101         No longer intersecting damaged rect with view rect in updateFormat.\r
102 */\r
103 \r
104 /**\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
111 * <p>\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
124 * <p>\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
127 *\r
128 * @author John Raley\r
129 *\r
130 * @see MFormatter\r
131 * @see LineLayout\r
132 * @see LayoutContext\r
133 * @see LayoutInfo\r
134 */\r
135 \r
136 final class AsyncFormatter extends MFormatter implements Runnable\r
137 {\r
138     static final String COPYRIGHT =\r
139                 "(C) Copyright IBM Corp. 1998-1999 - All Rights Reserved";\r
140     /**\r
141     * Text to format.\r
142     */\r
143     private MConstText fText;\r
144 \r
145     /**\r
146     * Default values.\r
147     */\r
148     private AttributeMap fDefaultValues;\r
149     \r
150     /**\r
151     * Font resolver.\r
152     */\r
153     private FontResolver fFontResolver;\r
154     \r
155     /**\r
156     * Default character metric - used to set height of empty paragraphs\r
157     */\r
158     private DefaultCharacterMetric fDefaultCharMetric;\r
159     \r
160     /**\r
161     * Length to which lines are formatted.\r
162     */\r
163     private int fLineDim;\r
164 \r
165     /**\r
166     * Table of formatted lines.\r
167     */\r
168     private LayoutInfo fLineTable[];\r
169 \r
170     /**\r
171     * Size of line table.\r
172     */\r
173     private int fLTSize = 10; // initial size must be > 0\r
174 \r
175     /**\r
176     * Index of last positive entry in line table.\r
177     */\r
178     private int fLTPosEnd;\r
179 \r
180     /**\r
181     * Index of first negative entry in line table.\r
182     */\r
183     private int fLTNegStart;\r
184 \r
185     /**\r
186     * Length of text on which negative line offsets are based.\r
187     * @see #formatText\r
188     */\r
189     private int fLTCurTextLen;\r
190 \r
191     /**\r
192     * Length of formatted text in fill direction, in pixels.\r
193     */\r
194     private int fPixHeight;\r
195 \r
196     /**\r
197     * Length of formatted text including pseudoline.\r
198     */\r
199     private int fFullPixHeight;\r
200 \r
201     private int fMinX;\r
202     private int fMaxX;\r
203 \r
204     /**\r
205     * <tt>true</tt> if lines should be formatted to fit line dimension.\r
206     */\r
207     private boolean fWrap;\r
208 \r
209     /**\r
210     * <tt>true</tt> if characters run horizontally.\r
211     */\r
212     private boolean fHLine = true;\r
213 \r
214     /**\r
215      * <tt>true</tt> if characters run from from low to high coordinates on line.\r
216      */\r
217     private boolean fLineInc = true;\r
218 \r
219     /**\r
220      * <tt>true</tt> if lines run from low to high coordinates within page.\r
221      */\r
222     private boolean fFillInc = true;\r
223 \r
224     /**\r
225     * Value returned from <tt>findLineAt()</tt>\r
226     * if pixel height precedes topmost line.\r
227     */\r
228     private static final int kBeforeFirstLine = -2;\r
229 \r
230     /**\r
231     * Value returned from <tt>findLineAt()</tt> and <tt>getLineContaining()</tt>\r
232     * if offset / pixel height is after all existing lines.\r
233     */\r
234     private static final int kAfterLastLine = -1;\r
235 \r
236     /**\r
237     * Thread which invokes formatter in the background.\r
238     */\r
239     private Thread fDaemon;\r
240 \r
241     /**\r
242     * FontRenderContext to measure with.  Currently not settable after\r
243     * construction.\r
244     */\r
245     private FontRenderContext fFontRenderContext;\r
246 \r
247     /**\r
248     * Controls whether background formatting can run.\r
249     */\r
250     private boolean fBgFormatAllowed = false;\r
251     \r
252     /**\r
253     * Cached line break object.\r
254     */\r
255     private BreakIterator fLineBreak = null;\r
256     \r
257     /**\r
258     * Cached LineBreakMeasurer.\r
259     */\r
260     private LineBreakMeasurer fCachedMeasurer = null;\r
261     private int fCachedMeasurerStart;\r
262     private int fCachedMeasurerLimit;\r
263     \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
269     \r
270     /**\r
271     * Current text time stamp.  Used to maintain modification invariant.\r
272     */\r
273     private int fCurTimeStamp;\r
274     \r
275     /**\r
276     * Cache of ParagraphRenderers.\r
277     */\r
278     private Hashtable fRendererCache = new Hashtable();\r
279     \r
280     /**\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
283     * <p>\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
289     */\r
290 \r
291     private static boolean isParagraphSeparator(char ch) {\r
292 \r
293         return ch == '\n' || ch == '\u2029';\r
294     }\r
295 \r
296     /**\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
301     */\r
302     AsyncFormatter(MConstText text, \r
303                    AttributeMap defaultValues,\r
304                    int lineBound,\r
305                    boolean wrap,\r
306                    Graphics g)\r
307     {\r
308         fText = text;\r
309         fDefaultValues = defaultValues;\r
310         fFontResolver = new FontResolver(fDefaultValues);\r
311         \r
312         fLineDim = lineBound;\r
313         fWrap = wrap;\r
314         Graphics2D g2d = Graphics2DConversion.getGraphics2D(g);\r
315         fFontRenderContext = g2d.getFontRenderContext();\r
316         \r
317         fDefaultCharMetric = new DefaultCharacterMetric(fFontResolver, \r
318                                                         fFontRenderContext);\r
319         fLTCurTextLen = text.length();\r
320         removeAllLines();\r
321 \r
322         fDaemon = new Thread(this);\r
323         fDaemon.start();\r
324     }\r
325     \r
326     public AttributeMap getDefaultValues() {\r
327     \r
328         return fDefaultValues;\r
329     }\r
330     \r
331     public void checkTimeStamp() {\r
332         String admonition = "Probably, you modified " +\r
333                             "the text before calling stopBackgroundFormatting().";\r
334                             \r
335         if (fText.getTimeStamp() != fCurTimeStamp) {\r
336             throw new Error("Time stamp is out of sync.  " + admonition);\r
337         }\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
343                             "text="+fText);\r
344         }\r
345     }\r
346 \r
347     /**\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
351     */\r
352     public synchronized void setWrap(boolean wrap)\r
353     {\r
354         if (wrap != fWrap) {\r
355             fWrap = wrap;\r
356             removeAllLines();\r
357         }\r
358     }\r
359 \r
360     /**\r
361     * Return true if lines are wrapped using the line dimension.\r
362     * @see #setWrap\r
363     */\r
364     public synchronized boolean wrap()\r
365     {\r
366         return fWrap;\r
367     }\r
368 \r
369     /**\r
370     * Specify the lineBound in pixels.  If line wrapping is on, lines\r
371     * will be wrapped to this value.\r
372     * <p>\r
373     *\r
374     * @param lineBound the distance, in pixels, used to wrap lines.\r
375     */\r
376     public synchronized void setLineBound(int lineBound)\r
377     {\r
378         if (fLineDim != lineBound) {\r
379             fLineDim = lineBound;                \r
380             if (fWrap) {\r
381                 removeAllLines();\r
382             }\r
383         }\r
384     }\r
385 \r
386     /**\r
387     * Return the number of pixels along the line dimension.\r
388     */\r
389     public synchronized int lineBound()\r
390     {\r
391         return fLineDim;\r
392     }\r
393 \r
394 //    /**\r
395 //    * Return <tt>true</tt> if characters run from low to high coordinates on line.\r
396 //    */\r
397 //    private boolean lineInc()\r
398 //    {\r
399 //        return fLineInc;\r
400 //    }\r
401 //\r
402 //    /**\r
403 //    * Return <tt>true</tt> if lines run from low to high coordinates on page.\r
404 //    */\r
405 //    private boolean fillInc()\r
406 //    {\r
407 //        return fFillInc;\r
408 //    }\r
409 \r
410     /**\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
413     * line dim.\r
414     */\r
415     private synchronized void removeAllLines()\r
416     {\r
417         fCurTimeStamp = fText.getTimeStamp();\r
418         stopBackgroundFormatting();\r
419 \r
420         fMinX = 0;\r
421         fMaxX = fLineDim;\r
422         \r
423         fLineTable = new LayoutInfo[fLTSize]; // fLTSize must be > 0\r
424         fLTNegStart = fLTSize;\r
425         fLTPosEnd = 0;\r
426 \r
427         fLineTable[0] = pseudoLineInfo(null, 0);\r
428 \r
429         fPixHeight = fLineTable[0].getHeight(); // ??? or should it be zero?\r
430         fFullPixHeight = fPixHeight;\r
431 \r
432         // format at least one line:\r
433         formatToHeight(fPixHeight + 1);\r
434 \r
435         enableBGFormat();\r
436     }\r
437 \r
438     /**\r
439      * Fill the layout info with information appropriate to the pseudoline.\r
440      */\r
441     private synchronized LayoutInfo pseudoLineInfo(LayoutInfo info, int offset)\r
442     {\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
446                                info,\r
447                                (LineBreakMeasurer)null,\r
448                                fFontRenderContext,\r
449                                offset,\r
450                                offset,\r
451                                fLineDim,\r
452                                fLineDim);\r
453 \r
454         return info;\r
455     }\r
456 \r
457     /**\r
458     * Return the index of the last valid line in the line table.\r
459     */\r
460     private int lastLine()\r
461     {\r
462         return (fLTNegStart == fLTSize) ? fLTPosEnd : fLTSize - 1;\r
463     }\r
464 \r
465     /**\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
468     * <p>\r
469     * @param lastPos the index of the line which will become the last positive\r
470     * entry in the line table\r
471     */\r
472     private void shiftTableTo(int lastPos)\r
473     {\r
474         LayoutInfo li;\r
475 \r
476         while (lastPos < fLTPosEnd)    { // shift +'s to -'s\r
477             li = fLineTable[fLTPosEnd];\r
478             fLineTable[fLTPosEnd--] = null;\r
479 \r
480             li.makeRelativeToEnd(fLTCurTextLen, fPixHeight);\r
481 \r
482             fLineTable[--fLTNegStart] = li;\r
483         }\r
484 \r
485         while (lastPos >= fLTNegStart) { // shift -'s to +'s\r
486             li = fLineTable[fLTNegStart];\r
487             fLineTable[fLTNegStart++] =    null;\r
488 \r
489             li.makeRelativeToBeginning(fLTCurTextLen, fPixHeight);\r
490 \r
491             fLineTable[++fLTPosEnd]    = li;\r
492         }\r
493     }\r
494 \r
495     /**\r
496     * Increase the size of the line table.\r
497     */\r
498     private void expandLineTable()\r
499     {\r
500         // This just doubles the size of the line table.\r
501 \r
502         LayoutInfo newLineTable[] = new LayoutInfo[fLineTable.length * 2];\r
503         int newNegStart = newLineTable.length - (fLineTable.length - fLTNegStart);\r
504 \r
505         System.arraycopy(fLineTable, 0, newLineTable, 0, fLTPosEnd + 1);\r
506         System.arraycopy(fLineTable, fLTNegStart, newLineTable, newNegStart, fLTSize - fLTNegStart);\r
507 \r
508         fLTNegStart = newNegStart;\r
509         fLTSize = newLineTable.length;\r
510         fLineTable = newLineTable;\r
511     }\r
512 \r
513     /**\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
517     * <p>\r
518     * @param fillCoord "height" of line to locate.\r
519     */\r
520     private int findLineAt(int fillCoord)\r
521     {\r
522         int low, high, mid;\r
523         int lowStart, highStart, midStart;\r
524 \r
525         if (fillCoord >= fPixHeight)\r
526             return kAfterLastLine;\r
527         else if (fillCoord < 0)\r
528             return kBeforeFirstLine;\r
529 \r
530         if ((fLTNegStart < fLTSize) && (fillCoord >= fLineTable[fLTNegStart].getGraphicStart(fPixHeight))) {\r
531             fillCoord -= fPixHeight;\r
532 \r
533             low = fLTNegStart;\r
534             high = fLTSize;\r
535             highStart = 0;\r
536         }\r
537         else {\r
538             low = 0;\r
539             high = fLTPosEnd + 1;\r
540             highStart = fLineTable[fLTPosEnd].getGraphicStart(0) + fLineTable[fLTPosEnd].getHeight();\r
541         }\r
542         lowStart = fLineTable[low].getGraphicStart(0);\r
543 \r
544         do {\r
545             if (lowStart == highStart)\r
546                 return low;\r
547 \r
548             mid = low + (fillCoord - lowStart) / (highStart - lowStart) * (high - low);\r
549             midStart = fLineTable[mid].getGraphicStart(0);\r
550 \r
551             if (midStart > fillCoord) {\r
552                 high = mid;\r
553                 highStart = fLineTable[high].getGraphicStart(0);\r
554             }\r
555             else if (midStart + fLineTable[mid].getHeight() <= fillCoord) {\r
556                 low = mid + 1;\r
557                 lowStart = fLineTable[low].getGraphicStart(0);\r
558             }\r
559             else\r
560                 return mid;\r
561 \r
562         } while (low < high);\r
563 \r
564         return 0;\r
565     }\r
566 \r
567     /**\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
570      */\r
571     private int lineCharStartInternal(int line)\r
572     {\r
573         return fLineTable[line].getCharStart(fLTCurTextLen);\r
574     }\r
575 \r
576     /**\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
579      */\r
580     private int lineCharLimitInternal(int line)\r
581     {\r
582         return lineCharStartInternal(line) + fLineTable[line].getCharLength();\r
583     }\r
584 \r
585     /**\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
588      */\r
589     private int lineGraphicStartInternal(int line)\r
590     {\r
591         return fLineTable[line].getGraphicStart(fPixHeight);\r
592     }\r
593 \r
594     /**\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
597      */\r
598     private int lineGraphicLimitInternal(int line)\r
599     {\r
600         return lineGraphicStartInternal(line) + fLineTable[line].getHeight();\r
601     }\r
602 \r
603     /**\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
606     */\r
607     private int lastLineCharStop()\r
608     {\r
609         return lineCharLimitInternal(lastLine());\r
610     }\r
611 \r
612     /**\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
615      * linetable index.\r
616      */\r
617     private int getValidLineContaining(TextOffset offset) {\r
618 \r
619         return getValidLineContaining(offset.fOffset, offset.fPlacement);\r
620     }\r
621 \r
622     /**\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
625      * linetable index.\r
626      */\r
627     private int getValidLineContaining(int insOffset, boolean placement)\r
628     {\r
629         int line = getLineContaining(insOffset, placement);\r
630         if (line == kAfterLastLine)\r
631             line = lastLine();\r
632         else if (line == kBeforeFirstLine)\r
633             throw new IllegalArgumentException("Debug: getLineContaining returned kBeforeFirstLine");\r
634 \r
635         return line;\r
636     }\r
637 \r
638     /**\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
641     * <p>\r
642     * @param offset the offset whose line should be located\r
643     * @returns line containing <tt>offset</tt>\r
644     */\r
645     private int getLineContaining(TextOffset offset) {\r
646 \r
647         return getLineContaining(offset.fOffset, offset.fPlacement);\r
648     }\r
649 \r
650     private int getLineContaining(int insOffset, boolean placement)\r
651     {\r
652         int pos = insOffset;\r
653         if (placement == TextOffset.BEFORE_OFFSET && pos > 0)\r
654             --pos;\r
655 \r
656         if (pos < 0) {\r
657             throw new IllegalArgumentException("Debug: getLineContaining offset < 0: " + pos);\r
658         }\r
659 \r
660         if (pos >= lastLineCharStop()) {\r
661             return emptyParagraphAtEndOfText()? kAfterLastLine : lastLine();\r
662         }\r
663 \r
664         int low, high, mid;\r
665         int lowStart, highStart, midStart;\r
666 \r
667         if ((fLTNegStart < fLTSize) && (pos >= fLineTable[fLTNegStart].getCharStart(fLTCurTextLen))) {\r
668             pos -= fLTCurTextLen;\r
669 \r
670             low = fLTNegStart;\r
671             high = fLTSize;\r
672             highStart = 0;\r
673         }\r
674         else {\r
675             low = 0;\r
676             high = fLTPosEnd + 1;\r
677             highStart = fLineTable[fLTPosEnd].getCharStart(0) + fLineTable[fLTPosEnd].getCharLength();\r
678         }\r
679         lowStart = fLineTable[low].getCharStart(0);\r
680 \r
681         do {\r
682             if (highStart == lowStart) {\r
683                 return low;\r
684             }\r
685 \r
686             mid = low + (pos - lowStart) / (highStart - lowStart) * (high - low);\r
687             midStart = fLineTable[mid].getCharStart(0);\r
688 \r
689             if (midStart > pos) {\r
690                 high = mid;\r
691                 highStart = fLineTable[high].getCharStart(0);\r
692             }\r
693             else if (midStart + fLineTable[mid].getCharLength() <= pos) {\r
694                 low = mid + 1;\r
695                 lowStart = fLineTable[low].getCharStart(0);\r
696             }\r
697             else {\r
698                 return mid;\r
699             }\r
700 \r
701         } while (low < high);\r
702 \r
703         return 0;\r
704     }\r
705 \r
706     /**\r
707     * Display text in drawArea. Does not reformat text.\r
708     * <p>\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
712     */\r
713     public synchronized void draw(Graphics g, Rectangle drawArea, Point origin)\r
714     {\r
715         draw(g, drawArea, origin, null, null, null);\r
716     }\r
717 \r
718     public synchronized void draw(Graphics g, Rectangle drawArea, Point origin,\r
719                 TextOffset selStart, TextOffset selStop, Color highlight) {\r
720 \r
721         checkTimeStamp();\r
722         Graphics2D g2d = Graphics2DConversion.getGraphics2D(g);\r
723 \r
724         // Get starting and ending fill 'heights.'\r
725 \r
726         int startFill;\r
727         int endFill;\r
728 \r
729         if (fFillInc)\r
730             startFill = drawArea.y - origin.y;\r
731         else\r
732             startFill = origin.y - (drawArea.y + drawArea.height);\r
733 \r
734         endFill = startFill + drawArea.height;\r
735 \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
744 \r
745         --endFill;\r
746 \r
747         // Format to ending fill height, so line table is valid for all lines we need to draw.\r
748 \r
749         formatToHeight(endFill);\r
750 \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
753 \r
754         int curLine = findLineAt(startFill);\r
755         if (curLine == kAfterLastLine)\r
756             return;\r
757         else if (curLine == kBeforeFirstLine)\r
758             curLine = 0;\r
759 \r
760         int lastLine = findLineAt(endFill);\r
761         if (lastLine == kBeforeFirstLine)\r
762             return;\r
763         else if (lastLine == kAfterLastLine)\r
764             lastLine = lastLine();\r
765 \r
766         // Get the base coordinates (lineX, lineY) for the starting line.\r
767 \r
768         int lineX, lineY;\r
769 \r
770         int gStart = lineGraphicStartInternal(curLine);\r
771 \r
772         if (fHLine) {\r
773             if (fLineInc)\r
774                 lineX = origin.x;\r
775             else\r
776                 lineX = origin.x - fLineDim;\r
777             if (fFillInc)\r
778                 lineY = origin.y + gStart;\r
779             else\r
780                 lineY = origin.y - (gStart + fLineTable[curLine].getHeight());\r
781         }\r
782         else {\r
783             if (fLineInc)\r
784                 lineY = origin.y;\r
785             else\r
786                 lineY = origin.y - fLineDim;\r
787             if (fFillInc)\r
788                 lineX = origin.x + gStart;\r
789             else\r
790                 lineX = origin.x - (gStart + fLineTable[curLine].getHeight());\r
791         }\r
792 \r
793         // Iterate through lines, drawing each one and incrementing the base coordinate by the line height.\r
794 \r
795 \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
800 \r
801             fLineTable[curLine].renderWithHighlight(fLTCurTextLen, g2d, fLineDim, lineX, lineY, selStart, selStop, highlight);\r
802 \r
803             // Increment line base for next iteration.\r
804             int lineInc = fLineTable[curLine].getHeight();\r
805             if (fFillInc) {\r
806                 if (fHLine)\r
807                     lineY += lineInc;\r
808                 else\r
809                     lineX += lineInc;\r
810             } else {\r
811                 if (fHLine)\r
812                     lineY -= lineInc;\r
813                 else\r
814                     lineX -= lineInc;\r
815             }\r
816         }\r
817     }\r
818 \r
819     /**\r
820     * Format text to given height.\r
821     * @param height the height to which text will be formatted.\r
822     */\r
823     public synchronized void formatToHeight(int reqHeight)\r
824     {\r
825         checkTimeStamp();\r
826         if (reqHeight <= fPixHeight) // already formatted to this height\r
827             return;\r
828 \r
829         if (fText.length() == lastLineCharStop()) // already formatted all the text\r
830             return;\r
831 \r
832         // +++ should disable update thread here\r
833 \r
834         if (fLTNegStart < fLTSize)\r
835             shiftTableTo(fLTSize - 1);\r
836 \r
837         formatText(0, 0, reqHeight, false);\r
838     }\r
839 \r
840     /**\r
841      * Format text to given offset.\r
842      * @param offset the offset to which text will be formatted.\r
843      */\r
844      private void formatToOffset(TextOffset offset)\r
845      {\r
846         formatToOffset(offset.fOffset, offset.fPlacement);\r
847      }\r
848      \r
849      private synchronized void formatToOffset(int offset, boolean placement) {\r
850         \r
851         checkTimeStamp();\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
856                 limit++;\r
857             if (limit >= llcs) { // ??? would '>' be ok instead or '>='?\r
858                 if (limit > fLTCurTextLen)\r
859                     limit = fLTCurTextLen;\r
860 \r
861                 shiftTableTo(lastLine());\r
862                 formatText(llcs, limit - llcs, Integer.MAX_VALUE, true);\r
863             }\r
864         }\r
865      }\r
866 \r
867 /**\r
868 * Reformat text after a change.\r
869 * After the formatter's text changes, call this method to reformat.  Does\r
870 * not redraw.\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
875 * be less than 0.\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
881 */\r
882     public Rectangle updateFormat(final int afStart,\r
883                                                final int afLength,\r
884                                                Rectangle viewRect,\r
885                                                Point origin)\r
886     {\r
887         if (afStart < 0) {\r
888             throw new IllegalArgumentException("Debug: updateFormat afStart < 0: " + afStart);\r
889         }\r
890         if (fBgFormatAllowed) {\r
891             throw new IllegalArgumentException("Background formatting should have been disabled");\r
892         }\r
893         fCurTimeStamp = fText.getTimeStamp();\r
894         \r
895         int curLine = getValidLineContaining(afStart, TextOffset.AFTER_OFFSET);\r
896         int lineStartPos = lineCharStartInternal(curLine);\r
897 \r
898         // optimize by finding out whether change occurred\r
899         // after first word break on curline\r
900 \r
901         int firstPossibleBreak;\r
902 \r
903         if (lineStartPos < fText.length()) {\r
904 \r
905             if (fLineBreak == null) {\r
906                 fLineBreak = BreakIterator.getLineInstance();\r
907             }\r
908             CharacterIterator charIter = fText.createCharacterIterator();\r
909             charIter.setIndex(lineStartPos);\r
910             fLineBreak.setText(charIter);\r
911 \r
912             firstPossibleBreak = fLineBreak.following(lineStartPos);\r
913         }\r
914         else\r
915             firstPossibleBreak = afStart;\r
916 \r
917         if ((curLine > 0) && (firstPossibleBreak == BreakIterator.DONE || afStart <= firstPossibleBreak)) {\r
918             curLine--;\r
919             if (curLine < fLTNegStart && curLine > fLTPosEnd)\r
920                 curLine = fLTPosEnd;\r
921         }\r
922 \r
923         shiftTableTo(curLine);\r
924 \r
925         int pixHeight; // after the formatText call, at least pixHeight text must be formatted\r
926 \r
927         if (fHLine) {\r
928             if (fFillInc)\r
929                 pixHeight = viewRect.y + viewRect.height - origin.y;\r
930             else\r
931                 pixHeight = origin.y - viewRect.y;\r
932         }\r
933         else {\r
934             if (fFillInc)\r
935                 pixHeight = viewRect.x + viewRect.width - origin.x;\r
936             else\r
937                 pixHeight = origin.x - viewRect.x;\r
938         }\r
939 \r
940         Rectangle r = formatText(afStart, afLength, pixHeight, false);\r
941 \r
942         //dumpLineTable();\r
943 \r
944         if ((fPixHeight < pixHeight) && (fLTNegStart < fLTSize) && (fLTCurTextLen > lastLineCharStop())) {\r
945             shiftTableTo(lastLine());\r
946             Rectangle s = formatText(0, 0, pixHeight, false);\r
947             r = r.union(s);\r
948         }\r
949 \r
950         intlRect(origin, r);\r
951         //System.out.println("Damaged rect: "+r+"; origin: "+origin);\r
952 \r
953         // don't need to synchronized here, b/c the daemon shouldn't be running when\r
954         // this is executing\r
955 \r
956         if (fText.length() < lastLineCharStop())\r
957             enableBGFormat();\r
958         else\r
959             stopBackgroundFormatting();\r
960 \r
961         //dumpLineTable();\r
962 \r
963         return r;\r
964     }\r
965     \r
966     private LineBreakMeasurer makeMeasurer(int paragraphStart, int paragraphLimit) {\r
967 \r
968         MTextIterator iter = new MTextIterator(fText,\r
969                                                fFontResolver,\r
970                                                paragraphStart,\r
971                                                paragraphLimit);\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
977         }\r
978         return measurer;\r
979     }        \r
980 \r
981 /**\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
986 * <p>\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
989 * begin.\r
990 * <p>\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
1005 */\r
1006 \r
1007     private Rectangle formatText(int afStart, final int afLength, int reqHeight, boolean seekOffsetAtEnd)\r
1008     {\r
1009     /* assumes line table shifted such that first line to format is\r
1010     last positive line */\r
1011 \r
1012         if (afLength < 0) {\r
1013             throw new IllegalArgumentException("afLength < 0.  afLength=" + afLength);\r
1014         }\r
1015 \r
1016         int newTextEnd = afStart + afLength;\r
1017 \r
1018         final int newCurTextLen = fText.length();\r
1019 \r
1020         // variable not used int oldPixHeight = fPixHeight;\r
1021         int oldFullPixHeight = fFullPixHeight;\r
1022         fPixHeight -= fLineTable[fLTPosEnd].getHeight();\r
1023 \r
1024         int curGraphicStart = fLineTable[fLTPosEnd].getGraphicStart(fPixHeight);\r
1025         int curLineStart = fLineTable[fLTPosEnd].getCharStart(newCurTextLen);\r
1026         \r
1027         int curParagraphStart = fText.paragraphStart(curLineStart);\r
1028         int curParagraphLimit = Integer.MIN_VALUE; // dummy value\r
1029 \r
1030         int damageStart = curGraphicStart;\r
1031 \r
1032         ParagraphRenderer renderer = null;\r
1033         LineBreakMeasurer measurer = null;\r
1034         \r
1035         // try to use cached LineBreakMeasurer if possible\r
1036         if (fCachedMeasurer != null && \r
1037             curParagraphStart == fCachedMeasurerStart) {\r
1038 \r
1039             curParagraphLimit = fText.paragraphLimit(curParagraphStart);\r
1040             \r
1041             try {\r
1042                 if (newCurTextLen - fLTCurTextLen == 1 && afLength == 1) {\r
1043                     if (curParagraphLimit == fCachedMeasurerLimit+1) {\r
1044                         MTextIterator iter = new MTextIterator(fText,\r
1045                                                                fFontResolver, \r
1046                                                                curParagraphStart,\r
1047                                                                curParagraphLimit);\r
1048                         fCachedMeasurer.insertChar(iter, afStart);\r
1049                         fCachedMeasurerLimit += 1;\r
1050                         measurer = fCachedMeasurer;\r
1051                     }\r
1052                 }\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
1057                                                                fFontResolver,\r
1058                                                                curParagraphStart,\r
1059                                                                curParagraphLimit);\r
1060                         fCachedMeasurer.deleteChar(iter, afStart);\r
1061                         fCachedMeasurerLimit -= 1;\r
1062                         measurer = fCachedMeasurer;\r
1063                     }\r
1064                 }\r
1065             }\r
1066             catch(ArrayIndexOutOfBoundsException e) {\r
1067                 fCachedMeasurer = null;\r
1068                 fgCacheMeasurers = false;\r
1069             }\r
1070             \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
1077             }\r
1078         }\r
1079         \r
1080         if (measurer == null) {\r
1081             // trigger paragraph update at start of formatting loop\r
1082             curParagraphLimit = curParagraphStart;\r
1083             curParagraphStart = 0;\r
1084         }\r
1085 \r
1086         fLTCurTextLen = newCurTextLen;\r
1087         \r
1088         while (true) {\r
1089             // System.out.println("line: " + fLTPosEnd + ", cls: " + curLineStart);\r
1090 \r
1091 \r
1092             if (curLineStart >= curParagraphLimit) {\r
1093                 curParagraphStart = curParagraphLimit;\r
1094                 curParagraphLimit = fText.paragraphLimit(curParagraphStart);\r
1095 \r
1096                 AttributeMap style = fText.paragraphStyleAt(curParagraphStart);\r
1097                 renderer = getRendererFor(style);\r
1098 \r
1099                 if (curParagraphStart < curParagraphLimit) {\r
1100                     measurer = makeMeasurer(curParagraphStart, curParagraphLimit);\r
1101                     measurer.setPosition(curLineStart);\r
1102                 }\r
1103                 else {\r
1104                     measurer = null;\r
1105                 }\r
1106             }\r
1107 \r
1108             {\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
1113                 }\r
1114 \r
1115                 fLineTable[fLTPosEnd] = renderer.layout(fText,\r
1116                                                         fLineTable[fLTPosEnd],\r
1117                                                         measurer,\r
1118                                                         fFontRenderContext,\r
1119                                                         curParagraphStart,\r
1120                                                         curParagraphLimit,\r
1121                                                         fWrap ? fLineDim : Integer.MAX_VALUE,\r
1122                                                         fLineDim);\r
1123                 if (haveOldDirection) {\r
1124                     if (fLineTable[fLTPosEnd].isLeftToRight() != oldDirection) {\r
1125                         newTextEnd = Math.max(newTextEnd, curParagraphLimit);\r
1126                     }\r
1127                 }\r
1128             }\r
1129 \r
1130             {\r
1131                 LayoutInfo theLine = fLineTable[fLTPosEnd];\r
1132 \r
1133                 theLine.setGraphicStart(curGraphicStart);\r
1134                 curGraphicStart += theLine.getHeight();\r
1135 \r
1136                 fPixHeight += theLine.getHeight();\r
1137                 curLineStart += theLine.getCharLength();\r
1138 \r
1139                 if (!fWrap) {\r
1140                     int lineWidth = theLine.getTotalAdvance() + theLine.getLeadingMargin();\r
1141                     if (theLine.isLeftToRight()) {\r
1142                         if (fMaxX < lineWidth) {\r
1143                             fMaxX = lineWidth;\r
1144                         }\r
1145                     }\r
1146                     else {\r
1147                         if (fLineDim-lineWidth < fMinX) {\r
1148                             fMinX = fLineDim-lineWidth;\r
1149                         }\r
1150                     }\r
1151                 }\r
1152             }\r
1153             /*\r
1154                 Next, discard obsolete lines.  A line is obsolete if it\r
1155                 contains new text or text which has been formatted.\r
1156             */\r
1157 \r
1158             while (fLTNegStart < fLTSize) {\r
1159                 int linePos = fLineTable[fLTNegStart].getCharStart(newCurTextLen);\r
1160                 if (linePos >= curLineStart && linePos >= newTextEnd)\r
1161                     break;\r
1162 \r
1163                 // System.out.println("delete neg line: " + fLTNegStart);\r
1164                 fPixHeight -= fLineTable[fLTNegStart].getHeight();\r
1165                 fLineTable[fLTNegStart++] = null;\r
1166             }\r
1167 \r
1168             int stopAt;\r
1169             if (fLTNegStart < fLTSize)\r
1170                 stopAt = fLineTable[fLTNegStart].getCharStart(newCurTextLen);\r
1171             else\r
1172                 stopAt = newCurTextLen;\r
1173 \r
1174             /*\r
1175                 Now, if exit conditions aren't met, create a new line.\r
1176             */\r
1177 \r
1178             if (seekOffsetAtEnd) {\r
1179                 if ((curLineStart >= newTextEnd) && (fLTNegStart == fLTSize)) {\r
1180                     // System.out.println("break 1");\r
1181                     break;\r
1182                 }\r
1183             }\r
1184             else {\r
1185                 if (curLineStart >= stopAt) {\r
1186                     // System.out.println("curLineStart: " + curLineStart + " >= stopAt: " + stopAt);\r
1187                     break;\r
1188                 }\r
1189                 else if (fLTNegStart==fLTSize && fPixHeight >= reqHeight) {\r
1190                     // System.out.println("break 3");\r
1191                     break;\r
1192                 }\r
1193             }\r
1194 \r
1195             if (fLTPosEnd + 1 == fLTNegStart)\r
1196                 expandLineTable();\r
1197 \r
1198             fLineTable[++fLTPosEnd] = null; // will be created by Renderer\r
1199         }\r
1200         //System.out.print("\n");\r
1201 \r
1202         if (newCurTextLen == 0) {\r
1203             fLineTable[0] = pseudoLineInfo(fLineTable[0], 0);\r
1204             fPixHeight = fLineTable[0].getHeight();\r
1205         }\r
1206         fFullPixHeight = fPixHeight;\r
1207 \r
1208         if (isParaBreakBefore(newCurTextLen)) {\r
1209             fFullPixHeight += lastCharHeight();\r
1210         }\r
1211 /*\r
1212         System.out.println("curLineStart: " + curLineStart +\r
1213             ", fLTPosEnd: " + fLTPosEnd +\r
1214             ", fLTNegStart: " + fLTNegStart +\r
1215             ", fLTSize: " + fLTSize);\r
1216 \r
1217         System.out.println("oldFullPixHeight: " + oldFullPixHeight + ", newFullPixHeight: " + fFullPixHeight);\r
1218 */\r
1219         int damageLength;\r
1220         if (fFullPixHeight == oldFullPixHeight) {\r
1221             damageLength = fLineTable[fLTPosEnd].getGraphicStart(fPixHeight)\r
1222                             + fLineTable[fLTPosEnd].getHeight() - damageStart;\r
1223         }\r
1224         else {\r
1225             damageLength = Math.max(fFullPixHeight, oldFullPixHeight);\r
1226         }\r
1227 \r
1228         return new Rectangle(fMinX, damageStart, fMaxX-fMinX, damageLength);\r
1229     }\r
1230 \r
1231 //    private void dumpLineTable()\r
1232 //    {\r
1233 //        int i;\r
1234 //\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
1240 //\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
1245 //    }\r
1246 \r
1247     public synchronized int minX() {\r
1248     \r
1249         return fMinX;\r
1250     }\r
1251     \r
1252     /**\r
1253     * Return the horizontal extent of the text, in pixels.\r
1254     * <p>\r
1255     * This returns an approximation based on the currently formatted text.\r
1256     */\r
1257     public synchronized int maxX()\r
1258     {\r
1259         checkTimeStamp();\r
1260 \r
1261         return fMaxX;\r
1262     }\r
1263 \r
1264     /**\r
1265     * Return the height of the last character in the text.\r
1266     *\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
1269     */\r
1270     private int lastCharHeight()\r
1271     {\r
1272         int charIndex = lastLineCharStop() - 1;\r
1273         AttributeMap st = fText.characterStyleAt(charIndex);\r
1274         DefaultCharacterMetric.Metric metric = fDefaultCharMetric.getMetricForStyle(st);\r
1275 \r
1276         int height = metric.getAscent();\r
1277         height += metric.getDescent();\r
1278         height += metric.getLeading();\r
1279 \r
1280         return height;\r
1281     }\r
1282 \r
1283     /**\r
1284      * Return true if the character at pos is a paragraph separator.\r
1285      */\r
1286     private boolean isParaBreakBefore(int pos)\r
1287     {\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
1291     }\r
1292 \r
1293     public synchronized int minY() {\r
1294     \r
1295         return 0;\r
1296     }\r
1297     \r
1298     /**\r
1299     * Return the vertical extent of the text, in pixels.\r
1300     * <p>\r
1301     * This returns an approximation based on the currently formatted text.\r
1302     */\r
1303     public synchronized int maxY()\r
1304     {\r
1305         checkTimeStamp();\r
1306 \r
1307         int numChars = lastLineCharStop();\r
1308 \r
1309         int pixHeight = fPixHeight;\r
1310         if (numChars == fLTCurTextLen && isParaBreakBefore(fLTCurTextLen)) {\r
1311             pixHeight += lastCharHeight();\r
1312         }\r
1313 \r
1314         if (numChars != 0)\r
1315             return pixHeight * fText.length() / numChars;\r
1316         else\r
1317             return 0;\r
1318     }\r
1319 \r
1320     /**\r
1321     * Return the actual pixel length of the text which has been formatted.\r
1322     */\r
1323     public synchronized int formattedHeight()\r
1324     {\r
1325         checkTimeStamp();\r
1326         return fPixHeight;\r
1327     }\r
1328 \r
1329     /**\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
1335      */\r
1336 \r
1337     private TextOffset lineDimToOffset(TextOffset result, int line, int lineX, int lineY, TextOffset anchor, boolean infiniteMode)\r
1338     {\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
1342 \r
1343         LayoutInfo lineInfo = fLineTable[line];\r
1344 \r
1345         result = lineInfo.pixelToOffset(fLTCurTextLen, result, fLineDim, lineX, lineY);\r
1346 \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
1351 \r
1352             result.setOffset(result.fOffset - 1, TextOffset.AFTER_OFFSET);\r
1353         }\r
1354 \r
1355         return result;\r
1356     }\r
1357 \r
1358     /**\r
1359     * Given a screen location p, return the offset of the character in the text nearest to p.\r
1360     */\r
1361     public synchronized TextOffset pointToTextOffset(TextOffset result, int px, int py, Point origin, TextOffset anchor, boolean infiniteMode)\r
1362     {\r
1363         checkTimeStamp();\r
1364         if (result == null)\r
1365             result = new TextOffset();\r
1366 \r
1367         int fillD;\r
1368 \r
1369         if (fHLine)\r
1370             fillD = py - origin.y;\r
1371         else\r
1372             fillD = px - origin.x;\r
1373 \r
1374         if (!fFillInc)\r
1375             fillD = -fillD;\r
1376 \r
1377         if (fillD < 0) {\r
1378             result.setOffset(0, TextOffset.AFTER_OFFSET);\r
1379             return result;\r
1380         }\r
1381 \r
1382         formatToHeight(fillD);\r
1383 \r
1384         if (fillD >= fPixHeight) {\r
1385             boolean bias = fLTCurTextLen==0? TextOffset.AFTER_OFFSET : TextOffset.BEFORE_OFFSET;\r
1386             result.setOffset(fLTCurTextLen, bias);\r
1387             return result;\r
1388         }\r
1389 \r
1390         int line = findLineAt(fillD); // always a valid line\r
1391         int gStart = lineGraphicStartInternal(line);\r
1392 \r
1393         int lineX, lineY;  // upper-left corner of line\r
1394         if (fHLine) {\r
1395             lineX = origin.x;\r
1396             lineY = fFillInc? origin.y + gStart : origin.y - (gStart + fLineTable[line].getHeight());\r
1397         }\r
1398         else {\r
1399             lineY = origin.y;\r
1400             lineX = fFillInc? origin.x + gStart : origin.x - (gStart + fLineTable[line].getHeight());\r
1401         }\r
1402 \r
1403         return lineDimToOffset(result, line, px - lineX, py - lineY, anchor, infiniteMode);\r
1404     }\r
1405 \r
1406     private boolean emptyParagraphAtEndOfText() {\r
1407 \r
1408         return fLTCurTextLen > 0 &&\r
1409                 isParagraphSeparator(fText.at(fLTCurTextLen-1));\r
1410     }\r
1411 \r
1412     /**\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
1416      */\r
1417     private boolean afterLastParagraph(TextOffset offset)\r
1418     {\r
1419         return offset.fOffset == fLTCurTextLen &&\r
1420             emptyParagraphAtEndOfText();\r
1421     }\r
1422 \r
1423     /**\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
1428     */\r
1429     public synchronized Rectangle getCaretRect(TextOffset offset, Point origin) {\r
1430 \r
1431         Rectangle r = new Rectangle();\r
1432         getCaretRect(r, offset, origin);\r
1433         return r;\r
1434     }\r
1435     \r
1436     private void getCaretRect(Rectangle r, TextOffset offset, Point origin) {\r
1437         \r
1438         checkTimeStamp();\r
1439         formatToOffset(offset);\r
1440 \r
1441         if (afterLastParagraph(offset)) {\r
1442             int pseudoLineHeight = lastCharHeight();\r
1443             if (fHLine) {\r
1444                 int lineY = fFillInc ? origin.y + fPixHeight : origin.y - fPixHeight - pseudoLineHeight;\r
1445                 r.setBounds(origin.x, lineY, 0, pseudoLineHeight);\r
1446             }\r
1447             else {\r
1448                 int lineX = fFillInc? origin.x + fPixHeight : origin.x - fPixHeight - pseudoLineHeight;\r
1449                 r.setBounds(lineX, origin.y, pseudoLineHeight, 0);\r
1450             }\r
1451             return;\r
1452         }\r
1453 \r
1454         int line = getValidLineContaining(offset);\r
1455 \r
1456         int gStart = lineGraphicStartInternal(line);\r
1457 \r
1458         int lineX, lineY;\r
1459 \r
1460         if (fHLine) {\r
1461             lineX = origin.x;\r
1462             if (fFillInc)\r
1463                 lineY = origin.y + gStart;\r
1464             else\r
1465                 lineY = origin.y - (gStart + fLineTable[line].getHeight());\r
1466         }\r
1467         else {\r
1468             lineY = origin.y;\r
1469             if (fFillInc)\r
1470                 lineX = origin.x + gStart;\r
1471             else\r
1472                 lineX = origin.x - (gStart + fLineTable[line].getHeight());\r
1473         }\r
1474 \r
1475         Rectangle bounds = fLineTable[line].caretBounds(fText, fLTCurTextLen, fLineDim, offset.fOffset, lineX, lineY);\r
1476 \r
1477         r.setBounds(bounds);\r
1478     }\r
1479 \r
1480     /**\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
1487     */\r
1488     public synchronized void drawCaret(Graphics g,\r
1489                                        TextOffset offset,\r
1490                                        Point origin,\r
1491                                        Color strongCaretColor,\r
1492                                        Color weakCaretColor) {\r
1493 \r
1494         checkTimeStamp();\r
1495         Graphics2D g2d = Graphics2DConversion.getGraphics2D(g);\r
1496         formatToOffset(offset);\r
1497 \r
1498         LayoutInfo line;\r
1499         int gStart;\r
1500         \r
1501         if (afterLastParagraph(offset)) {\r
1502             gStart = fPixHeight;\r
1503             line = pseudoLineInfo(null, offset.fOffset);\r
1504         }\r
1505         else {\r
1506             int lineIndex = getValidLineContaining(offset);\r
1507             gStart = lineGraphicStartInternal(lineIndex);\r
1508             line = fLineTable[lineIndex];\r
1509         }\r
1510         \r
1511         int lineX, lineY;\r
1512 \r
1513         if (fHLine) {\r
1514             lineX = origin.x;\r
1515             if (fFillInc)\r
1516                 lineY = origin.y + gStart;\r
1517             else\r
1518                 lineY = origin.y - (gStart + line.getHeight());\r
1519         }\r
1520         else {\r
1521             lineY = origin.y;\r
1522             if (fFillInc)\r
1523                 lineX = origin.x + gStart;\r
1524             else\r
1525                 lineX = origin.x - (gStart + line.getHeight());\r
1526         }\r
1527 \r
1528         line.renderCaret(fText, fLTCurTextLen, g2d, fLineDim, lineX, lineY,\r
1529                                 offset.fOffset, strongCaretColor, weakCaretColor);\r
1530     }\r
1531 \r
1532 /**\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
1538 */\r
1539     public synchronized Rectangle getBoundingRect(TextOffset offset1,\r
1540                                                   TextOffset offset2,\r
1541                                                   Point origin,\r
1542                                                   boolean tight) {\r
1543 \r
1544         Rectangle r = new Rectangle();\r
1545         getBoundingRect(r, offset1, offset2, origin, tight);\r
1546         return r;\r
1547     }\r
1548 \r
1549 /*\r
1550     Transform r from "text" coordinates to "screen" coordinates.\r
1551 */\r
1552 \r
1553     private void intlRect(Point origin, Rectangle r) {\r
1554 \r
1555         int lineOrig, fillOrig;\r
1556 \r
1557         if (fHLine) {\r
1558             lineOrig = origin.x;\r
1559             fillOrig = origin.y;\r
1560         }\r
1561         else {\r
1562             lineOrig = origin.y;\r
1563             fillOrig = origin.x;\r
1564         }\r
1565 \r
1566         if (fLineInc)\r
1567             r.x += lineOrig;\r
1568         else\r
1569             r.x = lineOrig - (r.x + r.width);\r
1570 \r
1571         if (fFillInc)\r
1572             r.y += fillOrig;\r
1573         else\r
1574             r.y = fillOrig - (r.y + r.height);\r
1575 \r
1576 \r
1577         if (!fHLine) {\r
1578             int t = r.x;\r
1579             r.x = r.y;\r
1580             r.y = t;\r
1581             t = r.width;\r
1582             r.width = r.height;\r
1583             r.height = t;\r
1584         }\r
1585     }\r
1586 \r
1587 \r
1588     public synchronized void getBoundingRect(Rectangle r, \r
1589                                              TextOffset offset1,\r
1590                                              TextOffset offset2,\r
1591                                              Point origin,\r
1592                                              boolean tight)\r
1593     {\r
1594         checkTimeStamp();\r
1595         if (offset1.equals(offset2)) {\r
1596             getCaretRect(r, offset1, origin);\r
1597             return;\r
1598         }\r
1599         if (offset1.greaterThan(offset2)) {\r
1600             TextOffset t; t = offset1; offset1 = offset2; offset2 = t;\r
1601         }\r
1602 \r
1603         formatToOffset(offset2);\r
1604 \r
1605         int line = getValidLineContaining(offset1);\r
1606         r.y = lineGraphicStartInternal(line);\r
1607         \r
1608         int gLimit;\r
1609         boolean sameLine = false;\r
1610                 \r
1611         if (afterLastParagraph(offset2))\r
1612             gLimit = fPixHeight + lastCharHeight();\r
1613         else {\r
1614             int line2 = getValidLineContaining(offset2);\r
1615             gLimit = lineGraphicLimitInternal(line2);\r
1616             sameLine = (line == line2);\r
1617         }\r
1618 \r
1619         r.height = gLimit - r.y;\r
1620 \r
1621         if (sameLine && tight==TIGHT) {\r
1622             Rectangle rt = new Rectangle();\r
1623             getCaretRect(rt, offset1, origin);\r
1624             r.setBounds(rt);\r
1625             if (!offset1.equals(offset2)) {\r
1626                 getCaretRect(rt, offset2, origin);\r
1627                 r.add(rt);\r
1628             }\r
1629         }\r
1630         else {\r
1631             r.x = fMinX;\r
1632             r.width = fMaxX - fMinX;\r
1633             intlRect(origin, r);\r
1634         }\r
1635 \r
1636         // System.out.print("gbr: " + r.x + ", " + r.y + ", " + r.width + ", " + r.height);\r
1637 \r
1638         // System.out.println(" --> " + r.x + ", " + r.y + ", " + r.width + ", " + r.height);\r
1639     }\r
1640 \r
1641     /**\r
1642     * Compute the offset resulting from moving from a previous offset in direction dir.\r
1643     * For arrow keys.\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
1648     */\r
1649     public synchronized TextOffset findInsertionOffset(TextOffset result, TextOffset prevOffset, short dir)\r
1650     {\r
1651         return findNewInsertionOffset(result, prevOffset, prevOffset, dir);\r
1652     }\r
1653 \r
1654     /**\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
1657      */\r
1658     private short remapArrowKey(short dir) {\r
1659 \r
1660         if (!fLineInc) {\r
1661             if (dir == eLeft)\r
1662                 dir = eRight;\r
1663             else if (dir == eRight)\r
1664                 dir = eLeft;\r
1665         }\r
1666 \r
1667         if (!fFillInc) {\r
1668             if (dir == eUp)\r
1669                 dir = eDown;\r
1670             else if (dir == eDown)\r
1671                 dir = eUp;\r
1672         }\r
1673 \r
1674         if (!fHLine) {\r
1675             if (dir == eLeft)\r
1676                 dir = eUp;\r
1677             else if (dir == eRight)\r
1678                 dir = eDown;\r
1679             else if (dir == eUp)\r
1680                 dir = eLeft;\r
1681             else if (dir == eDown)\r
1682                 dir = eRight;\r
1683         }\r
1684 \r
1685         return dir;\r
1686     }\r
1687 \r
1688     /**\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
1696     */\r
1697     public synchronized TextOffset findNewInsertionOffset(TextOffset result, TextOffset origOffset, TextOffset prevOffset, short dir)\r
1698     {\r
1699         checkTimeStamp();\r
1700         if (result == null)\r
1701             result = new TextOffset();\r
1702 \r
1703         dir = remapArrowKey(dir);\r
1704 \r
1705         // assume that text at origOffset and prevOffset has already been formatted\r
1706 \r
1707         if (dir == eLeft || dir == eRight) {\r
1708             formatToOffset(prevOffset);\r
1709             int line = getValidLineContaining(prevOffset);\r
1710 \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
1715             }\r
1716             else if (result.fOffset >= fLTCurTextLen) {\r
1717                 result.setOffset(fLTCurTextLen, TextOffset.BEFORE_OFFSET);\r
1718             }\r
1719         }\r
1720         else {\r
1721             int distOnLine;\r
1722 \r
1723             if (afterLastParagraph(origOffset))\r
1724                 distOnLine = 0;\r
1725             else {\r
1726                 int line = getValidLineContaining(origOffset);\r
1727 \r
1728                 distOnLine = fLineTable[line].strongCaretBaselinePosition(fLTCurTextLen, fLineDim, origOffset.fOffset);\r
1729             }\r
1730 \r
1731             // get prevOffset's line\r
1732             int line;\r
1733             if (afterLastParagraph(prevOffset))\r
1734                 line = lastLine() + 1;\r
1735             else {\r
1736                 line = getLineContaining(prevOffset);\r
1737 \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
1743                 }\r
1744 \r
1745                 if (line == kBeforeFirstLine)\r
1746                     line = 0;\r
1747                 else if (line == kAfterLastLine)\r
1748                     line = lastLine();\r
1749             }\r
1750 \r
1751             if (dir == eUp)\r
1752                 line--;\r
1753             else if (dir == eDown)\r
1754                 line++;\r
1755             else\r
1756                 throw new IllegalArgumentException("Debug: Illegal direction parameter in findNewInsertionOffset");\r
1757 \r
1758             if (line < 0) {\r
1759                 //result.setOffset(0, TextOffset.AFTER_OFFSET);\r
1760                 result.assign(prevOffset);\r
1761             }\r
1762             else if (line > lastLine()) {\r
1763                 result.setOffset(fLTCurTextLen, TextOffset.BEFORE_OFFSET);\r
1764             }\r
1765             else {\r
1766                 if (fLineTable[line] == null)\r
1767                     line = (dir == eUp)? fLTPosEnd : fLTNegStart;\r
1768 \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
1772             }\r
1773         }\r
1774 \r
1775         // System.out.println("fnio prev: " + prevOffset + ", new: " + result);\r
1776 \r
1777         return result;\r
1778     }\r
1779 \r
1780     public synchronized void stopBackgroundFormatting()\r
1781     {\r
1782         checkTimeStamp();\r
1783         fBgFormatAllowed = false;\r
1784     }\r
1785 \r
1786     private synchronized void enableBGFormat()\r
1787     {\r
1788         try {\r
1789             fBgFormatAllowed = true;\r
1790             notify();\r
1791         }\r
1792         catch (IllegalMonitorStateException e) {\r
1793         }\r
1794     }\r
1795 \r
1796     private int lineIndexToNumber(int lineIndex) {\r
1797 \r
1798         if (lineIndex <= fLTPosEnd) {\r
1799             return lineIndex;\r
1800         }\r
1801         else {\r
1802             return lineIndex - (fLTNegStart-fLTPosEnd-1);\r
1803         }\r
1804     }\r
1805 \r
1806     private int lineNumberToIndex(int lineNumber) {\r
1807 \r
1808         if (lineNumber <= fLTPosEnd) {\r
1809             return lineNumber;\r
1810         }\r
1811         else {\r
1812             return lineNumber + (fLTNegStart-fLTPosEnd-1);\r
1813         }\r
1814     }\r
1815 \r
1816     private void formatToLineNumber(int lineNumber) {\r
1817 \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
1823         }\r
1824     }\r
1825 \r
1826     private static final boolean STRICT = true;\r
1827     private static final boolean LENIENT = false;\r
1828 \r
1829     /**\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
1836      */\r
1837     private void validateLineNumber(int lineNumber, boolean strict) {\r
1838 \r
1839         formatToLineNumber(lineNumber);\r
1840 \r
1841         int maxNumber = lineIndexToNumber(fLTSize);\r
1842         if (strict == STRICT) {\r
1843             maxNumber -= 1;\r
1844         }\r
1845 \r
1846         if (lineNumber > maxNumber+1 ||\r
1847                 (lineNumber == maxNumber+1 && !emptyParagraphAtEndOfText())) {\r
1848             throw new IllegalArgumentException("Invalid line number: " + lineNumber);\r
1849         }\r
1850     }\r
1851 \r
1852     public synchronized int getLineCount() {\r
1853 \r
1854         // format all text:\r
1855         formatToHeight(Integer.MAX_VALUE);\r
1856 \r
1857         int lineCount = lineIndexToNumber(fLTSize);\r
1858 \r
1859         if (emptyParagraphAtEndOfText()) {\r
1860             lineCount += 1;\r
1861         }\r
1862 \r
1863         return lineCount;\r
1864     }\r
1865     \r
1866     public synchronized int lineContaining(int charIndex) {\r
1867         \r
1868         formatToOffset(charIndex, TextOffset.AFTER_OFFSET);\r
1869         \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
1874         }\r
1875         \r
1876         return lineContaining(charIndex, placement);\r
1877     }\r
1878 \r
1879     public synchronized int lineContaining(TextOffset offset) {\r
1880 \r
1881         formatToOffset(offset);\r
1882 \r
1883         if (afterLastParagraph(offset)) {\r
1884             return lineIndexToNumber(fLTSize);\r
1885         }\r
1886         \r
1887         return lineContaining(offset.fOffset, offset.fPlacement);\r
1888     }\r
1889     \r
1890     private int lineContaining(int off, boolean placement) {\r
1891         \r
1892         int line = off==0? 0 : getLineContaining(off, placement);\r
1893         \r
1894         if (line == kAfterLastLine) {\r
1895             line = fLTSize;\r
1896         }\r
1897         else if (line == kBeforeFirstLine) {\r
1898             throw new Error("lineContaining got invalid result from getLineContaining().");\r
1899         }\r
1900 \r
1901         return lineIndexToNumber(line);\r
1902     }\r
1903 \r
1904     public synchronized int lineRangeLow(int lineNumber) {\r
1905 \r
1906         validateLineNumber(lineNumber, STRICT);\r
1907         int index = lineNumberToIndex(lineNumber);\r
1908 \r
1909         if (index == fLTSize) {\r
1910             if (emptyParagraphAtEndOfText()) {\r
1911                 return lastLineCharStop();\r
1912             }\r
1913         }\r
1914 \r
1915         if (index >= fLTSize) {\r
1916             throw new IllegalArgumentException("lineNumber is invalid.");\r
1917         }\r
1918         else {\r
1919             return lineCharStartInternal(index);\r
1920         }\r
1921     }\r
1922 \r
1923     public synchronized int lineRangeLimit(int lineNumber) {\r
1924 \r
1925         validateLineNumber(lineNumber, STRICT);\r
1926         int index = lineNumberToIndex(lineNumber);\r
1927 \r
1928         if (index == fLTSize) {\r
1929             if (emptyParagraphAtEndOfText()) {\r
1930                 return lastLineCharStop();\r
1931             }\r
1932         }\r
1933 \r
1934         if (index >= fLTSize) {\r
1935             throw new IllegalArgumentException("lineNumber is invalid.");\r
1936         }\r
1937         else {\r
1938             return lineCharLimitInternal(index);\r
1939         }\r
1940     }\r
1941 \r
1942     /**\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
1945      */\r
1946     public synchronized int lineAtHeight(int height) {\r
1947 \r
1948         if (height >= fPixHeight) {\r
1949 \r
1950             int line = getLineCount();\r
1951             if (height < fFullPixHeight) {\r
1952                 line -= 1;\r
1953             }\r
1954             return line;\r
1955         }\r
1956         else if (height < 0) {\r
1957             return -1;\r
1958         }\r
1959         else {\r
1960             return lineIndexToNumber(findLineAt(height));\r
1961         }\r
1962     }\r
1963 \r
1964     public synchronized int lineGraphicStart(int lineNumber) {\r
1965 \r
1966         checkTimeStamp();\r
1967         validateLineNumber(lineNumber, LENIENT);\r
1968 \r
1969         int index = lineNumberToIndex(lineNumber);\r
1970 \r
1971         if (index < fLTSize) {\r
1972             return lineGraphicStartInternal(index);\r
1973         }\r
1974         else {\r
1975             if (index == fLTSize+1) {\r
1976                 return fFullPixHeight;\r
1977             }\r
1978             else {\r
1979                 return fPixHeight;\r
1980             }\r
1981         }\r
1982     }\r
1983 \r
1984     public synchronized boolean lineIsLeftToRight(int lineNumber) {\r
1985         \r
1986         validateLineNumber(lineNumber, STRICT);\r
1987         \r
1988         int index = lineNumberToIndex(lineNumber);\r
1989         \r
1990         if (index < fLTSize) {\r
1991             return fLineTable[index].isLeftToRight();\r
1992         }\r
1993         else {\r
1994             AttributeMap st = fText.paragraphStyleAt(fLTCurTextLen);\r
1995             return !TextAttribute.RUN_DIRECTION_RTL.equals(st.get(TextAttribute.RUN_DIRECTION));\r
1996         }\r
1997     }\r
1998 \r
1999     /**\r
2000     * Number of pixels by which to advance formatting in the background.\r
2001     */\r
2002     private static final int kPixIncrement = 100;\r
2003 \r
2004     /**\r
2005     * Time to sleep between background formatting operations.\r
2006     */\r
2007     private static final int kInterval = 100;\r
2008 \r
2009     /**\r
2010     * Perform periodic background formatting.\r
2011     */\r
2012     public void run()\r
2013     {\r
2014         while (true) {\r
2015             synchronized (this) {\r
2016                 while(!fBgFormatAllowed) {\r
2017                     try {\r
2018                         wait();\r
2019                     } catch(InterruptedException e) {\r
2020                     }\r
2021                 }\r
2022 \r
2023                 checkTimeStamp();                \r
2024                 formatToHeight(fPixHeight + kPixIncrement);\r
2025 \r
2026                 if (lastLineCharStop() == fLTCurTextLen) {\r
2027                     stopBackgroundFormatting();\r
2028                 }\r
2029             }\r
2030 \r
2031             try {\r
2032                 Thread.sleep(kInterval);\r
2033             }\r
2034             catch(InterruptedException e) {\r
2035             }\r
2036         }\r
2037     }\r
2038 \r
2039     private ParagraphRenderer getRendererFor(AttributeMap s) {\r
2040     \r
2041         // Note:  eventually we could let clients put their own renderers\r
2042         // on the text.\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
2047         }\r
2048         return renderer;\r
2049     }\r
2050 \r
2051 }\r