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