KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > java > awt > font > TextLine


1 /*
2  * @(#)TextLine.java 1.52 03/12/19
3  *
4  * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7
8 /*
9  * (C) Copyright IBM Corp. 1998-2003, All Rights Reserved
10  *
11  */

12
13 package java.awt.font;
14
15 import java.awt.Font JavaDoc;
16 import java.awt.Graphics2D JavaDoc;
17 import java.awt.Shape JavaDoc;
18
19 import java.awt.geom.Rectangle2D JavaDoc;
20 import java.awt.geom.AffineTransform JavaDoc;
21 import java.awt.geom.GeneralPath JavaDoc;
22
23 import java.awt.im.InputMethodHighlight JavaDoc;
24
25 import java.text.CharacterIterator JavaDoc;
26 import java.text.AttributedCharacterIterator JavaDoc;
27 import java.text.Annotation JavaDoc;
28 import java.text.Bidi JavaDoc;
29
30 import java.util.Map JavaDoc;
31 import java.util.Hashtable JavaDoc;
32
33 import sun.font.BidiUtils;
34 import sun.font.CoreMetrics;
35 import sun.font.Decoration;
36 import sun.font.FontLineMetrics;
37 import sun.font.FontResolver;
38 import sun.font.GraphicComponent;
39 import sun.font.TextLabelFactory;
40 import sun.font.TextLineComponent;
41 import sun.text.CodePointIterator;
42
43 final class TextLine {
44
45     static final class TextLineMetrics {
46         public final float ascent;
47         public final float descent;
48         public final float leading;
49         public final float advance;
50
51         public TextLineMetrics(float ascent,
52                            float descent,
53                            float leading,
54                            float advance) {
55             this.ascent = ascent;
56             this.descent = descent;
57             this.leading = leading;
58             this.advance = advance;
59         }
60     }
61
62     private TextLineComponent[] fComponents;
63     private float[] fBaselineOffsets;
64     private int[] fComponentVisualOrder; // if null, ltr
65
private float[] locs; // x,y pairs for components in visual order
66
private char[] fChars;
67     private int fCharsStart;
68     private int fCharsLimit;
69     private int[] fCharVisualOrder; // if null, ltr
70
private int[] fCharLogicalOrder; // if null, ltr
71
private byte[] fCharLevels; // if null, 0
72
private boolean fIsDirectionLTR;
73
74     private TextLineMetrics fMetrics = null; // built on demand in getMetrics
75

76     public TextLine(TextLineComponent[] components,
77                     float[] baselineOffsets,
78                     char[] chars,
79                     int charsStart,
80                     int charsLimit,
81                     int[] charLogicalOrder,
82                     byte[] charLevels,
83                     boolean isDirectionLTR) {
84
85         int[] componentVisualOrder = computeComponentOrder(components,
86                                                            charLogicalOrder);
87
88         fComponents = components;
89         fBaselineOffsets = baselineOffsets;
90         fComponentVisualOrder = componentVisualOrder;
91         fChars = chars;
92         fCharsStart = charsStart;
93         fCharsLimit = charsLimit;
94         fCharLogicalOrder = charLogicalOrder;
95         fCharLevels = charLevels;
96         fIsDirectionLTR = isDirectionLTR;
97         checkCtorArgs();
98
99         init();
100     }
101
102     private void checkCtorArgs() {
103
104         int checkCharCount = 0;
105         for (int i=0; i < fComponents.length; i++) {
106             checkCharCount += fComponents[i].getNumCharacters();
107         }
108
109         if (checkCharCount != this.characterCount()) {
110             throw new IllegalArgumentException JavaDoc("Invalid TextLine! " +
111                                 "char count is different from " +
112                                 "sum of char counts of components.");
113         }
114     }
115
116     private void init() {
117
118     // first, we need to check for graphic components on the TOP or BOTTOM baselines. So
119
// we perform the work that used to be in getMetrics here.
120

121     float ascent = 0;
122     float descent = 0;
123     float leading = 0;
124     float advance = 0;
125
126     // ascent + descent must not be less than this value
127
float maxGraphicHeight = 0;
128     float maxGraphicHeightWithLeading = 0;
129
130     // walk through EGA's
131
TextLineComponent tlc;
132     boolean fitTopAndBottomGraphics = false;
133
134     for (int i = 0; i < fComponents.length; i++) {
135         tlc = fComponents[i];
136
137         CoreMetrics cm = tlc.getCoreMetrics();
138         byte baseline = (byte)cm.baselineIndex;
139
140         if (baseline >= 0) {
141         float baselineOffset = fBaselineOffsets[baseline];
142
143         ascent = Math.max(ascent, -baselineOffset + cm.ascent);
144
145         float gd = baselineOffset + cm.descent;
146         descent = Math.max(descent, gd);
147
148         leading = Math.max(leading, gd + cm.leading);
149         }
150         else {
151         fitTopAndBottomGraphics = true;
152         float graphicHeight = cm.ascent + cm.descent;
153         float graphicHeightWithLeading = graphicHeight + cm.leading;
154         maxGraphicHeight = Math.max(maxGraphicHeight, graphicHeight);
155         maxGraphicHeightWithLeading = Math.max(maxGraphicHeightWithLeading,
156                                graphicHeightWithLeading);
157         }
158     }
159
160     if (fitTopAndBottomGraphics) {
161         if (maxGraphicHeight > ascent + descent) {
162         descent = maxGraphicHeight - ascent;
163         }
164         if (maxGraphicHeightWithLeading > ascent + leading) {
165         leading = maxGraphicHeightWithLeading - ascent;
166         }
167     }
168
169     leading -= descent;
170
171     // we now know enough to compute the locs, but we need the final loc
172
// for the advance before we can create the metrics object
173

174     if (fitTopAndBottomGraphics) {
175         // we have top or bottom baselines, so expand the baselines array
176
// full offsets are needed by CoreMetrics.effectiveBaselineOffset
177
fBaselineOffsets = new float[] {
178         fBaselineOffsets[0],
179         fBaselineOffsets[1],
180         fBaselineOffsets[2],
181         descent,
182         -ascent
183         };
184     }
185         
186         float x = 0;
187     float y = 0;
188         CoreMetrics pcm = null;
189
190         locs = new float[fComponents.length * 2 + 2];
191
192         for (int i = 0, n = 0; i < fComponents.length; ++i, n += 2) {
193             int vi = fComponentVisualOrder == null ? i : fComponentVisualOrder[i];
194             
195             tlc = fComponents[vi];
196             CoreMetrics cm = tlc.getCoreMetrics();
197
198             if ((pcm != null) &&
199                 (pcm.italicAngle != 0 || cm.italicAngle != 0) && // adjust because of italics
200
(pcm.italicAngle != cm.italicAngle ||
201          pcm.baselineIndex != cm.baselineIndex ||
202          pcm.ssOffset != cm.ssOffset)) {
203
204                         // 1) compute the area of overlap - min effective ascent and min effective descent
205
// 2) compute the x positions along italic angle of ascent and descent for left and right
206
// 3) compute maximum left - right, adjust right position by this value
207
// this is a crude form of kerning between textcomponents
208

209                         // 1)
210
float pb = pcm.effectiveBaselineOffset(fBaselineOffsets);
211                         float pa = pb - pcm.ascent;
212                         float pd = pb + pcm.descent;
213                         pb += pcm.ssOffset;
214                         
215             float cb = cm.effectiveBaselineOffset(fBaselineOffsets);
216                         float ca = cb - cm.ascent;
217                         float cd = cb + cm.descent;
218                         cb += cm.ssOffset;
219
220                         float a = Math.max(pa, ca);
221                         float d = Math.min(pd, cd);
222
223                         // 2)
224
float pax = pcm.italicAngle * (pb - a);
225                         float pdx = pcm.italicAngle * (pb - d);
226
227                         float cax = cm.italicAngle * (cb - a);
228                         float cdx = cm.italicAngle * (cb - d);
229
230                         // 3)
231
float dax = pax - cax;
232                         float ddx = pdx - cdx;
233                         float dx = Math.max(dax, ddx);
234
235                         x += dx;
236             y = cb;
237             } else {
238         // no italic adjustment for x, but still need to compute y
239
y = cm.effectiveBaselineOffset(fBaselineOffsets) + cm.ssOffset;
240         }
241
242             locs[n] = x;
243             locs[n+1] = y;
244
245             x += tlc.getAdvance();
246             pcm = cm;
247         }
248
249     // do we want italic padding at the right of the line?
250
if (pcm.italicAngle != 0) {
251         float pb = pcm.effectiveBaselineOffset(fBaselineOffsets);
252         float pa = pb - pcm.ascent;
253         float pd = pb + pcm.descent;
254         pb += pcm.ssOffset;
255
256             float d;
257         if (pcm.italicAngle > 0) {
258         d = pb + pcm.ascent;
259         } else {
260         d = pb - pcm.descent;
261         }
262         d *= pcm.italicAngle;
263
264         x += d;
265     }
266     locs[locs.length - 2] = x;
267     // locs[locs.length - 1] = 0; // final offset is always back on baseline
268

269     // ok, build fMetrics since we have the final advance
270
advance = x;
271     fMetrics = new TextLineMetrics(ascent, descent, leading, advance);
272     }
273
274     private abstract static class Function {
275
276         abstract float computeFunction(TextLine JavaDoc line,
277                                        int componentIndex,
278                                        int indexInArray);
279     }
280
281     private static Function fgPosAdvF = new Function() {
282         float computeFunction(TextLine JavaDoc line,
283                               int componentIndex,
284                               int indexInArray) {
285
286             TextLineComponent tlc = line.fComponents[componentIndex];
287             int vi = line.fComponentVisualOrder == null
288                 ? componentIndex
289                 : line.fComponentVisualOrder[componentIndex];
290             return line.locs[vi * 2] + tlc.getCharX(indexInArray) + tlc.getCharAdvance(indexInArray);
291         }
292     };
293
294     private static Function fgAdvanceF = new Function() {
295
296         float computeFunction(TextLine JavaDoc line,
297                               int componentIndex,
298                               int indexInArray) {
299
300             TextLineComponent tlc = line.fComponents[componentIndex];
301             return tlc.getCharAdvance(indexInArray);
302         }
303     };
304
305     private static Function fgXPositionF = new Function() {
306
307         float computeFunction(TextLine JavaDoc line,
308                               int componentIndex,
309                               int indexInArray) {
310
311             int vi = line.fComponentVisualOrder == null
312                 ? componentIndex
313                 : line.fComponentVisualOrder[componentIndex];
314             TextLineComponent tlc = line.fComponents[componentIndex];
315             return line.locs[vi * 2] + tlc.getCharX(indexInArray);
316         }
317     };
318
319     private static Function fgYPositionF = new Function() {
320
321         float computeFunction(TextLine JavaDoc line,
322                               int componentIndex,
323                               int indexInArray) {
324
325             TextLineComponent tlc = line.fComponents[componentIndex];
326             float charPos = tlc.getCharY(indexInArray);
327
328             // charPos is relative to the component - adjust for
329
// baseline
330

331             return charPos + line.getComponentShift(componentIndex);
332         }
333     };
334
335     public int characterCount() {
336
337         return fCharsLimit - fCharsStart;
338     }
339
340     public boolean isDirectionLTR() {
341
342         return fIsDirectionLTR;
343     }
344
345     public TextLineMetrics getMetrics() {
346     return fMetrics;
347     }
348
349     public int visualToLogical(int visualIndex) {
350
351         if (fCharLogicalOrder == null) {
352         return visualIndex;
353     }
354
355     if (fCharVisualOrder == null) {
356         fCharVisualOrder = BidiUtils.createInverseMap(fCharLogicalOrder);
357     }
358  
359         return fCharVisualOrder[visualIndex];
360     }
361
362     public int logicalToVisual(int logicalIndex) {
363
364         return (fCharLogicalOrder == null)?
365             logicalIndex : fCharLogicalOrder[logicalIndex];
366     }
367
368     public byte getCharLevel(int logicalIndex) {
369
370         return fCharLevels==null? 0 : fCharLevels[logicalIndex];
371     }
372
373     public boolean isCharLTR(int logicalIndex) {
374
375         return (getCharLevel(logicalIndex) & 0x1) == 0;
376     }
377
378     public int getCharType(int logicalIndex) {
379
380         return Character.getType(fChars[logicalIndex + fCharsStart]);
381     }
382
383     public boolean isCharSpace(int logicalIndex) {
384
385         return Character.isSpaceChar(fChars[logicalIndex + fCharsStart]);
386     }
387
388     public boolean isCharWhitespace(int logicalIndex) {
389
390         return Character.isWhitespace(fChars[logicalIndex + fCharsStart]);
391     }
392
393     public float getCharAngle(int logicalIndex) {
394
395         return getCoreMetricsAt(logicalIndex).italicAngle;
396     }
397
398     public CoreMetrics getCoreMetricsAt(int logicalIndex) {
399
400         if (logicalIndex < 0) {
401             throw new IllegalArgumentException JavaDoc("Negative logicalIndex.");
402         }
403
404         if (logicalIndex > fCharsLimit - fCharsStart) {
405             throw new IllegalArgumentException JavaDoc("logicalIndex too large.");
406         }
407
408         int currentTlc = 0;
409         int tlcStart = 0;
410         int tlcLimit = 0;
411
412         do {
413             tlcLimit += fComponents[currentTlc].getNumCharacters();
414             if (tlcLimit > logicalIndex) {
415                 break;
416             }
417             ++currentTlc;
418             tlcStart = tlcLimit;
419         } while(currentTlc < fComponents.length);
420
421         return fComponents[currentTlc].getCoreMetrics();
422     }
423
424     public float getCharAscent(int logicalIndex) {
425
426         return getCoreMetricsAt(logicalIndex).ascent;
427     }
428
429     public float getCharDescent(int logicalIndex) {
430
431         return getCoreMetricsAt(logicalIndex).descent;
432     }
433
434     public float getCharShift(int logicalIndex) {
435
436         return getCoreMetricsAt(logicalIndex).ssOffset;
437     }
438
439     private float applyFunctionAtIndex(int logicalIndex, Function f) {
440
441         if (logicalIndex < 0) {
442             throw new IllegalArgumentException JavaDoc("Negative logicalIndex.");
443         }
444
445         int tlcStart = 0;
446
447         for(int i=0; i < fComponents.length; i++) {
448
449             int tlcLimit = tlcStart + fComponents[i].getNumCharacters();
450             if (tlcLimit > logicalIndex) {
451                 return f.computeFunction(this, i, logicalIndex - tlcStart);
452             }
453             else {
454                 tlcStart = tlcLimit;
455             }
456         }
457
458         throw new IllegalArgumentException JavaDoc("logicalIndex too large.");
459     }
460
461     public float getCharAdvance(int logicalIndex) {
462
463         return applyFunctionAtIndex(logicalIndex, fgAdvanceF);
464     }
465
466     public float getCharXPosition(int logicalIndex) {
467
468         return applyFunctionAtIndex(logicalIndex, fgXPositionF);
469     }
470
471     public float getCharYPosition(int logicalIndex) {
472
473         return applyFunctionAtIndex(logicalIndex, fgYPositionF);
474     }
475
476     public float getCharLinePosition(int logicalIndex) {
477
478         return getCharXPosition(logicalIndex);
479     }
480
481     public float getCharLinePosition(int logicalIndex, boolean leading) {
482         Function f = isCharLTR(logicalIndex) == leading ? fgXPositionF : fgPosAdvF;
483         return applyFunctionAtIndex(logicalIndex, f);
484     }
485
486     public boolean caretAtOffsetIsValid(int offset) {
487
488         if (offset < 0) {
489             throw new IllegalArgumentException JavaDoc("Negative offset.");
490         }
491
492         int tlcStart = 0;
493
494         for(int i=0; i < fComponents.length; i++) {
495
496             int tlcLimit = tlcStart + fComponents[i].getNumCharacters();
497             if (tlcLimit > offset) {
498                 return fComponents[i].caretAtOffsetIsValid(offset-tlcStart);
499             }
500             else {
501                 tlcStart = tlcLimit;
502             }
503         }
504
505         throw new IllegalArgumentException JavaDoc("logicalIndex too large.");
506     }
507
508     public Rectangle2D JavaDoc getCharBounds(int logicalIndex) {
509
510         if (logicalIndex < 0) {
511             throw new IllegalArgumentException JavaDoc("Negative logicalIndex.");
512         }
513
514         int tlcStart = 0;
515
516         for (int i=0; i < fComponents.length; i++) {
517
518             int tlcLimit = tlcStart + fComponents[i].getNumCharacters();
519             if (tlcLimit > logicalIndex) {
520
521                 TextLineComponent tlc = fComponents[i];
522                 int indexInTlc = logicalIndex - tlcStart;
523                 Rectangle2D JavaDoc chBounds = tlc.getCharVisualBounds(indexInTlc);
524
525                 int vi = fComponentVisualOrder == null ? i : fComponentVisualOrder[i];
526                 chBounds.setRect(chBounds.getX() + locs[vi * 2],
527                                  chBounds.getY() + locs[vi * 2 + 1],
528                                  chBounds.getWidth(),
529                                  chBounds.getHeight());
530                 return chBounds;
531             }
532             else {
533                 tlcStart = tlcLimit;
534             }
535         }
536
537         throw new IllegalArgumentException JavaDoc("logicalIndex too large.");
538     }
539
540     private float getComponentShift(int index) {
541     CoreMetrics cm = fComponents[index].getCoreMetrics();
542     return cm.effectiveBaselineOffset(fBaselineOffsets);
543     }
544
545     public void draw(Graphics2D JavaDoc g2, float x, float y) {
546
547         for (int i = 0, n = 0; i < fComponents.length; i++, n += 2) {
548             int vi = fComponentVisualOrder==null? i : fComponentVisualOrder[i];
549             TextLineComponent tlc = fComponents[vi];
550             tlc.draw(g2, locs[n] + x, locs[n+1] + y);
551         }
552     }
553
554     /** return the union of the visual bounds of all the components */
555
556     public Rectangle2D JavaDoc getBounds() {
557
558         float left = Float.MAX_VALUE, right = -Float.MAX_VALUE;
559         float top = Float.MAX_VALUE, bottom = -Float.MAX_VALUE;
560
561         for (int i=0, n = 0; i < fComponents.length; i++, n += 2) {
562             int vi = fComponentVisualOrder==null? i : fComponentVisualOrder[i];
563             TextLineComponent tlc = fComponents[vi];
564
565             Rectangle2D JavaDoc tlcBounds = tlc.getVisualBounds();
566             float x = locs[n];
567             float y = locs[n+1];
568
569             left = Math.min(left, x + (float)tlcBounds.getX());
570             right = Math.max(right, x + (float)tlcBounds.getMaxX());
571
572             top = Math.min(top, y + (float)tlcBounds.getY());
573             bottom = Math.max(bottom, y + (float)tlcBounds.getMaxY());
574         }
575
576         return new Rectangle2D.Float JavaDoc(left, top, right-left, bottom-top);
577     }
578
579     public Rectangle2D JavaDoc getItalicBounds() {
580         
581         float left = Float.MAX_VALUE, right = -Float.MAX_VALUE;
582         float top = Float.MAX_VALUE, bottom = -Float.MAX_VALUE;
583
584         for (int i=0, n = 0; i < fComponents.length; i++, n += 2) {
585             int vi = fComponentVisualOrder==null? i : fComponentVisualOrder[i];
586             TextLineComponent tlc = fComponents[vi];
587
588             Rectangle2D JavaDoc tlcBounds = tlc.getItalicBounds();
589             float x = locs[n];
590             float y = locs[n+1];
591
592             left = Math.min(left, x + (float)tlcBounds.getX());
593             right = Math.max(right, x + (float)tlcBounds.getMaxX());
594
595             top = Math.min(top, y + (float)tlcBounds.getY());
596             bottom = Math.max(bottom, y + (float)tlcBounds.getMaxY());
597         }
598
599         return new Rectangle2D.Float JavaDoc(left, top, right-left, bottom-top);
600     }
601
602     public Shape JavaDoc getOutline(AffineTransform JavaDoc tx) {
603
604         GeneralPath JavaDoc dstShape = new GeneralPath JavaDoc(GeneralPath.WIND_NON_ZERO);
605
606         for (int i=0, n = 0; i < fComponents.length; i++, n += 2) {
607             int vi = fComponentVisualOrder==null? i : fComponentVisualOrder[i];
608             TextLineComponent tlc = fComponents[vi];
609
610             dstShape.append(tlc.getOutline(locs[n], locs[n+1]), false);
611         }
612
613         if (tx != null) {
614             dstShape.transform(tx);
615         }
616         return dstShape;
617     }
618
619     public int hashCode() {
620         return (fComponents.length << 16) ^
621                     (fComponents[0].hashCode() << 3) ^ (fCharsLimit-fCharsStart);
622     }
623
624     public String JavaDoc toString() {
625         StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
626
627         for (int i = 0; i < fComponents.length; i++) {
628             buf.append(fComponents[i]);
629         }
630
631         return buf.toString();
632     }
633
634     /**
635      * Create a TextLine from the text. The Font must be able to
636      * display all of the text.
637      * attributes==null is equivalent to using an empty Map for
638      * attributes
639      */

640     public static TextLine JavaDoc fastCreateTextLine(FontRenderContext JavaDoc frc,
641                                               char[] chars,
642                                               Font JavaDoc font,
643                                               CoreMetrics lm,
644                                               Map JavaDoc attributes) {
645
646         boolean isDirectionLTR = true;
647         byte[] levels = null;
648         int[] charsLtoV = null;
649         Bidi JavaDoc bidi = null;
650         int characterCount = chars.length;
651
652         boolean requiresBidi = false;
653         boolean directionKnown = false;
654         byte[] embs = null;
655         if (attributes != null) {
656           try {
657             Boolean JavaDoc runDirection = (Boolean JavaDoc)attributes.get(TextAttribute.RUN_DIRECTION);
658             if (runDirection != null) {
659               directionKnown = true;
660               isDirectionLTR = TextAttribute.RUN_DIRECTION_LTR.equals(runDirection);
661               requiresBidi = !isDirectionLTR;
662             }
663           }
664           catch (ClassCastException JavaDoc e) {
665           }
666
667           try {
668             Integer JavaDoc embeddingLevel = (Integer JavaDoc)attributes.get(TextAttribute.BIDI_EMBEDDING);
669             if (embeddingLevel != null) {
670               int intLevel = embeddingLevel.intValue();
671               if (intLevel >= -61 && intLevel < 62) {
672                 byte level = (byte)intLevel;
673                 requiresBidi = true;
674                 embs = new byte[characterCount];
675                 for (int i = 0; i < embs.length; ++i) {
676                   embs[i] = level;
677                 }
678               }
679             }
680           }
681           catch (ClassCastException JavaDoc e) {
682           }
683         }
684
685         if (!requiresBidi) {
686         requiresBidi = Bidi.requiresBidi(chars, 0, chars.length);
687         }
688
689         if (requiresBidi) {
690       int bidiflags = Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT;
691           if (directionKnown) {
692           if (isDirectionLTR) {
693           bidiflags = Bidi.DIRECTION_LEFT_TO_RIGHT;
694           } else {
695           bidiflags = Bidi.DIRECTION_RIGHT_TO_LEFT;
696           }
697           }
698
699           bidi = new Bidi JavaDoc(chars, 0, embs, 0, chars.length, bidiflags);
700       if (!bidi.isLeftToRight()) {
701           levels = BidiUtils.getLevels(bidi);
702           int[] charsVtoL = BidiUtils.createVisualToLogicalMap(levels);
703           charsLtoV = BidiUtils.createInverseMap(charsVtoL);
704           isDirectionLTR = bidi.baseIsLeftToRight();
705       }
706         }
707
708         Decoration decorator;
709         if (attributes != null) {
710             decorator = Decoration.getDecoration(StyledParagraph.addInputMethodAttrs(attributes));
711         }
712         else {
713             decorator = Decoration.getPlainDecoration();
714         }
715     int layoutFlags = 0; // no extra info yet, bidi determines run and line direction
716
TextLabelFactory factory = new TextLabelFactory(frc, chars, bidi, layoutFlags);
717
718         TextLineComponent[] components = new TextLineComponent[1];
719         
720         components = createComponentsOnRun(0, chars.length,
721                                            chars,
722                                            charsLtoV, levels,
723                                            factory, font, lm,
724                                            frc,
725                                            decorator,
726                                            components,
727                                            0);
728                                            
729         int numComponents = components.length;
730         while (components[numComponents-1] == null) {
731             numComponents -= 1;
732         }
733         
734         if (numComponents != components.length) {
735             TextLineComponent[] temp = new TextLineComponent[numComponents];
736             System.arraycopy(components, 0, temp, 0, numComponents);
737             components = temp;
738         }
739         
740         return new TextLine JavaDoc(components, lm.baselineOffsets,
741                             chars, 0, chars.length, charsLtoV, levels, isDirectionLTR);
742     }
743
744     private static TextLineComponent[] expandArray(TextLineComponent[] orig) {
745
746         TextLineComponent[] newComponents = new TextLineComponent[orig.length + 8];
747         System.arraycopy(orig, 0, newComponents, 0, orig.length);
748
749         return newComponents;
750     }
751
752     /**
753      * Returns an array in logical order of the TextLineComponents on
754      * the text in the given range, with the given attributes.
755      */

756     public static TextLineComponent[] createComponentsOnRun(int runStart,
757                                                             int runLimit,
758                                                             char[] chars,
759                                                             int[] charsLtoV,
760                                                             byte[] levels,
761                                                             TextLabelFactory factory,
762                                                             Font JavaDoc font,
763                                                             CoreMetrics cm,
764                                                             FontRenderContext JavaDoc frc,
765                                                             Decoration decorator,
766                                                             TextLineComponent[] components,
767                                                             int numComponents) {
768
769         int pos = runStart;
770         do {
771             int chunkLimit = firstVisualChunk(charsLtoV, levels, pos, runLimit); // <= displayLimit
772

773             do {
774                 int startPos = pos;
775                 int lmCount;
776                 
777                 if (cm == null) {
778                     LineMetrics JavaDoc lineMetrics = font.getLineMetrics(chars, startPos, chunkLimit, frc);
779                     cm = CoreMetrics.get(lineMetrics);
780                     lmCount = lineMetrics.getNumChars();
781                 }
782                 else {
783                     lmCount = (chunkLimit-startPos);
784                 }
785
786                 TextLineComponent nextComponent =
787                     factory.createExtended(font, cm, decorator, startPos, startPos + lmCount);
788
789                 ++numComponents;
790                 if (numComponents >= components.length) {
791                     components = expandArray(components);
792                 }
793
794                 components[numComponents-1] = nextComponent;
795                 
796                 pos += lmCount;
797             } while (pos < chunkLimit);
798
799         } while (pos < runLimit);
800         
801         return components;
802     }
803
804     /**
805      * Returns an array (in logical order) of the TextLineComponents representing
806      * the text. The components are both logically and visually contiguous.
807      */

808     public static TextLineComponent[] getComponents(StyledParagraph JavaDoc styledParagraph,
809                                                     char[] chars,
810                                                     int textStart,
811                                                     int textLimit,
812                                                     int[] charsLtoV,
813                                                     byte[] levels,
814                                                     TextLabelFactory factory) {
815
816         FontRenderContext JavaDoc frc = factory.getFontRenderContext();
817
818         int numComponents = 0;
819         TextLineComponent[] tempComponents = new TextLineComponent[1];
820
821         int pos = textStart;
822         do {
823             int runLimit = Math.min(styledParagraph.getRunLimit(pos), textLimit);
824