History

My first initial commit with all of mine experimental changes.

Dmitry Kabak (userdima committed 13 years ago

Commit d37b88d1 parent 3f5b46618352 files · +2598093 −-8690Browse files

Showing the changes to java/pkg/java/awt/font/TextLine.java (1 of 18352 files). Show all

Unified · Split

java/pkg/java/awt/font/TextLine.java+1437−0
@@ -0,0 +1,1437 @@
1+/*
2+ * Copyright (c) 1998, 2006, 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.Bidi;
47+import java.text.CharacterIterator;
48+import java.util.Hashtable;
49+import java.util.Map;
50+import sun.font.AttributeValues;
51+import sun.font.BidiUtils;
52+import sun.font.CoreMetrics;
53+import sun.font.Decoration;
54+import sun.font.FontLineMetrics;
55+import sun.font.FontResolver;
56+import sun.font.GraphicComponent;
57+import sun.font.LayoutPathImpl;
58+import sun.font.LayoutPathImpl.EmptyPath;
59+import sun.font.LayoutPathImpl.SegmentPathBuilder;
60+import sun.font.TextLabelFactory;
61+import sun.font.TextLineComponent;
62+import sun.text.CodePointIterator;
63+
64+import java.awt.geom.Line2D;
65+
66+final class TextLine {
67+
68+ static final class TextLineMetrics {
69+ public final float ascent;
70+ public final float descent;
71+ public final float leading;
72+ public final float advance;
73+
74+ public TextLineMetrics(float ascent,
75+ float descent,
76+ float leading,
77+ float advance) {
78+ this.ascent = ascent;
79+ this.descent = descent;
80+ this.leading = leading;
81+ this.advance = advance;
82+ }
83+ }
84+
85+ private TextLineComponent[] fComponents;
86+ private float[] fBaselineOffsets;
87+ private int[] fComponentVisualOrder; // if null, ltr
88+ private float[] locs; // x,y pairs for components in visual order
89+ private char[] fChars;
90+ private int fCharsStart;
91+ private int fCharsLimit;
92+ private int[] fCharVisualOrder; // if null, ltr
93+ private int[] fCharLogicalOrder; // if null, ltr
94+ private byte[] fCharLevels; // if null, 0
95+ private boolean fIsDirectionLTR;
96+ private LayoutPathImpl lp;
97+ private boolean isSimple;
98+ private Rectangle pixelBounds;
99+ private FontRenderContext frc;
100+
101+ private TextLineMetrics fMetrics = null; // built on demand in getMetrics
102+
103+ public TextLine(FontRenderContext frc,
104+ TextLineComponent[] components,
105+ float[] baselineOffsets,
106+ char[] chars,
107+ int charsStart,
108+ int charsLimit,
109+ int[] charLogicalOrder,
110+ byte[] charLevels,
111+ boolean isDirectionLTR) {
112+
113+ int[] componentVisualOrder = computeComponentOrder(components,
114+ charLogicalOrder);
115+
116+ this.frc = frc;
117+ fComponents = components;
118+ fBaselineOffsets = baselineOffsets;
119+ fComponentVisualOrder = componentVisualOrder;
120+ fChars = chars;
121+ fCharsStart = charsStart;
122+ fCharsLimit = charsLimit;
123+ fCharLogicalOrder = charLogicalOrder;
124+ fCharLevels = charLevels;
125+ fIsDirectionLTR = isDirectionLTR;
126+ checkCtorArgs();
127+
128+ init();
129+ }
130+
131+ private void checkCtorArgs() {
132+
133+ int checkCharCount = 0;
134+ for (int i=0; i < fComponents.length; i++) {
135+ checkCharCount += fComponents[i].getNumCharacters();
136+ }
137+
138+ if (checkCharCount != this.characterCount()) {
139+ throw new IllegalArgumentException("Invalid TextLine! " +
140+ "char count is different from " +
141+ "sum of char counts of components.");
142+ }
143+ }
144+
145+ private void init() {
146+
147+ // first, we need to check for graphic components on the TOP or BOTTOM baselines. So
148+ // we perform the work that used to be in getMetrics here.
149+
150+ float ascent = 0;
151+ float descent = 0;
152+ float leading = 0;
153+ float advance = 0;
154+
155+ // ascent + descent must not be less than this value
156+ float maxGraphicHeight = 0;
157+ float maxGraphicHeightWithLeading = 0;
158+
159+ // walk through EGA's
160+ TextLineComponent tlc;
161+ boolean fitTopAndBottomGraphics = false;
162+
163+ isSimple = true;
164+
165+ for (int i = 0; i < fComponents.length; i++) {
166+ tlc = fComponents[i];
167+
168+ isSimple &= tlc.isSimple();
169+
170+ CoreMetrics cm = tlc.getCoreMetrics();
171+
172+ byte baseline = (byte)cm.baselineIndex;
173+
174+ if (baseline >= 0) {
175+ float baselineOffset = fBaselineOffsets[baseline];
176+
177+ ascent = Math.max(ascent, -baselineOffset + cm.ascent);
178+
179+ float gd = baselineOffset + cm.descent;
180+ descent = Math.max(descent, gd);
181+
182+ leading = Math.max(leading, gd + cm.leading);
183+ }
184+ else {
185+ fitTopAndBottomGraphics = true;
186+ float graphicHeight = cm.ascent + cm.descent;
187+ float graphicHeightWithLeading = graphicHeight + cm.leading;
188+ maxGraphicHeight = Math.max(maxGraphicHeight, graphicHeight);
189+ maxGraphicHeightWithLeading = Math.max(maxGraphicHeightWithLeading,
190+ graphicHeightWithLeading);
191+ }
192+ }
193+
194+ if (fitTopAndBottomGraphics) {
195+ if (maxGraphicHeight > ascent + descent) {
196+ descent = maxGraphicHeight - ascent;
197+ }
198+ if (maxGraphicHeightWithLeading > ascent + leading) {
199+ leading = maxGraphicHeightWithLeading - ascent;
200+ }
201+ }
202+
203+ leading -= descent;
204+
205+ // we now know enough to compute the locs, but we need the final loc
206+ // for the advance before we can create the metrics object
207+
208+ if (fitTopAndBottomGraphics) {
209+ // we have top or bottom baselines, so expand the baselines array
210+ // full offsets are needed by CoreMetrics.effectiveBaselineOffset
211+ fBaselineOffsets = new float[] {
212+ fBaselineOffsets[0],
213+ fBaselineOffsets[1],
214+ fBaselineOffsets[2],
215+ descent,
216+ -ascent
217+ };
218+ }
219+
220+ float x = 0;
221+ float y = 0;
222+ CoreMetrics pcm = null;
223+
224+ boolean needPath = false;
225+ locs = new float[fComponents.length * 2 + 2];
226+
227+ for (int i = 0, n = 0; i < fComponents.length; ++i, n += 2) {
228+ tlc = fComponents[getComponentLogicalIndex(i)];
229+ CoreMetrics cm = tlc.getCoreMetrics();
230+
231+ if ((pcm != null) &&
232+ (pcm.italicAngle != 0 || cm.italicAngle != 0) && // adjust because of italics
233+ (pcm.italicAngle != cm.italicAngle ||
234+ pcm.baselineIndex != cm.baselineIndex ||
235+ pcm.ssOffset != cm.ssOffset)) {
236+
237+ // 1) compute the area of overlap - min effective ascent and min effective descent
238+ // 2) compute the x positions along italic angle of ascent and descent for left and right
239+ // 3) compute maximum left - right, adjust right position by this value
240+ // this is a crude form of kerning between textcomponents
241+
242+ // note glyphvectors preposition glyphs based on offset,
243+ // so tl doesn't need to adjust glyphvector position
244+ // 1)
245+ float pb = pcm.effectiveBaselineOffset(fBaselineOffsets);
246+ float pa = pb - pcm.ascent;
247+ float pd = pb + pcm.descent;
248+ // pb += pcm.ssOffset;
249+
250+ float cb = cm.effectiveBaselineOffset(fBaselineOffsets);
251+ float ca = cb - cm.ascent;
252+ float cd = cb + cm.descent;
253+ // cb += cm.ssOffset;
254+
255+ float a = Math.max(pa, ca);
256+ float d = Math.min(pd, cd);
257+
258+ // 2)
259+ float pax = pcm.italicAngle * (pb - a);
260+ float pdx = pcm.italicAngle * (pb - d);
261+
262+ float cax = cm.italicAngle * (cb - a);
263+ float cdx = cm.italicAngle * (cb - d);
264+
265+ // 3)
266+ float dax = pax - cax;
267+ float ddx = pdx - cdx;
268+ float dx = Math.max(dax, ddx);
269+
270+ x += dx;
271+ y = cb;
272+ } else {
273+ // no italic adjustment for x, but still need to compute y
274+ y = cm.effectiveBaselineOffset(fBaselineOffsets); // + cm.ssOffset;
275+ }
276+
277+ locs[n] = x;
278+ locs[n+1] = y;
279+
280+ x += tlc.getAdvance();
281+ pcm = cm;
282+
283+ needPath |= tlc.getBaselineTransform() != null;
284+ }
285+
286+ // do we want italic padding at the right of the line?
287+ if (pcm.italicAngle != 0) {
288+ float pb = pcm.effectiveBaselineOffset(fBaselineOffsets);
289+ float pa = pb - pcm.ascent;
290+ float pd = pb + pcm.descent;
291+ pb += pcm.ssOffset;
292+
293+ float d;
294+ if (pcm.italicAngle > 0) {
295+ d = pb + pcm.ascent;
296+ } else {
297+ d = pb - pcm.descent;
298+ }
299+ d *= pcm.italicAngle;
300+
301+ x += d;
302+ }
303+ locs[locs.length - 2] = x;
304+ // locs[locs.length - 1] = 0; // final offset is always back on baseline
305+
306+ // ok, build fMetrics since we have the final advance
307+ advance = x;
308+ fMetrics = new TextLineMetrics(ascent, descent, leading, advance);
309+
310+ // build path if we need it
311+ if (needPath) {
312+ isSimple = false;
313+
314+ Point2D.Double pt = new Point2D.Double();
315+ double tx = 0, ty = 0;
316+ SegmentPathBuilder builder = new SegmentPathBuilder();
317+ builder.moveTo(locs[0], 0);
318+ for (int i = 0, n = 0; i < fComponents.length; ++i, n += 2) {
319+ tlc = fComponents[getComponentLogicalIndex(i)];
320+ AffineTransform at = tlc.getBaselineTransform();
321+ if (at != null && ((at.getType() & at.TYPE_TRANSLATION) != 0)) {
322+ double dx = at.getTranslateX();
323+ double dy = at.getTranslateY();
324+ builder.moveTo(tx += dx, ty += dy);
325+ }
326+ pt.x = locs[n+2] - locs[n];
327+ pt.y = 0;
328+ if (at != null) {
329+ at.deltaTransform(pt, pt);
330+ }
331+ builder.lineTo(tx += pt.x, ty += pt.y);
332+ }
333+ lp = builder.complete();
334+
335+ if (lp == null) { // empty path
336+ tlc = fComponents[getComponentLogicalIndex(0)];
337+ AffineTransform at = tlc.getBaselineTransform();
338+ if (at != null) {
339+ lp = new EmptyPath(at);
340+ }
341+ }
342+ }
343+ }
344+
345+ public Rectangle getPixelBounds(FontRenderContext frc, float x, float y) {
346+ Rectangle result = null;
347+
348+ // if we have a matching frc, set it to null so we don't have to test it
349+ // for each component
350+ if (frc != null && frc.equals(this.frc)) {
351+ frc = null;
352+ }
353+
354+ // only cache integral locations with the default frc, this is a bit strict
355+ int ix = (int)Math.floor(x);
356+ int iy = (int)Math.floor(y);
357+ float rx = x - ix;
358+ float ry = y - iy;
359+ boolean canCache = frc == null && rx == 0 && ry == 0;
360+
361+ if (canCache && pixelBounds != null) {
362+ result = new Rectangle(pixelBounds);
363+ result.x += ix;
364+ result.y += iy;
365+ return result;
366+ }
367+
368+ // couldn't use cache, or didn't have it, so compute
369+
370+ if (isSimple) { // all glyphvectors with no decorations, no layout path
371+ for (int i = 0, n = 0; i < fComponents.length; i++, n += 2) {
372+ TextLineComponent tlc = fComponents[getComponentLogicalIndex(i)];
373+ Rectangle pb = tlc.getPixelBounds(frc, locs[n] + rx, locs[n+1] + ry);
374+ if (!pb.isEmpty()) {
375+ if (result == null) {
376+ result = pb;
377+ } else {
378+ result.add(pb);
379+ }
380+ }
381+ }
382+ if (result == null) {
383+ result = new Rectangle(0, 0, 0, 0);
384+ }
385+ } else { // draw and test
386+ final int MARGIN = 3;
387+ Rectangle2D r2d = getVisualBounds();
388+ if (lp != null) {
389+ r2d = lp.mapShape(r2d).getBounds();
390+ }
391+ Rectangle bounds = r2d.getBounds();
392+ BufferedImage im = new BufferedImage(bounds.width + MARGIN * 2,
393+ bounds.height + MARGIN * 2,
394+ BufferedImage.TYPE_INT_ARGB);
395+
396+ Graphics2D g2d = im.createGraphics();
397+ g2d.setColor(Color.WHITE);
398+ g2d.fillRect(0, 0, im.getWidth(), im.getHeight());
399+
400+ g2d.setColor(Color.BLACK);
401+ draw(g2d, rx + MARGIN - bounds.x, ry + MARGIN - bounds.y);
402+
403+ result = computePixelBounds(im);
404+ result.x -= MARGIN - bounds.x;
405+ result.y -= MARGIN - bounds.y;
406+ }
407+
408+ if (canCache) {
409+ pixelBounds = new Rectangle(result);
410+ }
411+
412+ result.x += ix;
413+ result.y += iy;
414+ return result;
415+ }
416+
417+ static Rectangle computePixelBounds(BufferedImage im) {
418+ int w = im.getWidth();
419+ int h = im.getHeight();
420+
421+ int l = -1, t = -1, r = w, b = h;
422+
423+ {
424+ // get top
425+ int[] buf = new int[w];
426+ loop: while (++t < h) {
427+ im.getRGB(0, t, buf.length, 1, buf, 0, w); // w ignored
428+ for (int i = 0; i < buf.length; i++) {
429+ if (buf[i] != -1) {
430+ break loop;
431+ }
432+ }
433+ }
434+ }
435+
436+ // get bottom
437+ {
438+ int[] buf = new int[w];
439+ loop: while (--b > t) {
440+ im.getRGB(0, b, buf.length, 1, buf, 0, w); // w ignored
441+ for (int i = 0; i < buf.length; ++i) {
442+ if (buf[i] != -1) {
443+ break loop;
444+ }
445+ }
446+ }
447+ ++b;
448+ }
449+
450+ // get left
451+ {
452+ loop: while (++l < r) {
453+ for (int i = t; i < b; ++i) {
454+ int v = im.getRGB(l, i);
455+ if (v != -1) {
456+ break loop;
457+ }
458+ }
459+ }
460+ }
461+
462+ // get right
463+ {
464+ loop: while (--r > l) {
465+ for (int i = t; i < b; ++i) {
466+ int v = im.getRGB(r, i);
467+ if (v != -1) {
468+ break loop;
469+ }
470+ }
471+ }
472+ ++r;
473+ }
474+
475+ return new Rectangle(l, t, r-l, b-t);
476+ }
477+
478+ private abstract static class Function {
479+
480+ abstract float computeFunction(TextLine line,
481+ int componentIndex,
482+ int indexInArray);
483+ }
484+
485+ private static Function fgPosAdvF = new Function() {
486+ float computeFunction(TextLine line,
487+ int componentIndex,
488+ int indexInArray) {
489+
490+ TextLineComponent tlc = line.fComponents[componentIndex];
491+ int vi = line.getComponentVisualIndex(componentIndex);
492+ return line.locs[vi * 2] + tlc.getCharX(indexInArray) + tlc.getCharAdvance(indexInArray);
493+ }
494+ };
495+
496+ private static Function fgAdvanceF = new Function() {
497+
498+ float computeFunction(TextLine line,
499+ int componentIndex,
500+ int indexInArray) {
501+
502+ TextLineComponent tlc = line.fComponents[componentIndex];
503+ return tlc.getCharAdvance(indexInArray);
504+ }
505+ };
506+
507+ private static Function fgXPositionF = new Function() {
508+
509+ float computeFunction(TextLine line,
510+ int componentIndex,
511+ int indexInArray) {
512+
513+ int vi = line.getComponentVisualIndex(componentIndex);
514+ TextLineComponent tlc = line.fComponents[componentIndex];
515+ return line.locs[vi * 2] + tlc.getCharX(indexInArray);
516+ }
517+ };
518+
519+ private static Function fgYPositionF = new Function() {
520+
521+ float computeFunction(TextLine line,
522+ int componentIndex,
523+ int indexInArray) {
524+
525+ TextLineComponent tlc = line.fComponents[componentIndex];
526+ float charPos = tlc.getCharY(indexInArray);
527+
528+ // charPos is relative to the component - adjust for
529+ // baseline
530+
531+ return charPos + line.getComponentShift(componentIndex);
532+ }
533+ };
534+
535+ public int characterCount() {
536+
537+ return fCharsLimit - fCharsStart;
538+ }
539+
540+ public boolean isDirectionLTR() {
541+
542+ return fIsDirectionLTR;
543+ }
544+
545+ public TextLineMetrics getMetrics() {
546+ return fMetrics;
547+ }
548+
549+ public int visualToLogical(int visualIndex) {
550+
551+ if (fCharLogicalOrder == null) {
552+ return visualIndex;
553+ }
554+
555+ if (fCharVisualOrder == null) {
556+ fCharVisualOrder = BidiUtils.createInverseMap(fCharLogicalOrder);
557+ }
558+
559+ return fCharVisualOrder[visualIndex];
560+ }
561+
562+ public int logicalToVisual(int logicalIndex) {
563+
564+ return (fCharLogicalOrder == null)?
565+ logicalIndex : fCharLogicalOrder[logicalIndex];
566+ }
567+
568+ public byte getCharLevel(int logicalIndex) {
569+
570+ return fCharLevels==null? 0 : fCharLevels[logicalIndex];
571+ }
572+
573+ public boolean isCharLTR(int logicalIndex) {
574+
575+ return (getCharLevel(logicalIndex) & 0x1) == 0;
576+ }
577+
578+ public int getCharType(int logicalIndex) {
579+
580+ return Character.getType(fChars[logicalIndex + fCharsStart]);
581+ }
582+
583+ public boolean isCharSpace(int logicalIndex) {
584+
585+ return Character.isSpaceChar(fChars[logicalIndex + fCharsStart]);
586+ }
587+
588+ public boolean isCharWhitespace(int logicalIndex) {
589+
590+ return Character.isWhitespace(fChars[logicalIndex + fCharsStart]);
591+ }
592+
593+ public float getCharAngle(int logicalIndex) {
594+
595+ return getCoreMetricsAt(logicalIndex).italicAngle;
596+ }
597+
598+ public CoreMetrics getCoreMetricsAt(int logicalIndex) {
599+
600+ if (logicalIndex < 0) {
601+ throw new IllegalArgumentException("Negative logicalIndex.");
602+ }
603+
604+ if (logicalIndex > fCharsLimit - fCharsStart) {
605+ throw new IllegalArgumentException("logicalIndex too large.");
606+ }
607+
608+ int currentTlc = 0;
609+ int tlcStart = 0;
610+ int tlcLimit = 0;
611+
612+ do {
613+ tlcLimit += fComponents[currentTlc].getNumCharacters();
614+ if (tlcLimit > logicalIndex) {
615+ break;
616+ }
617+ ++currentTlc;
618+ tlcStart = tlcLimit;
619+ } while(currentTlc < fComponents.length);
620+
621+ return fComponents[currentTlc].getCoreMetrics();
622+ }
623+
624+ public float getCharAscent(int logicalIndex) {
625+
626+ return getCoreMetricsAt(logicalIndex).ascent;
627+ }
628+
629+ public float getCharDescent(int logicalIndex) {
630+
631+ return getCoreMetricsAt(logicalIndex).descent;
632+ }
633+
634+ public float getCharShift(int logicalIndex) {
635+
636+ return getCoreMetricsAt(logicalIndex).ssOffset;
637+ }
638+
639+ private float applyFunctionAtIndex(int logicalIndex, Function f) {
640+
641+ if (logicalIndex < 0) {
642+ throw new IllegalArgumentException("Negative logicalIndex.");
643+ }
644+
645+ int tlcStart = 0;
646+
647+ for(int i=0; i < fComponents.length; i++) {
648+
649+ int tlcLimit = tlcStart + fComponents[i].getNumCharacters();
650+ if (tlcLimit > logicalIndex) {
651+ return f.computeFunction(this, i, logicalIndex - tlcStart);
652+ }
653+ else {
654+ tlcStart = tlcLimit;
655+ }
656+ }
657+
658+ throw new IllegalArgumentException("logicalIndex too large.");
659+ }
660+
661+ public float getCharAdvance(int logicalIndex) {
662+
663+ return applyFunctionAtIndex(logicalIndex, fgAdvanceF);
664+ }
665+
666+ public float getCharXPosition(int logicalIndex) {
667+
668+ return applyFunctionAtIndex(logicalIndex, fgXPositionF);
669+ }
670+
671+ public float getCharYPosition(int logicalIndex) {
672+
673+ return applyFunctionAtIndex(logicalIndex, fgYPositionF);
674+ }
675+
676+ public float getCharLinePosition(int logicalIndex) {
677+
678+ return getCharXPosition(logicalIndex);
679+ }
680+
681+ public float getCharLinePosition(int logicalIndex, boolean leading) {
682+ Function f = isCharLTR(logicalIndex) == leading ? fgXPositionF : fgPosAdvF;
683+ return applyFunctionAtIndex(logicalIndex, f);
684+ }
685+
686+ public boolean caretAtOffsetIsValid(int offset) {
687+
688+ if (offset < 0) {
689+ throw new IllegalArgumentException("Negative offset.");
690+ }
691+
692+ int tlcStart = 0;
693+
694+ for(int i=0; i < fComponents.length; i++) {
695+
696+ int tlcLimit = tlcStart + fComponents[i].getNumCharacters();
697+ if (tlcLimit > offset) {
698+ return fComponents[i].caretAtOffsetIsValid(offset-tlcStart);
699+ }
700+ else {
701+ tlcStart = tlcLimit;
702+ }
703+ }
704+
705+ throw new IllegalArgumentException("logicalIndex too large.");
706+ }
707+
708+ /**
709+ * map a component visual index to the logical index.
710+ */
711+ private int getComponentLogicalIndex(int vi) {
712+ if (fComponentVisualOrder == null) {
713+ return vi;
714+ }
715+ return fComponentVisualOrder[vi];
716+ }
717+
718+ /**
719+ * map a component logical index to the visual index.
720+ */
721+ private int getComponentVisualIndex(int li) {
722+ if (fComponentVisualOrder == null) {
723+ return li;
724+ }
725+ for (int i = 0; i < fComponentVisualOrder.length; ++i) {
726+ if (fComponentVisualOrder[i] == li) {
727+ return i;
728+ }
729+ }
730+ throw new IndexOutOfBoundsException("bad component index: " + li);
731+ }
732+
733+ public Rectangle2D getCharBounds(int logicalIndex) {
734+
735+ if (logicalIndex < 0) {
736+ throw new IllegalArgumentException("Negative logicalIndex.");
737+ }
738+
739+ int tlcStart = 0;
740+
741+ for (int i=0; i < fComponents.length; i++) {
742+
743+ int tlcLimit = tlcStart + fComponents[i].getNumCharacters();
744+ if (tlcLimit > logicalIndex) {
745+
746+ TextLineComponent tlc = fComponents[i];
747+ int indexInTlc = logicalIndex - tlcStart;
748+ Rectangle2D chBounds = tlc.getCharVisualBounds(indexInTlc);
749+
750+ int vi = getComponentVisualIndex(i);
751+ chBounds.setRect(chBounds.getX() + locs[vi * 2],
752+ chBounds.getY() + locs[vi * 2 + 1],
753+ chBounds.getWidth(),
754+ chBounds.getHeight());
755+ return chBounds;
756+ }
757+ else {
758+ tlcStart = tlcLimit;
759+ }
760+ }
761+
762+ throw new IllegalArgumentException("logicalIndex too large.");
763+ }
764+
765+ private float getComponentShift(int index) {
766+ CoreMetrics cm = fComponents[index].getCoreMetrics();
767+ return cm.effectiveBaselineOffset(fBaselineOffsets);
768+ }
769+
770+ public void draw(Graphics2D g2, float x, float y) {
771+ if (lp == null) {
772+ for (int i = 0, n = 0; i < fComponents.length; i++, n += 2) {
773+ TextLineComponent tlc = fComponents[getComponentLogicalIndex(i)];
774+ tlc.draw(g2, locs[n] + x, locs[n+1] + y);
775+ }
776+ } else {
777+ AffineTransform oldTx = g2.getTransform();
778+ Point2D.Float pt = new Point2D.Float();
779+ for (int i = 0, n = 0; i < fComponents.length; i++, n += 2) {
780+ TextLineComponent tlc = fComponents[getComponentLogicalIndex(i)];
781+ lp.pathToPoint(locs[n], locs[n+1], false, pt);
782+ pt.x += x;
783+ pt.y += y;
784+ AffineTransform at = tlc.getBaselineTransform();
785+
786+ if (at != null) {
787+ g2.translate(pt.x - at.getTranslateX(), pt.y - at.getTranslateY());
788+ g2.transform(at);
789+ tlc.draw(g2, 0, 0);
790+ g2.setTransform(oldTx);
791+ } else {
792+ tlc.draw(g2, pt.x, pt.y);
793+ }
794+ }
795+ }
796+ }
797+
798+ /**
799+ * Return the union of the visual bounds of all the components.
800+ * This incorporates the path. It does not include logical
801+ * bounds (used by carets).
802+ */
803+ public Rectangle2D getVisualBounds() {
804+ Rectangle2D result = null;
805+
806+ for (int i = 0, n = 0; i < fComponents.length; i++, n += 2) {
807+ TextLineComponent tlc = fComponents[getComponentLogicalIndex(i)];
808+ Rectangle2D r = tlc.getVisualBounds();
809+
810+ Point2D.Float pt = new Point2D.Float(locs[n], locs[n+1]);
811+ if (lp == null) {
812+ r.setRect(r.getMinX() + pt.x, r.getMinY() + pt.y,
813+ r.getWidth(), r.getHeight());
814+ } else {
815+ lp.pathToPoint(pt, false, pt);
816+
817+ AffineTransform at = tlc.getBaselineTransform();
818+ if (at != null) {
819+ AffineTransform tx = AffineTransform.getTranslateInstance
820+ (pt.x - at.getTranslateX(), pt.y - at.getTranslateY());
821+ tx.concatenate(at);
822+ r = tx.createTransformedShape(r).getBounds2D();
823+ } else {
824+ r.setRect(r.getMinX() + pt.x, r.getMinY() + pt.y,
825+ r.getWidth(), r.getHeight());
826+ }
827+ }
828+
829+ if (result == null) {
830+ result = r;
831+ } else {
832+ result.add(r);
833+ }
834+ }
835+
836+ if (result == null) {
837+ result = new Rectangle2D.Float(Float.MAX_VALUE, Float.MAX_VALUE, Float.MIN_VALUE, Float.MIN_VALUE);
838+ }
839+
840+ return result;
841+ }
842+
843+ public Rectangle2D getItalicBounds() {
844+
845+ float left = Float.MAX_VALUE, right = -Float.MAX_VALUE;
846+ float top = Float.MAX_VALUE, bottom = -Float.MAX_VALUE;
847+
848+ for (int i=0, n = 0; i < fComponents.length; i++, n += 2) {
849+ TextLineComponent tlc = fComponents[getComponentLogicalIndex(i)];
850+
851+ Rectangle2D tlcBounds = tlc.getItalicBounds();
852+ float x = locs[n];
853+ float y = locs[n+1];
854+
855+ left = Math.min(left, x + (float)tlcBounds.getX());
856+ right = Math.max(right, x + (float)tlcBounds.getMaxX());
857+
858+ top = Math.min(top, y + (float)tlcBounds.getY());
859+ bottom = Math.max(bottom, y + (float)tlcBounds.getMaxY());
860+ }
861+
862+ return new Rectangle2D.Float(left, top, right-left, bottom-top);
863+ }
864+
865+ public Shape getOutline(AffineTransform tx) {
866+
867+ GeneralPath dstShape = new GeneralPath(GeneralPath.WIND_NON_ZERO);
868+
869+ for (int i=0, n = 0; i < fComponents.length; i++, n += 2) {
870+ TextLineComponent tlc = fComponents[getComponentLogicalIndex(i)];
871+
872+ dstShape.append(tlc.getOutline(locs[n], locs[n+1]), false);
873+ }
874+
875+ if (tx != null) {
876+ dstShape.transform(tx);
877+ }
878+ return dstShape;
879+ }
880+
881+ public int hashCode() {
882+ return (fComponents.length << 16) ^
883+ (fComponents[0].hashCode() << 3) ^ (fCharsLimit-fCharsStart);
884+ }
885+
886+ public String toString() {
887+ StringBuilder buf = new StringBuilder();
888+
889+ for (int i = 0; i < fComponents.length; i++) {
890+ buf.append(fComponents[i]);
891+ }
892+
893+ return buf.toString();
894+ }
895+
896+ /**
897+ * Create a TextLine from the text. The Font must be able to
898+ * display all of the text.
899+ * attributes==null is equivalent to using an empty Map for
900+ * attributes
901+ */
902+ public static TextLine fastCreateTextLine(FontRenderContext frc,
903+ char[] chars,
904+ Font font,
905+ CoreMetrics lm,
906+ Map attributes) {
907+
908+ boolean isDirectionLTR = true;
909+ byte[] levels = null;
910+ int[] charsLtoV = null;
911+ Bidi bidi = null;
912+ int characterCount = chars.length;
913+
914+ boolean requiresBidi = false;
915+ byte[] embs = null;
916+
917+ AttributeValues values = null;
918+ if (attributes != null) {
919+ values = AttributeValues.fromMap(attributes);
920+ if (values.getRunDirection() >= 0) {
921+ isDirectionLTR = values.getRunDirection() == 0;
922+ requiresBidi = !isDirectionLTR;
923+ }
924+ if (values.getBidiEmbedding() != 0) {
925+ requiresBidi = true;
926+ byte level = (byte)values.getBidiEmbedding();
927+ embs = new byte[characterCount];
928+ for (int i = 0; i < embs.length; ++i) {
929+ embs[i] = level;
930+ }
931+ }
932+ }
933+
934+ // dlf: get baseRot from font for now???
935+
936+ if (!requiresBidi) {
937+ requiresBidi = Bidi.requiresBidi(chars, 0, chars.length);
938+ }
939+
940+ if (requiresBidi) {
941+ int bidiflags = values == null
942+ ? Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT
943+ : values.getRunDirection();
944+
945+ bidi = new Bidi(chars, 0, embs, 0, chars.length, bidiflags);
946+ if (!bidi.isLeftToRight()) {
947+ levels = BidiUtils.getLevels(bidi);
948+ int[] charsVtoL = BidiUtils.createVisualToLogicalMap(levels);
949+ charsLtoV = BidiUtils.createInverseMap(charsVtoL);
950+ isDirectionLTR = bidi.baseIsLeftToRight();
951+ }
952+ }
953+
954+ Decoration decorator = Decoration.getDecoration(values);
955+
956+ int layoutFlags = 0; // no extra info yet, bidi determines run and line direction
957+ TextLabelFactory factory = new TextLabelFactory(frc, chars, bidi, layoutFlags);
958+
959+ TextLineComponent[] components = new TextLineComponent[1];
960+
961+ components = createComponentsOnRun(0, chars.length,
962+ chars,
963+ charsLtoV, levels,
964+ factory, font, lm,
965+ frc,
966+ decorator,
967+ components,
968+ 0);
969+
970+ int numComponents = components.length;
971+ while (components[numComponents-1] == null) {
972+ numComponents -= 1;
973+ }
974+
975+ if (numComponents != components.length) {
976+ TextLineComponent[] temp = new TextLineComponent[numComponents];
977+ System.arraycopy(components, 0, temp, 0, numComponents);
978+ components = temp;
979+ }
980+
981+ return new TextLine(frc, components, lm.baselineOffsets,
982+ chars, 0, chars.length, charsLtoV, levels, isDirectionLTR);
983+ }
984+
985+ private static TextLineComponent[] expandArray(TextLineComponent[] orig) {
986+
987+ TextLineComponent[] newComponents = new TextLineComponent[orig.length + 8];
988+ System.arraycopy(orig, 0, newComponents, 0, orig.length);
989+
990+ return newComponents;
991+ }
992+
993+ /**
994+ * Returns an array in logical order of the TextLineComponents on
995+ * the text in the given range, with the given attributes.
996+ */
997+ public static TextLineComponent[] createComponentsOnRun(int runStart,
998+ int runLimit,
999+ char[] chars,
1000+ int[] charsLtoV,
1001+ byte[] levels,
1002+ TextLabelFactory factory,
1003+ Font font,
1004+ CoreMetrics cm,
1005+ FontRenderContext frc,
1006+ Decoration decorator,
1007+ TextLineComponent[] components,
1008+ int numComponents) {
1009+
1010+ int pos = runStart;
1011+ do {
1012+ int chunkLimit = firstVisualChunk(charsLtoV, levels, pos, runLimit); // <= displayLimit
1013+
1014+ do {
1015+ int startPos = pos;
1016+ int lmCount;
1017+
1018+ if (cm == null) {
1019+ LineMetrics lineMetrics = font.getLineMetrics(chars, startPos, chunkLimit, frc);
1020+ cm = CoreMetrics.get(lineMetrics);
1021+ lmCount = lineMetrics.getNumChars();
1022+ }
1023+ else {
1024+ lmCount = (chunkLimit-startPos);
1025+ }
1026+
1027+ TextLineComponent nextComponent =
1028+ factory.createExtended(font, cm, decorator, startPos, startPos + lmCount);
1029+
1030+ ++numComponents;
1031+ if (numComponents >= components.length) {
1032+ components = expandArray(components);
1033+ }
1034+
1035+ components[numComponents-1] = nextComponent;
1036+
1037+ pos += lmCount;
1038+ } while (pos < chunkLimit);
1039+
1040+ } while (pos < runLimit);
1041+
1042+ return components;
1043+ }
1044+
1045+ /**
1046+ * Returns an array (in logical order) of the TextLineComponents representing
1047+ * the text. The components are both logically and visually contiguous.
1048+ */
1049+ public static TextLineComponent[] getComponents(StyledParagraph styledParagraph,
1050+ char[] chars,
1051+ int textStart,
1052+ int textLimit,
1053+ int[] charsLtoV,
1054+ byte[] levels,
1055+ TextLabelFactory factory) {
1056+
1057+ FontRenderContext frc = factory.getFontRenderContext();
1058+
1059+ int numComponents = 0;
1060+ TextLineComponent[] tempComponents = new TextLineComponent[1];
1061+
1062+ int pos = textStart;
1063+ do {
1064+ int runLimit = Math.min(styledParagraph.getRunLimit(pos), textLimit);
1065+
1066+ Decoration decorator = styledParagraph.getDecorationAt(pos);
1067+
1068+ Object graphicOrFont = styledParagraph.getFontOrGraphicAt(pos);
1069+
1070+ if (graphicOrFont instanceof GraphicAttribute) {
1071+ // AffineTransform baseRot = styledParagraph.getBaselineRotationAt(pos);
1072+ // !!! For now, let's assign runs of text with both fonts and graphic attributes
1073+ // a null rotation (e.g. the baseline rotation goes away when a graphic
1074+ // is applied.
1075+ AffineTransform baseRot = null;
1076+ GraphicAttribute graphicAttribute = (GraphicAttribute) graphicOrFont;
1077+ do {
1078+ int chunkLimit = firstVisualChunk(charsLtoV, levels,
1079+ pos, runLimit);
1080+
1081+ GraphicComponent nextGraphic =
1082+ new GraphicComponent(graphicAttribute, decorator, charsLtoV, levels, pos, chunkLimit, baseRot);
1083+ pos = chunkLimit;
1084+
1085+ ++numComponents;
1086+ if (numComponents >= tempComponents.length) {
1087+ tempComponents = expandArray(tempComponents);
1088+ }
1089+
1090+ tempComponents[numComponents-1] = nextGraphic;
1091+
1092+ } while(pos < runLimit);
1093+ }
1094+ else {
1095+ Font font = (Font) graphicOrFont;
1096+
1097+ tempComponents = createComponentsOnRun(pos, runLimit,
1098+ chars,
1099+ charsLtoV, levels,
1100+ factory, font, null,
1101+ frc,
1102+ decorator,
1103+ tempComponents,
1104+ numComponents);
1105+ pos = runLimit;
1106+ numComponents = tempComponents.length;
1107+ while (tempComponents[numComponents-1] == null) {
1108+ numComponents -= 1;
1109+ }
1110+ }
1111+
1112+ } while (pos < textLimit);
1113+
1114+ TextLineComponent[] components;
1115+ if (tempComponents.length == numComponents) {
1116+ components = tempComponents;
1117+ }
1118+ else {
1119+ components = new TextLineComponent[numComponents];
1120+ System.arraycopy(tempComponents, 0, components, 0, numComponents);
1121+ }
1122+
1123+ return components;
1124+ }
1125+
1126+ /**
1127+ * Create a TextLine from the Font and character data over the
1128+ * range. The range is relative to both the StyledParagraph and the
1129+ * character array.
1130+ */
1131+ public static TextLine createLineFromText(char[] chars,
1132+ StyledParagraph styledParagraph,
1133+ TextLabelFactory factory,
1134+ boolean isDirectionLTR,
1135+ float[] baselineOffsets) {
1136+
1137+ factory.setLineContext(0, chars.length);
1138+
1139+ Bidi lineBidi = factory.getLineBidi();
1140+ int[] charsLtoV = null;
1141+ byte[] levels = null;
1142+
1143+ if (lineBidi != null) {
1144+ levels = BidiUtils.getLevels(lineBidi);
1145+ int[] charsVtoL = BidiUtils.createVisualToLogicalMap(levels);
1146+ charsLtoV = BidiUtils.createInverseMap(charsVtoL);
1147+ }
1148+
1149+ TextLineComponent[] components =
1150+ getComponents(styledParagraph, chars, 0, chars.length, charsLtoV, levels, factory);
1151+
1152+ return new TextLine(factory.getFontRenderContext(), components, baselineOffsets,
1153+ chars, 0, chars.length, charsLtoV, levels, isDirectionLTR);
1154+ }
1155+
1156+ /**
1157+ * Compute the components order from the given components array and
1158+ * logical-to-visual character mapping. May return null if canonical.
1159+ */
1160+ private static int[] computeComponentOrder(TextLineComponent[] components,
1161+ int[] charsLtoV) {
1162+
1163+ /*
1164+ * Create a visual ordering for the glyph sets. The important thing
1165+ * here is that the values have the proper rank with respect to
1166+ * each other, not the exact values. For example, the first glyph
1167+ * set that appears visually should have the lowest value. The last
1168+ * should have the highest value. The values are then normalized
1169+ * to map 1-1 with positions in glyphs.
1170+ *
1171+ */
1172+ int[] componentOrder = null;
1173+ if (charsLtoV != null && components.length > 1) {
1174+ componentOrder = new int[components.length];
1175+ int gStart = 0;
1176+ for (int i = 0; i < components.length; i++) {
1177+ componentOrder[i] = charsLtoV[gStart];
1178+ gStart += components[i].getNumCharacters();
1179+ }
1180+
1181+ componentOrder = BidiUtils.createContiguousOrder(componentOrder);
1182+ componentOrder = BidiUtils.createInverseMap(componentOrder);
1183+ }
1184+ return componentOrder;
1185+ }
1186+
1187+
1188+ /**
1189+ * Create a TextLine from the text. chars is just the text in the iterator.
1190+ */
1191+ public static TextLine standardCreateTextLine(FontRenderContext frc,
1192+ AttributedCharacterIterator text,
1193+ char[] chars,
1194+ float[] baselineOffsets) {
1195+
1196+ StyledParagraph styledParagraph = new StyledParagraph(text, chars);
1197+ Bidi bidi = new Bidi(text);
1198+ if (bidi.isLeftToRight()) {
1199+ bidi = null;
1200+ }
1201+ int layoutFlags = 0; // no extra info yet, bidi determines run and line direction
1202+ TextLabelFactory factory = new TextLabelFactory(frc, chars, bidi, layoutFlags);
1203+
1204+ boolean isDirectionLTR = true;
1205+ if (bidi != null) {
1206+ isDirectionLTR = bidi.baseIsLeftToRight();
1207+ }
1208+ return createLineFromText(chars, styledParagraph, factory, isDirectionLTR, baselineOffsets);
1209+ }
1210+
1211+
1212+
1213+ /*
1214+ * A utility to get a range of text that is both logically and visually
1215+ * contiguous.
1216+ * If the entire range is ok, return limit, otherwise return the first
1217+ * directional change after start. We could do better than this, but
1218+ * it doesn't seem worth it at the moment.
1219+ private static int firstVisualChunk(int order[], byte direction[],
1220+ int start, int limit)
1221+ {
1222+ if (order != null) {
1223+ int min = order[start];
1224+ int max = order[start];
1225+ int count = limit - start;
1226+ for (int i = start + 1; i < limit; i++) {
1227+ min = Math.min(min, order[i]);
1228+ max = Math.max(max, order[i]);
1229+ if (max - min >= count) {
1230+ if (direction != null) {
1231+ byte baseLevel = direction[start];
1232+ for (int j = start + 1; j < i; j++) {
1233+ if (direction[j] != baseLevel) {
1234+ return j;
1235+ }
1236+ }
1237+ }
1238+ return i;
1239+ }
1240+ }
1241+ }
1242+ return limit;
1243+ }
1244+ */
1245+
1246+ /**
1247+ * When this returns, the ACI's current position will be at the start of the
1248+ * first run which does NOT contain a GraphicAttribute. If no such run exists
1249+ * the ACI's position will be at the end, and this method will return false.
1250+ */
1251+ static boolean advanceToFirstFont(AttributedCharacterIterator aci) {
1252+
1253+ for (char ch = aci.first(); ch != aci.DONE; ch = aci.setIndex(aci.getRunLimit())) {
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+}