1 /*
2  * Copyright (c) 1998, 2011, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */

25
26 /*
27  * (C) Copyright IBM Corp. 1998-2003, All Rights Reserved
28  *
29  */

30
31 package java.awt.font;
32
33 import java.awt.Color;
34 import java.awt.Font;
35 import java.awt.Graphics2D;
36 import java.awt.Rectangle;
37 import java.awt.Shape;
38 import java.awt.geom.AffineTransform;
39 import java.awt.geom.GeneralPath;
40 import java.awt.geom.Point2D;
41 import java.awt.geom.Rectangle2D;
42 import java.awt.im.InputMethodHighlight;
43 import java.awt.image.BufferedImage;
44 import java.text.Annotation;
45 import java.text.AttributedCharacterIterator;
46 import java.text.AttributedCharacterIterator.Attribute;
47 import java.text.Bidi;
48 import java.text.CharacterIterator;
49 import java.util.Hashtable;
50 import java.util.Map;
51 import sun.font.AttributeValues;
52 import sun.font.BidiUtils;
53 import sun.font.CodePointIterator;
54 import sun.font.CoreMetrics;
55 import sun.font.Decoration;
56 import sun.font.FontLineMetrics;
57 import sun.font.FontResolver;
58 import sun.font.GraphicComponent;
59 import sun.font.LayoutPathImpl;
60 import sun.font.LayoutPathImpl.EmptyPath;
61 import sun.font.LayoutPathImpl.SegmentPathBuilder;
62 import sun.font.TextLabelFactory;
63 import sun.font.TextLineComponent;
64
65 import java.awt.geom.Line2D;
66
67 final class TextLine {
68
69     static final class TextLineMetrics {
70         public final float ascent;
71         public final float descent;
72         public final float leading;
73         public final float advance;
74
75         public TextLineMetrics(float ascent,
76                            float descent,
77                            float leading,
78                            float advance) {
79             this.ascent = ascent;
80             this.descent = descent;
81             this.leading = leading;
82             this.advance = advance;
83         }
84     }
85
86     private TextLineComponent[] fComponents;
87     private float[] fBaselineOffsets;
88     private int[] fComponentVisualOrder; // if null, ltr
89     private float[] locs; // x,y pairs for components in visual order
90     private char[] fChars;
91     private int fCharsStart;
92     private int fCharsLimit;
93     private int[] fCharVisualOrder;  // if null, ltr
94     private int[] fCharLogicalOrder; // if null, ltr
95     private byte[] fCharLevels;     // if null, 0
96     private boolean fIsDirectionLTR;
97     private LayoutPathImpl lp;
98     private boolean isSimple;
99     private Rectangle pixelBounds;
100     private FontRenderContext frc;
101
102     private TextLineMetrics fMetrics = null// built on demand in getMetrics
103
104     public TextLine(FontRenderContext frc,
105                     TextLineComponent[] components,
106                     float[] baselineOffsets,
107                     char[] chars,
108                     int charsStart,
109                     int charsLimit,
110                     int[] charLogicalOrder,
111                     byte[] charLevels,
112                     boolean isDirectionLTR) {
113
114         int[] componentVisualOrder = computeComponentOrder(components,
115                                                            charLogicalOrder);
116
117         this.frc = frc;
118         fComponents = components;
119         fBaselineOffsets = baselineOffsets;
120         fComponentVisualOrder = componentVisualOrder;
121         fChars = chars;
122         fCharsStart = charsStart;
123         fCharsLimit = charsLimit;
124         fCharLogicalOrder = charLogicalOrder;
125         fCharLevels = charLevels;
126         fIsDirectionLTR = isDirectionLTR;
127         checkCtorArgs();
128
129         init();
130     }
131
132     private void checkCtorArgs() {
133
134         int checkCharCount = 0;
135         for (int i=0; i < fComponents.length; i++) {
136             checkCharCount += fComponents[i].getNumCharacters();
137         }
138
139         if (checkCharCount != this.characterCount()) {
140             throw new IllegalArgumentException("Invalid TextLine!  " +
141                                 "char count is different from " +
142                                 "sum of char counts of components.");
143         }
144     }
145
146     private void init() {
147
148         // first, we need to check for graphic components on the TOP or BOTTOM baselines.  So
149         // we perform the work that used to be in getMetrics here.
150
151         float ascent = 0;
152         float descent = 0;
153         float leading = 0;
154         float advance = 0;
155
156         // ascent + descent must not be less than this value
157         float maxGraphicHeight = 0;
158         float maxGraphicHeightWithLeading = 0;
159
160         // walk through EGA's
161         TextLineComponent tlc;
162         boolean fitTopAndBottomGraphics = false;
163
164         isSimple = true;
165
166         for (int i = 0; i < fComponents.length; i++) {
167             tlc = fComponents[i];
168
169             isSimple &= tlc.isSimple();
170
171             CoreMetrics cm = tlc.getCoreMetrics();
172
173             byte baseline = (byte)cm.baselineIndex;
174
175             if (baseline >= 0) {
176                 float baselineOffset = fBaselineOffsets[baseline];
177
178                 ascent = Math.max(ascent, -baselineOffset + cm.ascent);
179
180                 float gd = baselineOffset + cm.descent;
181                 descent = Math.max(descent, gd);
182
183                 leading = Math.max(leading, gd + cm.leading);
184             }
185             else {
186                 fitTopAndBottomGraphics = true;
187                 float graphicHeight = cm.ascent + cm.descent;
188                 float graphicHeightWithLeading = graphicHeight + cm.leading;
189                 maxGraphicHeight = Math.max(maxGraphicHeight, graphicHeight);
190                 maxGraphicHeightWithLeading = Math.max(maxGraphicHeightWithLeading,
191                                                        graphicHeightWithLeading);
192             }
193         }
194
195         if (fitTopAndBottomGraphics) {
196             if (maxGraphicHeight > ascent + descent) {
197                 descent = maxGraphicHeight - ascent;
198             }
199             if (maxGraphicHeightWithLeading > ascent + leading) {
200                 leading = maxGraphicHeightWithLeading - ascent;
201             }
202         }
203
204         leading -= descent;
205
206         // we now know enough to compute the locs, but we need the final loc
207         // for the advance before we can create the metrics object
208
209         if (fitTopAndBottomGraphics) {
210             // we have top or bottom baselines, so expand the baselines array
211             // full offsets are needed by CoreMetrics.effectiveBaselineOffset
212             fBaselineOffsets = new float[] {
213                 fBaselineOffsets[0],
214                 fBaselineOffsets[1],
215                 fBaselineOffsets[2],
216                 descent,
217                 -ascent
218             };
219         }
220
221         float x = 0;
222         float y = 0;
223         CoreMetrics pcm = null;
224
225         boolean needPath = false;
226         locs = new float[fComponents.length * 2 + 2];
227
228         for (int i = 0, n = 0; i < fComponents.length; ++i, n += 2) {
229             tlc = fComponents[getComponentLogicalIndex(i)];
230             CoreMetrics cm = tlc.getCoreMetrics();
231
232             if ((pcm != null) &&
233                 (pcm.italicAngle != 0 || cm.italicAngle != 0) &&  // adjust because of italics
234                 (pcm.italicAngle != cm.italicAngle ||
235                  pcm.baselineIndex != cm.baselineIndex ||
236                  pcm.ssOffset != cm.ssOffset)) {
237
238                 // 1) compute the area of overlap - min effective ascent and min effective descent
239                 // 2) compute the x positions along italic angle of ascent and descent for left and right
240                 // 3) compute maximum left - right, adjust right position by this value
241                 // this is a crude form of kerning between textcomponents
242
243                 // note glyphvectors preposition glyphs based on offset,
244                 // so tl doesn't need to adjust glyphvector position
245                 // 1)
246                 float pb = pcm.effectiveBaselineOffset(fBaselineOffsets);
247                 float pa = pb - pcm.ascent;
248                 float pd = pb + pcm.descent;
249                 // pb += pcm.ssOffset;
250
251                 float cb = cm.effectiveBaselineOffset(fBaselineOffsets);
252                 float ca = cb - cm.ascent;
253                 float cd = cb + cm.descent;
254                 // cb += cm.ssOffset;
255
256                 float a = Math.max(pa, ca);
257                 float d = Math.min(pd, cd);
258
259                 // 2)
260                 float pax = pcm.italicAngle * (pb - a);
261                 float pdx = pcm.italicAngle * (pb - d);
262
263                 float cax = cm.italicAngle * (cb - a);
264                 float cdx = cm.italicAngle * (cb - d);
265
266                 // 3)
267                 float dax = pax - cax;
268                 float ddx = pdx - cdx;
269                 float dx = Math.max(dax, ddx);
270
271                 x += dx;
272                 y = cb;
273             } else {
274                 // no italic adjustment for x, but still need to compute y
275                 y = cm.effectiveBaselineOffset(fBaselineOffsets); // + cm.ssOffset;
276             }
277
278             locs[n] = x;
279             locs[n+1] = y;
280
281             x += tlc.getAdvance();
282             pcm = cm;
283
284             needPath |= tlc.getBaselineTransform() != null;
285         }
286
287         // do we want italic padding at the right of the line?
288         if (pcm.italicAngle != 0) {
289             float pb = pcm.effectiveBaselineOffset(fBaselineOffsets);
290             float pa = pb - pcm.ascent;
291             float pd = pb + pcm.descent;
292             pb += pcm.ssOffset;
293
294             float d;
295             if (pcm.italicAngle > 0) {
296                 d = pb + pcm.ascent;
297             } else {
298                 d = pb - pcm.descent;
299             }
300             d *= pcm.italicAngle;
301
302             x += d;
303         }
304         locs[locs.length - 2] = x;
305         // locs[locs.length - 1] = 0; // final offset is always back on baseline
306
307         // ok, build fMetrics since we have the final advance
308         advance = x;
309         fMetrics = new TextLineMetrics(ascent, descent, leading, advance);
310
311         // build path if we need it
312         if (needPath) {
313             isSimple = false;
314
315             Point2D.Double pt = new Point2D.Double();
316             double tx = 0, ty = 0;
317             SegmentPathBuilder builder = new SegmentPathBuilder();
318             builder.moveTo(locs[0], 0);
319             for (int i = 0, n = 0; i < fComponents.length; ++i, n += 2) {
320                 tlc = fComponents[getComponentLogicalIndex(i)];
321                 AffineTransform at = tlc.getBaselineTransform();
322                 if (at != null &&
323                     ((at.getType() & AffineTransform.TYPE_TRANSLATION) != 0)) {
324                     double dx = at.getTranslateX();
325                     double dy = at.getTranslateY();
326                     builder.moveTo(tx += dx, ty += dy);
327                 }
328                 pt.x = locs[n+2] - locs[n];
329                 pt.y = 0;
330                 if (at != null) {
331                     at.deltaTransform(pt, pt);
332                 }
333                 builder.lineTo(tx += pt.x, ty += pt.y);
334             }
335             lp = builder.complete();
336
337             if (lp == null) { // empty path
338                 tlc = fComponents[getComponentLogicalIndex(0)];
339                 AffineTransform at = tlc.getBaselineTransform();
340                 if (at != null) {
341                     lp = new EmptyPath(at);
342                 }
343             }
344         }
345     }
346
347     public Rectangle getPixelBounds(FontRenderContext frc, float x, float y) {
348         Rectangle result = null;
349
350         // if we have a matching frc, set it to null so we don't have to test it
351         // for each component
352         if (frc != null && frc.equals(this.frc)) {
353             frc = null;
354         }
355
356         // only cache integral locations with the default frc, this is a bit strict
357         int ix = (int)Math.floor(x);
358         int iy = (int)Math.floor(y);
359         float rx = x - ix;
360         float ry = y - iy;
361         boolean canCache = frc == null && rx == 0 && ry == 0;
362
363         if (canCache && pixelBounds != null) {
364             result = new Rectangle(pixelBounds);
365             result.x += ix;
366             result.y += iy;
367             return result;
368         }
369
370         // couldn't use cache, or didn't have it, so compute
371
372         if (isSimple) { // all glyphvectors with no decorations, no layout path
373             for (int i = 0, n = 0; i < fComponents.length; i++, n += 2) {
374                 TextLineComponent tlc = fComponents[getComponentLogicalIndex(i)];
375                 Rectangle pb = tlc.getPixelBounds(frc, locs[n] + rx, locs[n+1] + ry);
376                 if (!pb.isEmpty()) {
377                     if (result == null) {
378                         result = pb;
379                     } else {
380                         result.add(pb);
381                     }
382                 }
383             }
384             if (result == null) {
385                 result = new Rectangle(0, 0, 0, 0);
386             }
387         } else { // draw and test
388             final int MARGIN = 3;
389             Rectangle2D r2d = getVisualBounds();
390             if (lp != null) {
391                 r2d = lp.mapShape(r2d).getBounds();
392             }
393             Rectangle bounds = r2d.getBounds();
394             BufferedImage im = new BufferedImage(bounds.width + MARGIN * 2,
395                                                  bounds.height + MARGIN * 2,
396                                                  BufferedImage.TYPE_INT_ARGB);
397
398             Graphics2D g2d = im.createGraphics();
399             g2d.setColor(Color.WHITE);
400             g2d.fillRect(0, 0, im.getWidth(), im.getHeight());
401
402             g2d.setColor(Color.BLACK);
403             draw(g2d, rx + MARGIN - bounds.x, ry + MARGIN - bounds.y);
404
405             result = computePixelBounds(im);
406             result.x -= MARGIN - bounds.x;
407             result.y -= MARGIN - bounds.y;
408         }
409
410         if (canCache) {
411             pixelBounds = new Rectangle(result);
412         }
413
414         result.x += ix;
415         result.y += iy;
416         return result;
417     }
418
419     static Rectangle computePixelBounds(BufferedImage im) {
420         int w = im.getWidth();
421         int h = im.getHeight();
422
423         int l = -1, t = -1, r = w, b = h;
424
425         {
426             // get top
427             int[] buf = new int[w];
428             loop: while (++t < h) {
429                 im.getRGB(0, t, buf.length, 1, buf, 0, w); // w ignored
430                 for (int i = 0; i < buf.length; i++) {
431                     if (buf[i] != -1) {
432                         break loop;
433                     }
434                 }
435             }
436         }
437
438         // get bottom
439         {
440             int[] buf = new int[w];
441             loop: while (--b > t) {
442                 im.getRGB(0, b, buf.length, 1, buf, 0, w); // w ignored
443                 for (int i = 0; i < buf.length; ++i) {
444                     if (buf[i] != -1) {
445                         break loop;
446                     }
447                 }
448             }
449             ++b;
450         }
451
452         // get left
453         {
454             loop: while (++l < r) {
455                 for (int i = t; i < b; ++i) {
456                     int v = im.getRGB(l, i);
457                     if (v != -1) {
458                         break loop;
459                     }
460                 }
461             }
462         }
463
464         // get right
465         {
466             loop: while (--r > l) {
467                 for (int i = t; i < b; ++i) {
468                     int v = im.getRGB(r, i);
469                     if (v != -1) {
470                         break loop;
471                     }
472                 }
473             }
474             ++r;
475         }
476
477         return new Rectangle(l, t, r-l, b-t);
478     }
479
480     private abstract static class Function {
481
482         abstract float computeFunction(TextLine line,
483                                        int componentIndex,
484                                        int indexInArray);
485     }
486
487     private static Function fgPosAdvF = new Function() {
488         float computeFunction(TextLine line,
489                               int componentIndex,
490                               int indexInArray) {
491
492             TextLineComponent tlc = line.fComponents[componentIndex];
493                 int vi = line.getComponentVisualIndex(componentIndex);
494             return line.locs[vi * 2] + tlc.getCharX(indexInArray) + tlc.getCharAdvance(indexInArray);
495         }
496     };
497
498     private static Function fgAdvanceF = new Function() {
499
500         float computeFunction(TextLine line,
501                               int componentIndex,
502                               int indexInArray) {
503
504             TextLineComponent tlc = line.fComponents[componentIndex];
505             return tlc.getCharAdvance(indexInArray);
506         }
507     };
508
509     private static Function fgXPositionF = new Function() {
510
511         float computeFunction(TextLine line,
512                               int componentIndex,
513                               int indexInArray) {
514
515                 int vi = line.getComponentVisualIndex(componentIndex);
516             TextLineComponent tlc = line.fComponents[componentIndex];
517             return line.locs[vi * 2] + tlc.getCharX(indexInArray);
518         }
519     };
520
521     private static Function fgYPositionF = new Function() {
522
523         float computeFunction(TextLine line,
524                               int componentIndex,
525                               int indexInArray) {
526
527             TextLineComponent tlc = line.fComponents[componentIndex];
528             float charPos = tlc.getCharY(indexInArray);
529
530             // charPos is relative to the component - adjust for
531             // baseline
532
533             return charPos + line.getComponentShift(componentIndex);
534         }
535     };
536
537     public int characterCount() {
538
539         return fCharsLimit - fCharsStart;
540     }
541
542     public boolean isDirectionLTR() {
543
544         return fIsDirectionLTR;
545     }
546
547     public TextLineMetrics getMetrics() {
548         return fMetrics;
549     }
550
551     public int visualToLogical(int visualIndex) {
552
553         if (fCharLogicalOrder == null) {
554             return visualIndex;
555         }
556
557         if (fCharVisualOrder == null) {
558             fCharVisualOrder = BidiUtils.createInverseMap(fCharLogicalOrder);
559         }
560
561         return fCharVisualOrder[visualIndex];
562     }
563
564     public int logicalToVisual(int logicalIndex) {
565
566         return (fCharLogicalOrder == null)?
567             logicalIndex : fCharLogicalOrder[logicalIndex];
568     }
569
570     public byte getCharLevel(int logicalIndex) {
571
572         return fCharLevels==null? 0 : fCharLevels[logicalIndex];
573     }
574
575     public boolean isCharLTR(int logicalIndex) {
576
577         return (getCharLevel(logicalIndex) & 0x1) == 0;
578     }
579
580     public int getCharType(int logicalIndex) {
581
582         return Character.getType(fChars[logicalIndex + fCharsStart]);
583     }
584
585     public boolean isCharSpace(int logicalIndex) {
586
587         return Character.isSpaceChar(fChars[logicalIndex + fCharsStart]);
588     }
589
590     public boolean isCharWhitespace(int logicalIndex) {
591
592         return Character.isWhitespace(fChars[logicalIndex + fCharsStart]);
593     }
594
595     public float getCharAngle(int logicalIndex) {
596
597         return getCoreMetricsAt(logicalIndex).italicAngle;
598     }
599
600     public CoreMetrics getCoreMetricsAt(int logicalIndex) {
601
602         if (logicalIndex < 0) {
603             throw new IllegalArgumentException("Negative logicalIndex.");
604         }
605
606         if (logicalIndex > fCharsLimit - fCharsStart) {
607             throw new IllegalArgumentException("logicalIndex too large.");
608         }
609
610         int currentTlc = 0;
611         int tlcStart = 0;
612         int tlcLimit = 0;
613
614         do {
615             tlcLimit += fComponents[currentTlc].getNumCharacters();
616             if (tlcLimit > logicalIndex) {
617                 break;
618             }
619             ++currentTlc;
620             tlcStart = tlcLimit;
621         } while(currentTlc < fComponents.length);
622
623         return fComponents[currentTlc].getCoreMetrics();
624     }
625
626     public float getCharAscent(int logicalIndex) {
627
628         return getCoreMetricsAt(logicalIndex).ascent;
629     }
630
631     public float getCharDescent(int logicalIndex) {
632
633         return getCoreMetricsAt(logicalIndex).descent;
634     }
635
636     public float getCharShift(int logicalIndex) {
637
638         return getCoreMetricsAt(logicalIndex).ssOffset;
639     }
640
641     private float applyFunctionAtIndex(int logicalIndex, Function f) {
642
643         if (logicalIndex < 0) {
644             throw new IllegalArgumentException("Negative logicalIndex.");
645         }
646
647         int tlcStart = 0;
648
649         for(int i=0; i < fComponents.length; i++) {
650
651             int tlcLimit = tlcStart + fComponents[i].getNumCharacters();
652             if (tlcLimit > logicalIndex) {
653                 return f.computeFunction(this, i, logicalIndex - tlcStart);
654             }
655             else {
656                 tlcStart = tlcLimit;
657             }
658         }
659
660         throw new IllegalArgumentException("logicalIndex too large.");
661     }
662
663     public float getCharAdvance(int logicalIndex) {
664
665         return applyFunctionAtIndex(logicalIndex, fgAdvanceF);
666     }
667
668     public float getCharXPosition(int logicalIndex) {
669
670         return applyFunctionAtIndex(logicalIndex, fgXPositionF);
671     }
672
673     public float getCharYPosition(int logicalIndex) {
674
675         return applyFunctionAtIndex(logicalIndex, fgYPositionF);
676     }
677
678     public float getCharLinePosition(int logicalIndex) {
679
680         return getCharXPosition(logicalIndex);
681     }
682
683     public float getCharLinePosition(int logicalIndex, boolean leading) {
684         Function f = isCharLTR(logicalIndex) == leading ? fgXPositionF : fgPosAdvF;
685         return applyFunctionAtIndex(logicalIndex, f);
686     }
687
688     public boolean caretAtOffsetIsValid(int offset) {
689
690         if (offset < 0) {
691             throw new IllegalArgumentException("Negative offset.");
692         }
693
694         int tlcStart = 0;
695
696         for(int i=0; i < fComponents.length; i++) {
697
698             int tlcLimit = tlcStart + fComponents[i].getNumCharacters();
699             if (tlcLimit > offset) {
700                 return fComponents[i].caretAtOffsetIsValid(offset-tlcStart);
701             }
702             else {
703                 tlcStart = tlcLimit;
704             }
705         }
706
707         throw new IllegalArgumentException("logicalIndex too large.");
708     }
709
710     /**
711      * map a component visual index to the logical index.
712      */

713     private int getComponentLogicalIndex(int vi) {
714         if (fComponentVisualOrder == null) {
715             return vi;
716         }
717         return fComponentVisualOrder[vi];
718     }
719
720     /**
721      * map a component logical index to the visual index.
722      */

723     private int getComponentVisualIndex(int li) {
724         if (fComponentVisualOrder == null) {
725                 return li;
726         }
727         for (int i = 0; i < fComponentVisualOrder.length; ++i) {
728                 if (fComponentVisualOrder[i] == li) {
729                     return i;
730                 }
731         }
732         throw new IndexOutOfBoundsException("bad component index: " + li);
733     }
734
735     public Rectangle2D getCharBounds(int logicalIndex) {
736
737         if (logicalIndex < 0) {
738             throw new IllegalArgumentException("Negative logicalIndex.");
739         }
740
741         int tlcStart = 0;
742
743         for (int i=0; i < fComponents.length; i++) {
744
745             int tlcLimit = tlcStart + fComponents[i].getNumCharacters();
746             if (tlcLimit > logicalIndex) {
747
748                 TextLineComponent tlc = fComponents[i];
749                 int indexInTlc = logicalIndex - tlcStart;
750                 Rectangle2D chBounds = tlc.getCharVisualBounds(indexInTlc);
751
752                         int vi = getComponentVisualIndex(i);
753                 chBounds.setRect(chBounds.getX() + locs[vi * 2],
754                                  chBounds.getY() + locs[vi * 2 + 1],
755                                  chBounds.getWidth(),
756                                  chBounds.getHeight());
757                 return chBounds;
758             }
759             else {
760                 tlcStart = tlcLimit;
761             }
762         }
763
764         throw new IllegalArgumentException("logicalIndex too large.");
765     }
766
767     private float getComponentShift(int index) {
768         CoreMetrics cm = fComponents[index].getCoreMetrics();
769         return cm.effectiveBaselineOffset(fBaselineOffsets);
770     }
771
772     public void draw(Graphics2D g2, float x, float y) {
773         if (lp == null) {
774             for (int i = 0, n = 0; i < fComponents.length; i++, n += 2) {
775                 TextLineComponent tlc = fComponents[getComponentLogicalIndex(i)];
776                 tlc.draw(g2, locs[n] + x, locs[n+1] + y);
777             }
778         } else {
779             AffineTransform oldTx = g2.getTransform();
780             Point2D.Float pt = new Point2D.Float();
781             for (int i = 0, n = 0; i < fComponents.length; i++, n += 2) {
782                 TextLineComponent tlc = fComponents[getComponentLogicalIndex(i)];
783                 lp.pathToPoint(locs[n], locs[n+1], false, pt);
784                 pt.x += x;
785                 pt.y += y;
786                 AffineTransform at = tlc.getBaselineTransform();
787
788                 if (at != null) {
789                     g2.translate(pt.x - at.getTranslateX(), pt.y - at.getTranslateY());
790                     g2.transform(at);
791                     tlc.draw(g2, 0, 0);
792                     g2.setTransform(oldTx);
793                 } else {
794                     tlc.draw(g2, pt.x, pt.y);
795                 }
796             }
797         }
798     }
799
800     /**
801      * Return the union of the visual bounds of all the components.
802      * This incorporates the path.  It does not include logical
803      * bounds (used by carets).
804      */

805     public Rectangle2D getVisualBounds() {
806         Rectangle2D result = null;
807
808         for (int i = 0, n = 0; i < fComponents.length; i++, n += 2) {
809             TextLineComponent tlc = fComponents[getComponentLogicalIndex(i)];
810             Rectangle2D r = tlc.getVisualBounds();
811
812             Point2D.Float pt = new Point2D.Float(locs[n], locs[n+1]);
813             if (lp == null) {
814                 r.setRect(r.getMinX() + pt.x, r.getMinY() + pt.y,
815                           r.getWidth(), r.getHeight());
816             } else {
817                 lp.pathToPoint(pt, false, pt);
818
819                 AffineTransform at = tlc.getBaselineTransform();
820                 if (at != null) {
821                     AffineTransform tx = AffineTransform.getTranslateInstance
822                         (pt.x - at.getTranslateX(), pt.y - at.getTranslateY());
823                     tx.concatenate(at);
824                     r = tx.createTransformedShape(r).getBounds2D();
825                 } else {
826                     r.setRect(r.getMinX() + pt.x, r.getMinY() + pt.y,
827                               r.getWidth(), r.getHeight());
828                 }
829             }
830
831             if (result == null) {
832                 result = r;
833             } else {
834                 result.add(r);
835             }
836         }
837
838         if (result == null) {
839             result = new Rectangle2D.Float(Float.MAX_VALUE, Float.MAX_VALUE, Float.MIN_VALUE, Float.MIN_VALUE);
840         }
841
842         return result;
843     }
844
845     public Rectangle2D getItalicBounds() {
846
847         float left = Float.MAX_VALUE, right = -Float.MAX_VALUE;
848         float top = Float.MAX_VALUE, bottom = -Float.MAX_VALUE;
849
850         for (int i=0, n = 0; i < fComponents.length; i++, n += 2) {
851             TextLineComponent tlc = fComponents[getComponentLogicalIndex(i)];
852
853             Rectangle2D tlcBounds = tlc.getItalicBounds();
854             float x = locs[n];
855             float y = locs[n+1];
856
857             left = Math.min(left, x + (float)tlcBounds.getX());
858             right = Math.max(right, x + (float)tlcBounds.getMaxX());
859
860             top = Math.min(top, y + (float)tlcBounds.getY());
861             bottom = Math.max(bottom, y + (float)tlcBounds.getMaxY());
862         }
863
864         return new Rectangle2D.Float(left, top, right-left, bottom-top);
865     }
866
867     public Shape getOutline(AffineTransform tx) {
868
869         GeneralPath dstShape = new GeneralPath(GeneralPath.WIND_NON_ZERO);
870
871         for (int i=0, n = 0; i < fComponents.length; i++, n += 2) {
872             TextLineComponent tlc = fComponents[getComponentLogicalIndex(i)];
873
874             dstShape.append(tlc.getOutline(locs[n], locs[n+1]), false);
875         }
876
877         if (tx != null) {
878             dstShape.transform(tx);
879         }
880         return dstShape;
881     }
882
883     public String toString() {
884         StringBuilder buf = new StringBuilder();
885
886         for (int i = 0; i < fComponents.length; i++) {
887             buf.append(fComponents[i]);
888         }
889
890         return buf.toString();
891     }
892
893     /**
894      * Create a TextLine from the text.  The Font must be able to
895      * display all of the text.
896      * attributes==null is equivalent to using an empty Map for
897      * attributes
898      */

899     public static TextLine fastCreateTextLine(FontRenderContext frc,
900                                               char[] chars,
901                                               Font font,
902                                               CoreMetrics lm,
903                                               Map<? extends Attribute, ?> attributes) {
904
905         boolean isDirectionLTR = true;
906         byte[] levels = null;
907         int[] charsLtoV = null;
908         Bidi bidi = null;
909         int characterCount = chars.length;
910
911         boolean requiresBidi = false;
912         byte[] embs = null;
913
914         AttributeValues values = null;
915         if (attributes != null) {
916             values = AttributeValues.fromMap(attributes);
917             if (values.getRunDirection() >= 0) {
918                 isDirectionLTR = values.getRunDirection() == 0;
919                 requiresBidi = !isDirectionLTR;
920             }
921             if (values.getBidiEmbedding() != 0) {
922                 requiresBidi = true;
923                 byte level = (byte)values.getBidiEmbedding();
924                 embs = new byte[characterCount];
925                 for (int i = 0; i < embs.length; ++i) {
926                     embs[i] = level;
927                 }
928             }
929         }
930
931         // dlf: get baseRot from font for now???
932
933         if (!requiresBidi) {
934             requiresBidi = Bidi.requiresBidi(chars, 0, chars.length);
935         }
936
937         if (requiresBidi) {
938           int bidiflags = values == null
939               ? Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT
940               : values.getRunDirection();
941
942           bidi = new Bidi(chars, 0, embs, 0, chars.length, bidiflags);
943           if (!bidi.isLeftToRight()) {
944               levels = BidiUtils.getLevels(bidi);
945               int[] charsVtoL = BidiUtils.createVisualToLogicalMap(levels);
946               charsLtoV = BidiUtils.createInverseMap(charsVtoL);
947               isDirectionLTR = bidi.baseIsLeftToRight();
948           }
949         }
950
951         Decoration decorator = Decoration.getDecoration(values);
952
953         int layoutFlags = 0; // no extra info yet, bidi determines run and line direction
954         TextLabelFactory factory = new TextLabelFactory(frc, chars, bidi, layoutFlags);
955
956         TextLineComponent[] components = new TextLineComponent[1];
957
958         components = createComponentsOnRun(0, chars.length,
959                                            chars,
960                                            charsLtoV, levels,
961                                            factory, font, lm,
962                                            frc,
963                                            decorator,
964                                            components,
965                                            0);
966
967         int numComponents = components.length;
968         while (components[numComponents-1] == null) {
969             numComponents -= 1;
970         }
971
972         if (numComponents != components.length) {
973             TextLineComponent[] temp = new TextLineComponent[numComponents];
974             System.arraycopy(components, 0, temp, 0, numComponents);
975             components = temp;
976         }
977
978         return new TextLine(frc, components, lm.baselineOffsets,
979                             chars, 0, chars.length, charsLtoV, levels, isDirectionLTR);
980     }
981
982     private static TextLineComponent[] expandArray(TextLineComponent[] orig) {
983
984         TextLineComponent[] newComponents = new TextLineComponent[orig.length + 8];
985         System.arraycopy(orig, 0, newComponents, 0, orig.length);
986
987         return newComponents;
988     }
989
990     /**
991      * Returns an array in logical order of the TextLineComponents on
992      * the text in the given range, with the given attributes.
993      */

994     public static TextLineComponent[] createComponentsOnRun(int runStart,
995                                                             int runLimit,
996                                                             char[] chars,
997                                                             int[] charsLtoV,
998                                                             byte[] levels,
999                                                             TextLabelFactory factory,
1000                                                             Font font,
1001                                                             CoreMetrics cm,
1002                                                             FontRenderContext frc,
1003                                                             Decoration decorator,
1004                                                             TextLineComponent[] components,
1005                                                             int numComponents) {
1006
1007         int pos = runStart;
1008         do {
1009             int chunkLimit = firstVisualChunk(charsLtoV, levels, pos, runLimit); // <= displayLimit
1010
1011             do {
1012                 int startPos = pos;
1013                 int lmCount;
1014
1015                 if (cm == null) {
1016                     LineMetrics lineMetrics = font.getLineMetrics(chars, startPos, chunkLimit, frc);
1017                     cm = CoreMetrics.get(lineMetrics);
1018                     lmCount = lineMetrics.getNumChars();
1019                 }
1020                 else {
1021                     lmCount = (chunkLimit-startPos);
1022                 }
1023
1024                 TextLineComponent nextComponent =
1025                     factory.createExtended(font, cm, decorator, startPos, startPos + lmCount);
1026
1027                 ++numComponents;
1028                 if (numComponents >= components.length) {
1029                     components = expandArray(components);
1030                 }
1031
1032                 components[numComponents-1] = nextComponent;
1033
1034                 pos += lmCount;
1035             } while (pos < chunkLimit);
1036
1037         } while (pos < runLimit);
1038
1039         return components;
1040     }
1041
1042     /**
1043      * Returns an array (in logical order) of the TextLineComponents representing
1044      * the text.  The components are both logically and visually contiguous.
1045      */

1046     public static TextLineComponent[] getComponents(StyledParagraph styledParagraph,
1047                                                     char[] chars,
1048                                                     int textStart,
1049                                                     int textLimit,
1050                                                     int[] charsLtoV,
1051                                                     byte[] levels,
1052                                                     TextLabelFactory factory) {
1053
1054         FontRenderContext frc = factory.getFontRenderContext();
1055
1056         int numComponents = 0;
1057         TextLineComponent[] tempComponents = new TextLineComponent[1];
1058
1059         int pos = textStart;
1060         do {
1061             int runLimit = Math.min(styledParagraph.getRunLimit(pos), textLimit);
1062
1063             Decoration decorator = styledParagraph.getDecorationAt(pos);
1064
1065             Object graphicOrFont = styledParagraph.getFontOrGraphicAt(pos);
1066
1067             if (graphicOrFont instanceof GraphicAttribute) {
1068                 // AffineTransform baseRot = styledParagraph.getBaselineRotationAt(pos);
1069                 // !!! For now, let's assign runs of text with both fonts and graphic attributes
1070                 // a null rotation (e.g. the baseline rotation goes away when a graphic
1071                 // is applied.
1072                 AffineTransform baseRot = null;
1073                 GraphicAttribute graphicAttribute = (GraphicAttribute) graphicOrFont;
1074                 do {
1075                     int chunkLimit = firstVisualChunk(charsLtoV, levels,
1076                                     pos, runLimit);
1077
1078                     GraphicComponent nextGraphic =
1079                         new GraphicComponent(graphicAttribute, decorator, charsLtoV, levels, pos, chunkLimit, baseRot);
1080                     pos = chunkLimit;
1081
1082                     ++numComponents;
1083                     if (numComponents >= tempComponents.length) {
1084                         tempComponents = expandArray(tempComponents);
1085                     }
1086
1087                     tempComponents[numComponents-1] = nextGraphic;
1088
1089                 } while(pos < runLimit);
1090             }
1091             else {
1092                 Font font = (Font) graphicOrFont;
1093
1094                 tempComponents = createComponentsOnRun(pos, runLimit,
1095                                                         chars,
1096                                                         charsLtoV, levels,
1097                                                         factory, font, null,
1098                                                         frc,
1099                                                         decorator,
1100                                                         tempComponents,
1101                                                         numComponents);
1102                 pos = runLimit;
1103                 numComponents = tempComponents.length;
1104                 while (tempComponents[numComponents-1] == null) {
1105                     numComponents -= 1;
1106                 }
1107             }
1108
1109         } while (pos < textLimit);
1110
1111         TextLineComponent[] components;
1112         if (tempComponents.length == numComponents) {
1113             components = tempComponents;
1114         }
1115         else {
1116             components = new TextLineComponent[numComponents];
1117             System.arraycopy(tempComponents, 0, components, 0, numComponents);
1118         }
1119
1120         return components;
1121     }
1122
1123     /**
1124      * Create a TextLine from the Font and character data over the
1125      * range.  The range is relative to both the StyledParagraph and the
1126      * character array.
1127      */

1128     public static TextLine createLineFromText(char[] chars,
1129                                               StyledParagraph styledParagraph,
1130                                               TextLabelFactory factory,
1131                                               boolean isDirectionLTR,
1132                                               float[] baselineOffsets) {
1133
1134         factory.setLineContext(0, chars.length);
1135
1136         Bidi lineBidi = factory.getLineBidi();
1137         int[] charsLtoV = null;
1138         byte[] levels = null;
1139
1140         if (lineBidi != null) {
1141             levels = BidiUtils.getLevels(lineBidi);
1142             int[] charsVtoL = BidiUtils.createVisualToLogicalMap(levels);
1143             charsLtoV = BidiUtils.createInverseMap(charsVtoL);
1144         }
1145
1146         TextLineComponent[] components =
1147             getComponents(styledParagraph, chars, 0, chars.length, charsLtoV, levels, factory);
1148
1149         return new TextLine(factory.getFontRenderContext(), components, baselineOffsets,
1150                             chars, 0, chars.length, charsLtoV, levels, isDirectionLTR);
1151     }
1152
1153     /**
1154      * Compute the components order from the given components array and
1155      * logical-to-visual character mapping.  May return null if canonical.
1156      */

1157     private static int[] computeComponentOrder(TextLineComponent[] components,
1158                                                int[] charsLtoV) {
1159
1160         /*
1161          * Create a visual ordering for the glyph sets.  The important thing
1162          * here is that the values have the proper rank with respect to
1163          * each other, not the exact values.  For example, the first glyph
1164          * set that appears visually should have the lowest value.  The last
1165          * should have the highest value.  The values are then normalized
1166          * to map 1-1 with positions in glyphs.
1167          *
1168          */

1169         int[] componentOrder = null;
1170         if (charsLtoV != null && components.length > 1) {
1171             componentOrder = new int[components.length];
1172             int gStart = 0;
1173             for (int i = 0; i < components.length; i++) {
1174                 componentOrder[i] = charsLtoV[gStart];
1175                 gStart += components[i].getNumCharacters();
1176             }
1177
1178             componentOrder = BidiUtils.createContiguousOrder(componentOrder);
1179             componentOrder = BidiUtils.createInverseMap(componentOrder);
1180         }
1181         return componentOrder;
1182     }
1183
1184
1185     /**
1186      * Create a TextLine from the text.  chars is just the text in the iterator.
1187      */

1188     public static TextLine standardCreateTextLine(FontRenderContext frc,
1189                                                   AttributedCharacterIterator text,
1190                                                   char[] chars,
1191                                                   float[] baselineOffsets) {
1192
1193         StyledParagraph styledParagraph = new StyledParagraph(text, chars);
1194         Bidi bidi = new Bidi(text);
1195         if (bidi.isLeftToRight()) {
1196             bidi = null;
1197         }
1198         int layoutFlags = 0; // no extra info yet, bidi determines run and line direction
1199         TextLabelFactory factory = new TextLabelFactory(frc, chars, bidi, layoutFlags);
1200
1201         boolean isDirectionLTR = true;
1202         if (bidi != null) {
1203             isDirectionLTR = bidi.baseIsLeftToRight();
1204         }
1205         return createLineFromText(chars, styledParagraph, factory, isDirectionLTR, baselineOffsets);
1206     }
1207
1208
1209
1210     /*
1211      * A utility to get a range of text that is both logically and visually
1212      * contiguous.
1213      * If the entire range is ok, return limit, otherwise return the first
1214      * directional change after start.  We could do better than this, but
1215      * it doesn't seem worth it at the moment.
1216     private static int firstVisualChunk(int order[], byte direction[],
1217                                         int start, int limit)
1218     {
1219         if (order != null) {
1220             int min = order[start];
1221             int max = order[start];
1222             int count = limit - start;
1223             for (int i = start + 1; i < limit; i++) {
1224                 min = Math.min(min, order[i]);
1225                 max = Math.max(max, order[i]);
1226                 if (max - min >= count) {
1227                     if (direction != null) {
1228                         byte baseLevel = direction[start];
1229                         for (int j = start + 1; j < i; j++) {
1230                             if (direction[j] != baseLevel) {
1231                                 return j;
1232                             }
1233                         }
1234                     }
1235                     return i;
1236                 }
1237             }
1238         }
1239         return limit;
1240     }
1241      */

1242
1243     /**
1244      * When this returns, the ACI's current position will be at the start of the
1245      * first run which does NOT contain a GraphicAttribute.  If no such run exists
1246      * the ACI's position will be at the end, and this method will return false.
1247      */

1248     static boolean advanceToFirstFont(AttributedCharacterIterator aci) {
1249
1250         for (char ch = aci.first();
1251              ch != CharacterIterator.DONE;
1252              ch = aci.setIndex(aci.getRunLimit()))
1253         {
1254
1255             if (aci.getAttribute(TextAttribute.CHAR_REPLACEMENT) == null) {
1256                 return true;
1257             }
1258         }
1259
1260         return false;
1261     }
1262
1263     static float[] getNormalizedOffsets(float[] baselineOffsets, byte baseline) {
1264
1265         if (baselineOffsets[baseline] != 0) {
1266             float base = baselineOffsets[baseline];
1267             float[] temp = new float[baselineOffsets.length];
1268             for (int i = 0; i < temp.length; i++)
1269                 temp[i] = baselineOffsets[i] - base;
1270             baselineOffsets = temp;
1271         }
1272         return baselineOffsets;
1273     }
1274
1275     static Font getFontAtCurrentPos(AttributedCharacterIterator aci) {
1276
1277         Object value = aci.getAttribute(TextAttribute.FONT);
1278         if (value != null) {
1279             return (Font) value;
1280         }
1281         if (aci.getAttribute(TextAttribute.FAMILY) != null) {
1282             return Font.getFont(aci.getAttributes());
1283         }
1284
1285         int ch = CodePointIterator.create(aci).next();
1286         if (ch != CodePointIterator.DONE) {
1287             FontResolver resolver = FontResolver.getInstance();
1288             return resolver.getFont(resolver.getFontIndex(ch), aci.getAttributes());
1289         }
1290         return null;
1291     }
1292
1293   /*
1294    * The new version requires that chunks be at the same level.
1295    */

1296     private static int firstVisualChunk(int order[], byte direction[],
1297                                         int start, int limit)
1298     {
1299         if (order != null && direction != null) {
1300           byte dir = direction[start];
1301           while (++start < limit && direction[start] == dir) {}
1302           return start;
1303         }
1304         return limit;
1305     }
1306
1307   /*
1308    * create a new line with characters between charStart and charLimit
1309    * justified using the provided width and ratio.
1310    */

1311     public TextLine getJustifiedLine(float justificationWidth, float justifyRatio, int justStart, int justLimit) {
1312
1313         TextLineComponent[] newComponents = new TextLineComponent[fComponents.length];
1314         System.arraycopy(fComponents, 0, newComponents, 0, fComponents.length);
1315
1316         float leftHang = 0;
1317         float adv = 0;
1318         float justifyDelta = 0;
1319         boolean rejustify = false;
1320         do {
1321             adv = getAdvanceBetween(newComponents, 0, characterCount());
1322
1323             // all characters outside the justification range must be in the base direction
1324             // of the layout, otherwise justification makes no sense.
1325
1326             float justifyAdvance = getAdvanceBetween(newComponents, justStart, justLimit);
1327
1328             // get the actual justification delta
1329             justifyDelta = (justificationWidth - justifyAdvance) * justifyRatio;
1330
1331             // generate an array of GlyphJustificationInfo records to pass to
1332             // the justifier.  Array is visually ordered.
1333
1334             // get positions that each component will be using
1335             int[] infoPositions = new int[newComponents.length];
1336             int infoCount = 0;
1337             for (int visIndex = 0; visIndex < newComponents.length; visIndex++) {
1338                     int logIndex = getComponentLogicalIndex(visIndex);
1339                 infoPositions[logIndex] = infoCount;
1340                 infoCount += newComponents[logIndex].getNumJustificationInfos();
1341             }
1342             GlyphJustificationInfo[] infos = new GlyphJustificationInfo[infoCount];
1343
1344             // get justification infos
1345             int compStart = 0;
1346             for (int i = 0; i < newComponents.length; i++) {
1347                 TextLineComponent comp = newComponents[i];
1348                 int compLength = comp.getNumCharacters();
1349                 int compLimit = compStart + compLength;
1350                 if (compLimit > justStart) {
1351                     int rangeMin = Math.max(0, justStart - compStart);
1352                     int rangeMax = Math.min(compLength, justLimit - compStart);
1353                     comp.getJustificationInfos(infos, infoPositions[i], rangeMin, rangeMax);
1354
1355                     if (compLimit >= justLimit) {
1356                         break;
1357                     }
1358                 }
1359             }
1360
1361             // records are visually ordered, and contiguous, so start and end are
1362             // simply the places where we didn't fetch records
1363             int infoStart = 0;
1364             int infoLimit = infoCount;
1365             while (infoStart < infoLimit && infos[infoStart] == null) {
1366                 ++infoStart;
1367             }
1368
1369             while (infoLimit > infoStart && infos[infoLimit - 1] == null) {
1370                 --infoLimit;
1371             }
1372
1373             // invoke justifier on the records
1374             TextJustifier justifier = new TextJustifier(infos, infoStart, infoLimit);
1375
1376             float[] deltas = justifier.justify(justifyDelta);
1377
1378             boolean canRejustify = rejustify == false;
1379             boolean wantRejustify = false;
1380             boolean[] flags = new boolean[1];
1381
1382             // apply justification deltas
1383             compStart = 0;
1384             for (int i = 0; i < newComponents.length; i++) {
1385                 TextLineComponent comp = newComponents[i];
1386                 int compLength = comp.getNumCharacters();
1387                 int compLimit = compStart + compLength;
1388                 if (compLimit > justStart) {
1389                     int rangeMin = Math.max(0, justStart - compStart);
1390                     int rangeMax = Math.min(compLength, justLimit - compStart);
1391                     newComponents[i] = comp.applyJustificationDeltas(deltas, infoPositions[i] * 2, flags);
1392
1393                     wantRejustify |= flags[0];
1394
1395                     if (compLimit >= justLimit) {
1396                         break;
1397                     }
1398                 }
1399             }
1400
1401             rejustify = wantRejustify && !rejustify; // only make two passes
1402         } while (rejustify);
1403
1404         return new TextLine(frc, newComponents, fBaselineOffsets, fChars, fCharsStart,
1405                             fCharsLimit, fCharLogicalOrder, fCharLevels,
1406                             fIsDirectionLTR);
1407     }
1408
1409     // return the sum of the advances of text between the logical start and limit
1410     public static float getAdvanceBetween(TextLineComponent[] components, int start, int limit) {
1411         float advance = 0;
1412
1413         int tlcStart = 0;
1414         for(int i = 0; i < components.length; i++) {
1415             TextLineComponent comp = components[i];
1416
1417             int tlcLength = comp.getNumCharacters();
1418             int tlcLimit = tlcStart + tlcLength;
1419             if (tlcLimit > start) {
1420                 int measureStart = Math.max(0, start - tlcStart);
1421                 int measureLimit = Math.min(tlcLength, limit - tlcStart);
1422                 advance += comp.getAdvanceBetween(measureStart, measureLimit);
1423                 if (tlcLimit >= limit) {
1424                     break;
1425                 }
1426             }
1427
1428             tlcStart = tlcLimit;
1429         }
1430
1431         return advance;
1432     }
1433
1434     LayoutPathImpl getLayoutPath() {
1435         return lp;
1436     }
1437 }
1438