1 /*
2 * Copyright 1998-2006 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any 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 }