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
31package java.awt.font;
32
33import java.awt.Color;
34import java.awt.Font;
35import java.awt.Graphics2D;
36import java.awt.Rectangle;
37import java.awt.Shape;
38import java.awt.geom.AffineTransform;
39import java.awt.geom.GeneralPath;
40import java.awt.geom.Point2D;
41import java.awt.geom.Rectangle2D;
42import java.awt.im.InputMethodHighlight;
43import java.awt.image.BufferedImage;
44import java.text.Annotation;
45import java.text.AttributedCharacterIterator;
46import java.text.AttributedCharacterIterator.Attribute;
47import java.text.Bidi;
48import java.text.CharacterIterator;
49import java.util.Hashtable;
50import java.util.Map;
51import sun.font.AttributeValues;
52import sun.font.BidiUtils;
53import sun.font.CodePointIterator;
54import sun.font.CoreMetrics;
55import sun.font.Decoration;
56import sun.font.FontLineMetrics;
57import sun.font.FontResolver;
58import sun.font.GraphicComponent;
59import sun.font.LayoutPathImpl;
60import sun.font.LayoutPathImpl.EmptyPath;
61import sun.font.LayoutPathImpl.SegmentPathBuilder;
62import sun.font.TextLabelFactory;
63import sun.font.TextLineComponent;
64
65import java.awt.geom.Line2D;
66
67final 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