1
25
26
30
31 package java.awt.font;
32
33 import java.awt.Color;
34 import java.awt.Font;
35 import java.awt.Graphics2D;
36 import java.awt.Rectangle;
37 import java.awt.Shape;
38 import java.awt.geom.AffineTransform;
39 import java.awt.geom.GeneralPath;
40 import java.awt.geom.Point2D;
41 import java.awt.geom.Rectangle2D;
42 import java.awt.im.InputMethodHighlight;
43 import java.awt.image.BufferedImage;
44 import java.text.Annotation;
45 import java.text.AttributedCharacterIterator;
46 import java.text.AttributedCharacterIterator.Attribute;
47 import java.text.Bidi;
48 import java.text.CharacterIterator;
49 import java.util.Hashtable;
50 import java.util.Map;
51 import sun.font.AttributeValues;
52 import sun.font.BidiUtils;
53 import sun.font.CodePointIterator;
54 import sun.font.CoreMetrics;
55 import sun.font.Decoration;
56 import sun.font.FontLineMetrics;
57 import sun.font.FontResolver;
58 import sun.font.GraphicComponent;
59 import sun.font.LayoutPathImpl;
60 import sun.font.LayoutPathImpl.EmptyPath;
61 import sun.font.LayoutPathImpl.SegmentPathBuilder;
62 import sun.font.TextLabelFactory;
63 import sun.font.TextLineComponent;
64
65 import java.awt.geom.Line2D;
66
67 final class TextLine {
68
69 static final class TextLineMetrics {
70 public final float ascent;
71 public final float descent;
72 public final float leading;
73 public final float advance;
74
75 public TextLineMetrics(float ascent,
76 float descent,
77 float leading,
78 float advance) {
79 this.ascent = ascent;
80 this.descent = descent;
81 this.leading = leading;
82 this.advance = advance;
83 }
84 }
85
86 private TextLineComponent[] fComponents;
87 private float[] fBaselineOffsets;
88 private int[] fComponentVisualOrder;
89 private float[] locs;
90 private char[] fChars;
91 private int fCharsStart;
92 private int fCharsLimit;
93 private int[] fCharVisualOrder;
94 private int[] fCharLogicalOrder;
95 private byte[] fCharLevels;
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;
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
149
150
151 float ascent = 0;
152 float descent = 0;
153 float leading = 0;
154 float advance = 0;
155
156
157 float maxGraphicHeight = 0;
158 float maxGraphicHeightWithLeading = 0;
159
160
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
207
208
209 if (fitTopAndBottomGraphics) {
210
211
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) &&
234 (pcm.italicAngle != cm.italicAngle ||
235 pcm.baselineIndex != cm.baselineIndex ||
236 pcm.ssOffset != cm.ssOffset)) {
237
238
239
240
241
242
243
244
245
246 float pb = pcm.effectiveBaselineOffset(fBaselineOffsets);
247 float pa = pb - pcm.ascent;
248 float pd = pb + pcm.descent;
249
250
251 float cb = cm.effectiveBaselineOffset(fBaselineOffsets);
252 float ca = cb - cm.ascent;
253 float cd = cb + cm.descent;
254
255
256 float a = Math.max(pa, ca);
257 float d = Math.min(pd, cd);
258
259
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
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
275 y = cm.effectiveBaselineOffset(fBaselineOffsets);
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
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
306
307
308 advance = x;
309 fMetrics = new TextLineMetrics(ascent, descent, leading, advance);
310
311
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) {
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
351
352 if (frc != null && frc.equals(this.frc)) {
353 frc = null;
354 }
355
356
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
371
372 if (isSimple) {
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 {
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
427 int[] buf = new int[w];
428 loop: while (++t < h) {
429 im.getRGB(0, t, buf.length, 1, buf, 0, w);
430 for (int i = 0; i < buf.length; i++) {
431 if (buf[i] != -1) {
432 break loop;
433 }
434 }
435 }
436 }
437
438
439 {
440 int[] buf = new int[w];
441 loop: while (--b > t) {
442 im.getRGB(0, b, buf.length, 1, buf, 0, w);
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
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
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
531
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
713 private int getComponentLogicalIndex(int vi) {
714 if (fComponentVisualOrder == null) {
715 return vi;
716 }
717 return fComponentVisualOrder[vi];
718 }
719
720
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
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
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
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;
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
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);
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
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
1069
1070
1071
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
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
1157 private static int[] computeComponentOrder(TextLineComponent[] components,
1158 int[] charsLtoV) {
1159
1160
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
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;
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
1242
1243
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
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
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
1324
1325
1326 float justifyAdvance = getAdvanceBetween(newComponents, justStart, justLimit);
1327
1328
1329 justifyDelta = (justificationWidth - justifyAdvance) * justifyRatio;
1330
1331
1332
1333
1334
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
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
1362
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
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
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;
1402 } while (rejustify);
1403
1404 return new TextLine(frc, newComponents, fBaselineOffsets, fChars, fCharsStart,
1405 fCharsLimit, fCharLogicalOrder, fCharLevels,
1406 fIsDirectionLTR);
1407 }
1408
1409
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