1 18 package org.apache.batik.gvt.text; 19 20 import java.awt.BasicStroke ; 21 import java.awt.Graphics2D ; 22 import java.awt.Shape ; 23 import java.awt.Stroke ; 24 import java.awt.font.FontRenderContext ; 25 import java.awt.font.TextAttribute ; 26 import java.awt.geom.AffineTransform ; 27 import java.awt.geom.Area ; 28 import java.awt.geom.GeneralPath ; 29 import java.awt.geom.PathIterator ; 30 import java.awt.geom.Point2D ; 31 import java.awt.geom.Rectangle2D ; 32 import java.text.AttributedCharacterIterator ; 33 import java.text.CharacterIterator ; 34 import java.util.HashSet ; 35 import java.util.List ; 36 import java.util.Set ; 37 38 import org.apache.batik.gvt.font.AWTGVTFont; 39 import org.apache.batik.gvt.font.AltGlyphHandler; 40 import org.apache.batik.gvt.font.GVTFont; 41 import org.apache.batik.gvt.font.GVTGlyphMetrics; 42 import org.apache.batik.gvt.font.GVTGlyphVector; 43 import org.apache.batik.gvt.font.GVTLineMetrics; 44 import org.apache.batik.gvt.TextNode; 45 import org.apache.batik.gvt.text.GVTAttributedCharacterIterator; 46 import org.apache.batik.gvt.text.TextHit; 47 import org.apache.batik.gvt.text.TextSpanLayout; 48 49 56 public class GlyphLayout implements TextSpanLayout { 57 58 public static final char SOFT_HYPHEN = 0x00AD; 59 public static final char ZERO_WIDTH_SPACE = 0x200B; 60 public static final char ZERO_WIDTH_JOINER = 0x200D; 61 public static final char SPACE = ' '; 62 63 private GVTGlyphVector gv; 64 private GVTFont font; 65 private GVTLineMetrics metrics; 66 private AttributedCharacterIterator aci; 67 private FontRenderContext frc; 68 private Point2D advance; 69 private Point2D offset; 70 private float xScale=1; 71 private float yScale=1; 72 private Point2D prevCharPosition; 73 private TextPath textPath; 74 private Point2D textPathAdvance; 75 private int [] charMap; 76 private boolean vertical, adjSpacing=true; 77 private float [] glyphAdvances; 78 private boolean isAltGlyph; 80 private boolean layoutApplied = false; 84 private boolean spacingApplied = false; 91 private boolean pathApplied = false; 97 98 99 public static final AttributedCharacterIterator.Attribute FLOW_LINE_BREAK 100 = GVTAttributedCharacterIterator.TextAttribute.FLOW_LINE_BREAK; 101 102 public static final AttributedCharacterIterator.Attribute FLOW_PARAGRAPH 103 = GVTAttributedCharacterIterator.TextAttribute.FLOW_PARAGRAPH; 104 105 public static final AttributedCharacterIterator.Attribute 106 FLOW_EMPTY_PARAGRAPH 107 = GVTAttributedCharacterIterator.TextAttribute.FLOW_EMPTY_PARAGRAPH; 108 109 public static final AttributedCharacterIterator.Attribute LINE_HEIGHT 110 = GVTAttributedCharacterIterator.TextAttribute.LINE_HEIGHT; 111 112 public static final AttributedCharacterIterator.Attribute 113 TEXT_COMPOUND_DELIMITER 114 = GVTAttributedCharacterIterator.TextAttribute.TEXT_COMPOUND_DELIMITER; 115 116 public static final AttributedCharacterIterator.Attribute 117 VERTICAL_ORIENTATION 118 = GVTAttributedCharacterIterator.TextAttribute.VERTICAL_ORIENTATION; 119 120 public static final 121 AttributedCharacterIterator.Attribute VERTICAL_ORIENTATION_ANGLE = 122 GVTAttributedCharacterIterator.TextAttribute.VERTICAL_ORIENTATION_ANGLE; 123 124 public static final 125 AttributedCharacterIterator.Attribute HORIZONTAL_ORIENTATION_ANGLE = 126 GVTAttributedCharacterIterator.TextAttribute.HORIZONTAL_ORIENTATION_ANGLE; 127 128 private static final AttributedCharacterIterator.Attribute X 129 = GVTAttributedCharacterIterator.TextAttribute.X; 130 131 private static final AttributedCharacterIterator.Attribute Y 132 = GVTAttributedCharacterIterator.TextAttribute.Y; 133 134 private static final AttributedCharacterIterator.Attribute DX 135 = GVTAttributedCharacterIterator.TextAttribute.DX; 136 137 private static final AttributedCharacterIterator.Attribute DY 138 = GVTAttributedCharacterIterator.TextAttribute.DY; 139 140 private static final AttributedCharacterIterator.Attribute ROTATION 141 = GVTAttributedCharacterIterator.TextAttribute.ROTATION; 142 143 private static final AttributedCharacterIterator.Attribute BASELINE_SHIFT 144 = GVTAttributedCharacterIterator.TextAttribute.BASELINE_SHIFT; 145 146 private static final AttributedCharacterIterator.Attribute WRITING_MODE 147 = GVTAttributedCharacterIterator.TextAttribute.WRITING_MODE; 148 149 private static final Integer WRITING_MODE_TTB 150 = GVTAttributedCharacterIterator.TextAttribute.WRITING_MODE_TTB; 151 152 private static final Integer ORIENTATION_AUTO 153 = GVTAttributedCharacterIterator.TextAttribute.ORIENTATION_AUTO; 154 155 public static final AttributedCharacterIterator.Attribute GVT_FONT 156 = GVTAttributedCharacterIterator.TextAttribute.GVT_FONT; 157 158 static protected Set runAtts = new HashSet (); 159 160 static { 161 runAtts.add(X); 162 runAtts.add(Y); 163 runAtts.add(DX); 164 runAtts.add(DY); 165 runAtts.add(ROTATION); 166 runAtts.add(BASELINE_SHIFT); 167 } 168 169 static protected Set szAtts = new HashSet (); 170 171 static { 172 szAtts.add(TextAttribute.SIZE); 173 szAtts.add(GVT_FONT); 174 szAtts.add(LINE_HEIGHT); 175 } 176 177 178 189 public GlyphLayout(AttributedCharacterIterator aci, 190 int [] charMap, 191 Point2D offset, 192 FontRenderContext frc) { 193 194 this.aci = aci; 195 this.frc = frc; 196 this.offset = offset; 197 this.font = getFont(); 198 this.charMap = charMap; 199 200 this.metrics = font.getLineMetrics 201 (aci, aci.getBeginIndex(), aci.getEndIndex(), frc); 202 203 this.gv = null; 205 this.aci.first(); 206 this.vertical = (aci.getAttribute(WRITING_MODE) == WRITING_MODE_TTB); 207 this.textPath = (TextPath) aci.getAttribute 208 (GVTAttributedCharacterIterator.TextAttribute.TEXTPATH); 209 210 AltGlyphHandler altGlyphHandler 211 = (AltGlyphHandler)this.aci.getAttribute 212 (GVTAttributedCharacterIterator.TextAttribute.ALT_GLYPH_HANDLER); 213 if (altGlyphHandler != null) { 214 this.gv = altGlyphHandler.createGlyphVector 217 (frc, this.font.getSize(), this.aci); 218 if ( this.gv != null ){ 219 this.isAltGlyph = true; 220 } 221 } 222 if (this.gv == null) { 223 this.gv = font.createGlyphVector(frc, this.aci); 226 } 227 } 228 229 230 public GVTGlyphVector getGlyphVector() { 231 return this.gv; 232 } 233 234 235 240 public Point2D getOffset() { 241 return offset; 242 } 243 244 255 public void setScale(float xScale, float yScale, boolean adjSpacing) { 256 if (vertical) xScale = 1; 258 else yScale = 1; 259 260 if ((xScale != this.xScale) || 261 (yScale != this.yScale) || 262 (adjSpacing != this.adjSpacing)) { 263 this.xScale = xScale; 264 this.yScale = yScale; 265 this.adjSpacing = adjSpacing; 266 267 270 spacingApplied = false; 272 glyphAdvances = null; 273 pathApplied = false; 274 } 275 } 276 277 283 public void setOffset(Point2D offset) { 284 if ((offset.getX() != this.offset.getX()) || 286 (offset.getY() != this.offset.getY())) { 287 if ((layoutApplied)||(spacingApplied)) { 288 float dx = (float)(offset.getX()-this.offset.getX()); 291 float dy = (float)(offset.getY()-this.offset.getY()); 292 int numGlyphs = gv.getNumGlyphs(); 293 294 float [] gp = gv.getGlyphPositions(0, numGlyphs+1, null); 296 Point2D.Float pos = new Point2D.Float (); 297 for (int i=0; i<=numGlyphs; i++) { 298 pos.x = gp[2*i ]+dx; 299 pos.y = gp[2*i+1]+dy; 300 gv.setGlyphPosition(i, pos); 301 } 302 } 303 304 this.offset = offset; 308 309 312 pathApplied = false; 314 } 315 } 316 317 public GVTGlyphMetrics getGlyphMetrics(int glyphIndex) { 318 return gv.getGlyphMetrics(glyphIndex); 319 } 320 321 324 public boolean isVertical() { 325 return vertical; 326 } 327 328 331 public boolean isOnATextPath() { 332 return (textPath != null); 333 } 334 335 336 339 public int getGlyphCount() { 340 return gv.getNumGlyphs(); 341 } 342 343 344 353 public int getCharacterCount(int startGlyphIndex, int endGlyphIndex) { 354 return gv.getCharacterCount(startGlyphIndex, endGlyphIndex); 355 } 356 357 360 public boolean isLeftToRight() { 361 aci.first(); 362 int bidiLevel = 363 ((Integer )aci.getAttribute 364 (GVTAttributedCharacterIterator.TextAttribute.BIDI_LEVEL)) 365 .intValue(); 366 367 return ((bidiLevel&0x01) == 0); 370 } 371 372 373 377 private final void syncLayout() { 378 if (!pathApplied) { 379 doPathLayout(); 381 } 382 } 383 384 389 public void draw(Graphics2D g2d) { 390 syncLayout(); 391 gv.draw(g2d, aci); 392 } 393 394 398 public Point2D getAdvance2D() { 399 adjustTextSpacing(); 400 return advance; 401 } 402 403 404 407 public Shape getOutline() { 408 syncLayout(); 409 410 return gv.getOutline(); 411 } 412 413 public float [] getGlyphAdvances() { 414 if (glyphAdvances != null) 415 return glyphAdvances; 416 417 if (!spacingApplied) 418 adjustTextSpacing(); 420 421 int numGlyphs = gv.getNumGlyphs(); 422 float [] glyphPos = gv.getGlyphPositions(0, numGlyphs+1, null); 423 glyphAdvances = new float[numGlyphs+1]; 424 int off = 0; 425 if (isVertical()) 426 off = 1; 427 428 float start = glyphPos[off]; 429 for (int i=0; i<numGlyphs+1; i++) { 430 glyphAdvances[i] = glyphPos[i+i+off]-start; 431 } 432 return glyphAdvances; 433 } 434 435 442 public Shape getDecorationOutline(int decorationType) { 443 syncLayout(); 444 445 Shape g = new GeneralPath (); 446 if ((decorationType & DECORATION_UNDERLINE) != 0) { 447 ((GeneralPath ) g).append(getUnderlineShape(), false); 448 } 449 if ((decorationType & DECORATION_STRIKETHROUGH) != 0) { 450 ((GeneralPath ) g).append(getStrikethroughShape(), false); 451 } 452 if ((decorationType & DECORATION_OVERLINE) != 0) { 453 ((GeneralPath ) g).append(getOverlineShape(), false); 454 } 455 return g; 456 } 457 458 461 public Rectangle2D getBounds2D() { 462 syncLayout(); 463 return gv.getBounds2D(aci); 464 } 465 466 470 public Rectangle2D getGeometricBounds() { 471 syncLayout(); 472 Rectangle2D gvB, decB; 473 gvB = gv.getGeometricBounds(); 474 decB = getDecorationOutline(DECORATION_ALL).getBounds2D(); 475 return gvB.createUnion(decB); 476 } 477 478 482 public Point2D getTextPathAdvance() { 483 syncLayout(); 484 if (textPath != null) { 485 return textPathAdvance; 486 } else { 487 return getAdvance2D(); 488 } 489 } 490 491 492 500 public int getGlyphIndex(int charIndex) { 501 int numGlyphs = getGlyphCount(); 502 int j=0; 503 for (int i = 0; i < numGlyphs; i++) { 504 int count = getCharacterCount(i, i); 505 for (int n=0; n<count; n++) { 506 int glyphCharIndex = charMap[j++]; 507 if (charIndex == glyphCharIndex) 508 return i; 509 if (j >= charMap.length) 510 return -1; 511 } 512 } 513 return -1; 514 } 515 516 524 public int getLastGlyphIndex(int charIndex) { 525 int numGlyphs = getGlyphCount(); 526 int j=charMap.length-1; 527 for (int i = numGlyphs-1; i >= 0; --i) { 528 int count = getCharacterCount(i, i); 529 for (int n=0; n<count; n++) { 530 int glyphCharIndex = charMap[j--]; 531 if (charIndex == glyphCharIndex) return i; 532 if (j < 0) return -1; 533 } 534 } 535 return -1; 536 } 537 538 539 543 public double getComputedOrientationAngle(int index){ 544 545 if ( isGlyphOrientationAuto() ){ 546 if (isVertical()) { 547 char ch = aci.setIndex(index); 548 if (isLatinChar(ch)) 549 return 90.0; 550 else 551 return 0.0; 552 } 553 return 0.0; 554 } 555 else{ 556 return getGlyphOrientationAngle(); 557 } 558 } 559 560 570 public Shape getHighlightShape(int beginCharIndex, int endCharIndex) { 571 syncLayout(); 572 573 if (beginCharIndex > endCharIndex) { 574 int temp = beginCharIndex; 575 beginCharIndex = endCharIndex; 576 endCharIndex = temp; 577 } 578 GeneralPath shape = null; 579 int numGlyphs = getGlyphCount(); 580 581 Point2D.Float [] topPts = new Point2D.Float [2*numGlyphs]; 582 Point2D.Float [] botPts = new Point2D.Float [2*numGlyphs]; 583 584 int ptIdx = 0; 585 586 int currentChar = 0; 587 for (int i = 0; i < numGlyphs; i++) { 588 int glyphCharIndex = charMap[currentChar]; 589 if ((glyphCharIndex >= beginCharIndex) && 590 (glyphCharIndex <= endCharIndex) && 591 gv.isGlyphVisible(i)) { 592 593 Shape gbounds = gv.getGlyphLogicalBounds(i); 594 if (gbounds != null) { 595 if (shape == null) 597 shape = new GeneralPath (); 598 599 float [] pts = new float[6]; 603 int count = 0; 604 int type = -1; 605 606 PathIterator pi = gbounds.getPathIterator(null); 607 Point2D.Float firstPt = null; 608 609 while (!pi.isDone()) { 610 type = pi.currentSegment(pts); 611 if ((type == PathIterator.SEG_MOVETO) || 612 (type == PathIterator.SEG_LINETO)) { 613 if (count > 4) break; if (count == 4) { 616 if ((firstPt == null) || 618 (firstPt.x != pts[0]) || 619 (firstPt.y != pts[1])) 620 break; 621 } else { 622 Point2D.Float pt; 623 pt = new Point2D.Float (pts[0], pts[1]); 624 if (count == 0) firstPt = pt; 625 switch (count) { 627 case 0: botPts[ptIdx] = pt; break; 628 case 1: topPts[ptIdx] = pt; break; 629 case 2: topPts[ptIdx+1] = pt; break; 630 case 3: botPts[ptIdx+1] = pt; break; 631 } 632 } 633 } else if (type == PathIterator.SEG_CLOSE) { 634 if ((count < 4) || (count > 5)) break; 636 } else { 637 break; 639 } 640 641 count++; 642 pi.next(); 643 } 644 if (pi.isDone()) { 645 if ((botPts[ptIdx]!=null) && 647 ((topPts[ptIdx].x != topPts[ptIdx+1].x) || 648 (topPts[ptIdx].y != topPts[ptIdx+1].y))) 649 ptIdx += 2; 651 } else { 652 addPtsToPath(shape, topPts, botPts, ptIdx); 657 ptIdx = 0; 658 shape.append(gbounds, false); 659 } 660 } 661 } 662 currentChar += getCharacterCount(i, i); 663 if (currentChar >= charMap.length) 664 currentChar = charMap.length-1; 665 } 666 addPtsToPath(shape, topPts, botPts, ptIdx); 667 668 return shape; 669 } 670 671 public static final float eps = 0.00001f; 672 public static boolean epsEQ(double a, double b) { 673 return ((a+eps > b) && (a-eps < b)); 674 } 675 676 public static int makeConvexHull(Point2D.Float [] pts, int numPts) { 677 Point2D.Float tmp; 679 for (int i=1; i<numPts; i++) { 681 if ((pts[i].x < pts[i-1].x) || 684 ((pts[i].x == pts[i-1].x) && (pts[i].y < pts[i-1].y))) { 685 tmp = pts[i]; 686 pts[i] = pts[i-1]; 687 pts[i-1] = tmp; 688 i=0; 689 continue; 690 } 691 } 692 693 695 Point2D.Float pt0 = pts[0]; 696 Point2D.Float pt1 = pts[numPts-1]; 697 Point2D.Float dxdy = new Point2D.Float (pt1.x-pt0.x, pt1.y-pt0.y); 698 float soln, c = dxdy.y*pt0.x-dxdy.x*pt0.y; 699 700 Point2D.Float [] topList = new Point2D.Float [numPts]; 701 Point2D.Float [] botList = new Point2D.Float [numPts]; 702 botList[0] = topList[0] = pts[0]; 703 int nTopPts=1; 704 int nBotPts=1; 705 for (int i=1; i<numPts-1; i++) { 706 Point2D.Float pt = pts[i]; 707 soln = dxdy.x*pt.y-dxdy.y*pt.x+c; 708 if (soln < 0) { 709 while (nBotPts >= 2) { 711 pt0 = botList[nBotPts-2]; 712 pt1 = botList[nBotPts-1]; 713 float dx = pt1.x-pt0.x; 714 float dy = pt1.y-pt0.y; 715 float c0 = dy*pt0.x-dx*pt0.y; 716 soln = dx*pt.y-dy*pt.x+c0; 717 if (soln > eps) break; 719 if (soln > -eps) { 720 if (pt1.y < pt.y) pt = pt1; 722 nBotPts--; 723 break; 724 } 725 nBotPts--; 727 } 728 botList[nBotPts++] = pt; 729 } else { 730 while (nTopPts >= 2) { 732 pt0 = topList[nTopPts-2]; 733 pt1 = topList[nTopPts-1]; 734 float dx = pt1.x-pt0.x; 735 float dy = pt1.y-pt0.y; 736 float c0 = dy*pt0.x-dx*pt0.y; 737 soln = dx*pt.y-dy*pt.x+c0; 738 if (soln < -eps) break; 740 if (soln < eps) { 741 if (pt1.y > pt.y) pt = pt1; 743 nTopPts--; 744 break; 745 } 746 nTopPts--; 748 } 749 topList[nTopPts++] = pt; 750 } 751 } 752 753 Point2D.Float pt = pts[numPts-1]; 755 while (nBotPts >= 2) { 756 pt0 = botList[nBotPts-2]; 757 pt1 = botList[nBotPts-1]; 758 float dx = pt1.x-pt0.x; 759 float dy = pt1.y-pt0.y; 760 float c0 = dy*pt0.x-dx*pt0.y; 761 soln = dx*pt.y-dy*pt.x+c0; 762 if (soln > eps) 763 break; 765 if (soln > -eps) { 766 if (pt1.y >= pt.y) nBotPts--; 768 break; 769 } 770 nBotPts--; 772 } 773 774 while (nTopPts >= 2) { 775 pt0 = topList[nTopPts-2]; 776 pt1 = topList[nTopPts-1]; 777 float dx = pt1.x-pt0.x; 778 float dy = pt1.y-pt0.y; 779 float c0 = dy*pt0.x-dx*pt0.y; 780 soln = dx*pt.y-dy*pt.x+c0; 781 if (soln < -eps) 782 break; 784 if (soln < eps) { 785 if (pt1.y <= pt.y) nTopPts--; 787 break; 788 } 789 nTopPts--; 791 } 792 793 int i=0; 794 for (; i<nTopPts; i++) 795 pts[i] = topList[i]; 796 797 pts[i++] = pts[numPts-1]; 799 800 for (int n=nBotPts-1; n>0; n--, i++) 802 pts[i] = botList[n]; 803 804 return i; 806 } 807 808 public static void addPtsToPath(GeneralPath shape, 809 Point2D.Float [] topPts, 810 Point2D.Float [] botPts, 811 int numPts) { 812 if (numPts < 2) return; 813 if (numPts == 2) { 814 shape.moveTo(topPts[0].x, topPts[0].y); 815 shape.lineTo(topPts[1].x, topPts[1].y); 816 shape.lineTo(botPts[1].x, botPts[1].y); 817 shape.lineTo(botPts[0].x, botPts[0].y); 818 shape.lineTo(topPts[0].x, topPts[0].y); 819 return; 820 } 821 822 Point2D.Float [] boxes = new Point2D.Float [8]; 831 Point2D.Float [] chull = new Point2D.Float [8]; 832 boxes[4] = topPts[0]; 833 boxes[5] = topPts[1]; 834 boxes[6] = botPts[1]; 835 boxes[7] = botPts[0]; 836 Area []areas = new Area [numPts/2]; 837 int nAreas =0; 838 for (int i=2; i<numPts; i+=2) { 839 boxes[0] = boxes[4]; 840 boxes[1] = boxes[5]; 841 boxes[2] = boxes[6]; 842 boxes[3] = boxes[7]; 843 boxes[4] = topPts[i]; 844 boxes[5] = topPts[i+1]; 845 boxes[6] = botPts[i+1]; 846 boxes[7] = botPts[i]; 847 848 float delta,sz,dist; 849 delta = boxes[2].x-boxes[0].x; 850 dist = delta*delta; 851 delta = boxes[2].y-boxes[0].y; 852 dist += delta*delta; 853 sz = (float)Math.sqrt(dist); 854 855 delta = boxes[6].x-boxes[4].x; 856 dist = delta*delta; 857 delta = boxes[6].y-boxes[4].y; 858 dist += delta*delta; 859 sz += (float)Math.sqrt(dist); 860 861 delta = ((boxes[0].x+boxes[1].x+boxes[2].x+boxes[3].x)- 862 (boxes[4].x+boxes[5].x+boxes[6].x+boxes[7].x))/4; 863 dist = delta*delta; 864 delta = ((boxes[0].y+boxes[1].y+boxes[2].y+boxes[3].y)- 865 (boxes[4].y+boxes[5].y+boxes[6].y+boxes[7].y))/4; 866 dist += delta*delta; 867 dist = (float)Math.sqrt(dist); 868 GeneralPath gp = new GeneralPath (); 879 if (dist < sz) { 880 System.arraycopy(boxes, 0, chull, 0, 8); 882 int npts = makeConvexHull(chull, 8); 883 gp.moveTo(chull[0].x, chull[0].y); 884 for(int n=1; n<npts; n++) 885 gp.lineTo(chull[n].x, chull[n].y); 886 gp.closePath(); 887 } else { 888 mergeAreas(shape, areas, nAreas); 890 nAreas = 0; 892 if (i==2) { 894 gp.moveTo(boxes[0].x, boxes[0].y); 895 gp.lineTo(boxes[1].x, boxes[1].y); 896 gp.lineTo(boxes[2].x, boxes[2].y); 897 gp.lineTo(boxes[3].x, boxes[3].y); 898 gp.closePath(); 899 shape.append(gp, false); 900 gp.reset(); 901 } 902 gp.moveTo(boxes[4].x, boxes[4].y); 903 gp.lineTo(boxes[5].x, boxes[5].y); 904 gp.lineTo(boxes[6].x, boxes[6].y); 905 gp.lineTo(boxes[7].x, boxes[7].y); 906 gp.closePath(); 907 } 908 areas[nAreas++] = new Area (gp); 909 } 910 911 mergeAreas(shape, areas, nAreas); 912 } 913 914 public static void mergeAreas(GeneralPath shape, 915 Area []shapes, int nShapes) { 916 while (nShapes > 1) { 921 int n=0; 922 for (int i=1; i<nShapes;i+=2) { 923 shapes[i-1].add(shapes[i]); 924 shapes[n++] = shapes[i-1]; 925 shapes[i] = null; 926 } 927 928 if ((nShapes&0x1) == 1) 930 shapes[n-1].add(shapes[nShapes-1]); 931 nShapes = nShapes/2; 932 } 933 if (nShapes == 1) 934 shape.append(shapes[0], false); 935 } 936 937 947 public TextHit hitTestChar(float x, float y) { 948 syncLayout(); 949 950 TextHit textHit = null; 951 952 int currentChar = 0; 953 for (int i = 0; i < gv.getNumGlyphs(); i++) { 954 Shape gbounds = gv.getGlyphLogicalBounds(i); 955 if (gbounds != null) { 956 Rectangle2D gbounds2d = gbounds.getBounds2D(); 957 if (gbounds.contains(x, y)) { 960 boolean isRightHalf = 961 (x > (gbounds2d.getX()+(gbounds2d.getWidth()/2d))); 962 boolean isLeadingEdge = !isRightHalf; 963 int charIndex = charMap[currentChar]; 964 textHit = new TextHit(charIndex, isLeadingEdge); 965 return textHit; 966 } 967 } 968 currentChar += getCharacterCount(i, i); 969 if (currentChar >= charMap.length) 970 currentChar = charMap.length-1; 971 } 972 return textHit; 973 } 974 975 977 984 protected GVTFont getFont() { 985 aci.first(); 986 GVTFont gvtFont = (GVTFont)aci.getAttributes().get(GVT_FONT); 987 988 if (gvtFont != null) 989 return gvtFont; 990 991 return new AWTGVTFont(aci.getAttributes()); 993 } 994 995 998 protected Shape getOverlineShape() { 999 double y = metrics.getOverlineOffset(); 1000 float overlineThickness = metrics.getOverlineThickness(); 1001 1002 y += overlineThickness; 1005 1006 aci.first(); 1008 Float dy = (Float ) aci.getAttribute(DY); 1009 if (dy != null) 1010 y += dy.floatValue(); 1011 1012 Stroke overlineStroke = 1013 new BasicStroke (overlineThickness); 1014 Rectangle2D logicalBounds = gv.getLogicalBounds(); 1015 1016 return overlineStroke.createStrokedShape( 1017 new java.awt.geom.Line2D.Double( 1018 logicalBounds.getMinX() + overlineThickness/2.0, offset.getY()+y, 1019 logicalBounds.getMaxX() - overlineThickness/2.0, offset.getY()+y)); 1020 } 1021 1022 1025 protected Shape getUnderlineShape() { 1026 1027 double y = metrics.getUnderlineOffset(); 1028 float underlineThickness = metrics.getUnderlineThickness(); 1029 1030 y += underlineThickness*1.5; 1033 1034 BasicStroke underlineStroke = 1035 new BasicStroke (underlineThickness); 1036 1037 aci.first(); 1039 Float dy = (Float ) aci.getAttribute(DY); 1040 if (dy != null) 1041 y += dy.floatValue(); 1042 1043 Rectangle2D logicalBounds = gv.getLogicalBounds(); 1044 1045 return underlineStroke.createStrokedShape( 1046 new java.awt.geom.Line2D.Double( 1047 logicalBounds.getMinX() + underlineThickness/2.0, offset.getY()+y, 1048 logicalBounds.getMaxX() - underlineThickness/2.0, offset.getY()+y)); 1049 } 1050 1051 1054 protected Shape getStrikethroughShape() { 1055 double y = metrics.getStrikethroughOffset(); 1056 float strikethroughThickness = metrics.getStrikethroughThickness(); 1057 1058 Stroke strikethroughStroke = 1059 new BasicStroke (strikethroughThickness); 1060 1061 aci.first(); 1063 Float dy = (Float ) aci.getAttribute(DY); 1064 if (dy != null) 1065 y += dy.floatValue(); 1066 1067 Rectangle2D logicalBounds = gv.getLogicalBounds(); 1068 return strikethroughStroke.createStrokedShape( 1069 new java.awt.geom.Line2D.Double( 1070 logicalBounds.getMinX() + strikethroughThickness/2.0, offset.getY()+y, 1071 logicalBounds.getMaxX() - strikethroughThickness/2.0, offset.getY()+y)); 1072 } 1073 1074 1080 protected void doExplicitGlyphLayout() { 1081 1082 this.gv.performDefaultLayout(); 1083 1084 float baselineAscent 1085 = vertical ? 1086 (float) gv.getLogicalBounds().getWidth() : 1087 (metrics.getAscent() + Math.abs(metrics.getDescent())); 1088 1089 int numGlyphs = gv.getNumGlyphs(); 1090 1092 float[] gp = gv.getGlyphPositions(0, numGlyphs+1, null); 1093 float verticalFirstOffset = 0f; 1094 float horizontalFirstOffset = 0f; 1095 1096 boolean glyphOrientationAuto = isGlyphOrientationAuto(); 1097 int glyphOrientationAngle = 0; 1098 if (!glyphOrientationAuto) { 1099 glyphOrientationAngle = getGlyphOrientationAngle(); 1100 } 1101 int i=0; 1102 int aciStart = aci.getBeginIndex(); 1103 int aciIndex = 0; 1104 char ch = aci.first(); 1105 int runLimit = aciIndex+aciStart; 1106 1107 Float x=null, y=null, dx=null, dy=null, rotation=null; 1108 Object baseline=null; 1109 1110 float shift_x_pos = 0; 1111 float shift_y_pos = 0; 1112 float curr_x_pos = (float)offset.getX(); 1113 float curr_y_pos = (float)offset.getY(); 1114 1115 Point2D.Float pos = new Point2D.Float (); 1116 boolean hasArabicTransparent = false; 1117 1118 while (i < numGlyphs) { 1119 if (aciIndex+aciStart >= runLimit) { 1121 runLimit = aci.getRunLimit(runAtts); 1122 x = (Float ) aci.getAttribute(X); 1123 y = (Float ) aci.getAttribute(Y); 1124 dx = (Float ) aci.getAttribute(DX); 1125 dy = (Float ) aci.getAttribute(DY); 1126 rotation = (Float ) aci.getAttribute(ROTATION); 1127 baseline = aci.getAttribute(BASELINE_SHIFT); 1128 } 1129 1130 GVTGlyphMetrics gm = gv.getGlyphMetrics(i); 1131 1132 if (textPath == null) { 1133 if (i==0) { 1136 if (isVertical()) { 1137 if (glyphOrientationAuto) { 1138 if (isLatinChar(ch)) { 1139 verticalFirstOffset = 0f; 1141 } else { 1142 verticalFirstOffset = 1144 (float)gm.getBounds2D().getHeight(); 1145 } 1146 } else { 1147 if (glyphOrientationAngle == 0) { 1148 verticalFirstOffset = 1149 (float)gm.getBounds2D().getHeight(); 1150 } else { 1151 verticalFirstOffset = 0f; 1153 } 1154 } 1155 } else { 1156 if ((glyphOrientationAngle == 270)) { 1157 horizontalFirstOffset = 1158 (float)gm.getBounds2D().getHeight(); 1159 } else { 1160 horizontalFirstOffset = 0; 1162 } 1163 } 1164 } else { 1165 if (glyphOrientationAuto && 1166 (verticalFirstOffset == 0f) 1167 && !isLatinChar(ch)) { 1168 verticalFirstOffset = 1169 (float)gm.getBounds2D().getHeight(); 1170 } 1171 } 1172 } 1173 float ox = 0f; 1176 float oy = 0f; 1177 float glyphOrientationRotation = 0f; 1178 float glyphRotation = 0f; 1179 1180 1181 if (ch != CharacterIterator.DONE) { 1182 if (vertical) { 1183 if (glyphOrientationAuto) { 1184 if (isLatinChar(ch)) { 1185 glyphOrientationRotation = (float) (Math.PI / 2f); 1188 } else { 1189 glyphOrientationRotation = 0f; 1190 } 1191 } else { 1192 glyphOrientationRotation = (float)Math.toRadians(glyphOrientationAngle); 1193 } 1194 if (textPath != null) { 1195 x = null; 1197 } 1198 } else { 1199 glyphOrientationRotation = (float)Math.toRadians(glyphOrientationAngle); 1200 if (textPath != null) { 1201 y = null; 1203 } 1204 } 1205 1206 if (rotation == null || rotation.isNaN()) { 1208 glyphRotation = glyphOrientationRotation; 1209 } else { 1210 glyphRotation = (rotation.floatValue() + 1211 glyphOrientationRotation); 1212 } 1213 1214 if ((x != null) && !x.isNaN()) { 1215 if (i == 0) 1216 shift_x_pos = (float)(x.floatValue()-offset.getX()); 1217 curr_x_pos = x.floatValue()-shift_x_pos; 1218 } 1219 if (dx != null && !dx.isNaN()) { 1220 curr_x_pos += dx.floatValue(); 1221 } 1222 1223 if ((y != null) && !y.isNaN()) { 1224 if (i == 0) 1225 shift_y_pos = (float)(y.floatValue()-offset.getY()); 1226 curr_y_pos = y.floatValue()-shift_y_pos; 1227 } 1228 if (dy != null && !dy.isNaN()) { 1229 curr_y_pos += dy.floatValue(); 1230 } else if (i > 0) { 1231 curr_y_pos += gp[i*2 + 1]-gp[i*2 - 1]; 1232 } 1233 1234 float baselineAdjust = 0f; 1235 if (baseline != null) { 1236 if (baseline instanceof Integer ) { 1237 if (baseline==TextAttribute.SUPERSCRIPT_SUPER) { 1238 baselineAdjust = baselineAscent*0.5f; 1239 } else if (baseline==TextAttribute.SUPERSCRIPT_SUB) { 1240 baselineAdjust = -baselineAscent*0.5f; 1241 } 1242 } else if (baseline instanceof Float ) { 1243 baselineAdjust = ((Float ) baseline).floatValue(); 1244 } 1245 if (vertical) { 1246 ox = baselineAdjust; 1247 } else { 1248 oy = -baselineAdjust; 1249 } 1250 } 1251 1252 if (vertical) { 1253 oy += verticalFirstOffset; 1255 1256 if (glyphOrientationAuto) { 1257 if (isLatinChar(ch)) { 1258 ox += metrics.getStrikethroughOffset(); 1259 } else { 1260 Rectangle2D glyphBounds 1261 = gv.getGlyphVisualBounds(i).getBounds2D(); 1262 ox -= (float)((glyphBounds.getMaxX() - gp[2*i]) - 1263 glyphBounds.getWidth()/2); 1264 } 1265 } else { 1266 Rectangle2D glyphBounds 1268 = gv.getGlyphVisualBounds(i).getBounds2D(); 1269 if (glyphOrientationAngle == 0) { 1270 ox -= (float)((glyphBounds.getMaxX() - gp[2*i]) - 1271 glyphBounds.getWidth()/2); 1272 } else if (glyphOrientationAngle == 180) { 1273 ox += (float)((glyphBounds.getMaxX() - gp[2*i]) - 1274 glyphBounds.getWidth()/2); 1275 } else if (glyphOrientationAngle == 90) { 1276 ox += metrics.getStrikethroughOffset(); 1277 } else { ox -= metrics.getStrikethroughOffset(); 1279 } 1280 } 1281 } else { 1282 ox += horizontalFirstOffset; 1283 if (glyphOrientationAngle == 90) { 1284 oy -= gm.getHorizontalAdvance(); 1285 } else if (glyphOrientationAngle == 180) { 1286 oy -= metrics.getAscent(); 1287 } 1288 } 1289 } 1290 1291 pos.x = curr_x_pos+ox; 1293 pos.y = curr_y_pos+oy; 1294 gv.setGlyphPosition(i, pos); 1295 1296 if (ArabicTextHandler.arabicCharTransparent(ch)) { 1298 hasArabicTransparent = true; 1299 } else { 1300 if (vertical) { 1302 float advanceY = 0; 1303 if (glyphOrientationAuto) { 1304 if (isLatinChar(ch)) { 1305 advanceY = gm.getHorizontalAdvance(); 1306 } else { 1307 advanceY = gm.getVerticalAdvance(); 1308 } 1309 } else { 1310 if ((glyphOrientationAngle == 0) || 1311 (glyphOrientationAngle == 180)) { 1312 advanceY = gm.getVerticalAdvance(); 1313 } else if (glyphOrientationAngle == 90) { 1314 advanceY = gm.getHorizontalAdvance(); 1315 } else { advanceY = gm.getHorizontalAdvance(); 1317 gv.setGlyphTransform 1320 (i, AffineTransform.getTranslateInstance 1321 (0, advanceY)); 1322 } 1323 } 1324 curr_y_pos += advanceY; 1325 } else { 1326 float advanceX = 0; 1327 if (glyphOrientationAngle == 0) { 1328 advanceX = gm.getHorizontalAdvance(); 1329 } else if (glyphOrientationAngle == 180) { 1330 advanceX = gm.getHorizontalAdvance(); 1331 gv.setGlyphTransform 1334 (i, AffineTransform.getTranslateInstance 1335 (advanceX, 0)); 1336 } else { 1337 advanceX = gm.getVerticalAdvance(); 1339 } 1340 curr_x_pos += advanceX; 1341 } 1342 } 1343 1344 if (!epsEQ(glyphRotation,0)) { 1346 AffineTransform glyphTransform = gv.getGlyphTransform(i); 1347 if (glyphTransform == null) { 1348 glyphTransform = new AffineTransform (); 1349 } 1350 AffineTransform rotAt; 1351 if (epsEQ(glyphRotation, Math.PI/2)) { 1354 rotAt = new AffineTransform (0, 1, -1, 0, 0, 0); 1355 } else if (epsEQ(glyphRotation, Math.PI)) { 1356 rotAt = new AffineTransform (-1, 0, 0, -1, 0, 0); 1357 } else if (epsEQ(glyphRotation, 3*Math.PI/2)) { 1358 rotAt = new AffineTransform (0, -1, 1, 0, 0, 0); 1359 } else { 1360 rotAt = AffineTransform.getRotateInstance(glyphRotation); 1361 } 1362 glyphTransform.concatenate(rotAt); 1363 gv.setGlyphTransform(i, glyphTransform); 1364 } 1365 1366 aciIndex += gv.getCharacterCount(i,i); 1367 if (aciIndex >= charMap.length) 1368 aciIndex = charMap.length-1; 1369 ch = aci.setIndex(aciIndex+aciStart); 1370 i++; 1371 } 1372 pos.x = curr_x_pos; 1374 pos.y = curr_y_pos; 1375 gv.setGlyphPosition(i, pos); 1376 1377 advance = new Point2D.Float ((float)(curr_x_pos - offset.getX()), 1378 (float)(curr_y_pos - offset.getY())); 1379 1380 1381 if (hasArabicTransparent) { 1384 ch = aci.first(); 1385 aciIndex = 0; 1386 i=0; 1387 int transparentStart = -1; 1388 while (i < numGlyphs) { 1389 if (ArabicTextHandler.arabicCharTransparent(ch)) { 1390 if (transparentStart == -1) transparentStart = i; 1391 } else { 1392 if (transparentStart != -1) { 1393 Point2D loc = gv.getGlyphPosition(i); 1394 GVTGlyphMetrics gm = gv.getGlyphMetrics(i); 1395 int tyS=0, txS=0; 1396 float advX=0, advY=0; 1397 if (vertical) { 1398 if (glyphOrientationAuto || 1399 (glyphOrientationAngle == 90)) 1400 advY = gm.getHorizontalAdvance(); 1401 else if (glyphOrientationAngle == 270) 1402 advY = 0; 1403 else if (glyphOrientationAngle == 0) 1404 advX = gm.getHorizontalAdvance(); 1405 else advX = -gm.getHorizontalAdvance(); 1407 } else { 1408 if (glyphOrientationAngle == 0) 1409 advX = gm.getHorizontalAdvance(); 1410 else if (glyphOrientationAngle == 90) 1411 advY = gm.getHorizontalAdvance(); 1412 else if (glyphOrientationAngle == 180) 1413 advX = 0; 1414 else advY = -gm.getHorizontalAdvance(); 1416 } 1417 float baseX = (float)(loc.getX()+advX); 1418 float baseY = (float)(loc.getY()+advY); 1419 for (int j=transparentStart; j<i; j++) { 1420 Point2D locT = gv.getGlyphPosition(j); 1421 GVTGlyphMetrics gmT = gv.getGlyphMetrics(j); 1422 float locX = (float)locT.getX(); 1423 float locY = (float)locT.getY(); 1424 float tx=0, ty=0; 1425 float advT = gmT.getHorizontalAdvance(); 1426 if (vertical) { 1427 if (glyphOrientationAuto || 1428 (glyphOrientationAngle == 90)) 1429 locY = baseY-advT; 1430 else if (glyphOrientationAngle == 270) 1431 locY = baseY+advT; 1432 else if (glyphOrientationAngle == 0) 1433 locX = baseX-advT; 1434 else locX = baseX+advT; 1436 } else { 1437 if (glyphOrientationAngle == 0) 1438 locX = baseX-advT; 1439 else if (glyphOrientationAngle == 90) 1440 locY = baseY-advT; 1441 else if (glyphOrientationAngle == 180) 1442 locX = baseX+advT; 1443 else locY = baseY+advT; 1445 } 1446 1447 locT = new Point2D.Double (locX, locY); 1448 gv.setGlyphPosition(j, locT); 1449 if ((txS != 0) || (tyS != 0)) { 1450 AffineTransform at; 1451 at = AffineTransform.getTranslateInstance 1452 (tx,ty); 1453 at.concatenate(gv.getGlyphTransform(i)); 1454 gv.setGlyphTransform(i, at); 1455 } 1456 } 1457 transparentStart = -1; 1458 } 1459 } 1460 aciIndex += gv.getCharacterCount(i,i); 1461 if (aciIndex >= charMap.length) 1462 aciIndex = charMap.length-1; 1463 ch = aci.setIndex(aciIndex+aciStart); 1464 i++; 1465 } 1466 1467 } 1468 1469 1470 layoutApplied = true; 1471 spacingApplied = false; 1472 glyphAdvances = null; 1473 pathApplied = false; 1474 } 1475 1476 1479 protected void adjustTextSpacing() { 1480 1481 if (spacingApplied) 1482 return; 1484 1485 if (!layoutApplied) 1486 doExplicitGlyphLayout(); 1488 1489 aci.first(); 1490 Boolean customSpacing = (Boolean ) aci.getAttribute( 1491 GVTAttributedCharacterIterator.TextAttribute.CUSTOM_SPACING); 1492 if ((customSpacing != null) && customSpacing.booleanValue()) { 1493 advance = doSpacing 1494 ((Float ) aci.getAttribute 1495 (GVTAttributedCharacterIterator.TextAttribute.KERNING), 1496 (Float ) aci.getAttribute 1497 (GVTAttributedCharacterIterator.TextAttribute.LETTER_SPACING), 1498 (Float ) aci.getAttribute 1499 (GVTAttributedCharacterIterator.TextAttribute.WORD_SPACING)); 1500 layoutApplied = false; 1502 } 1503 1504 applyStretchTransform(!adjSpacing); 1507 1508 spacingApplied = true; 1509 pathApplied = false; 1510 } 1511 1512 1520 protected Point2D doSpacing(Float kern, 1521 Float letterSpacing, 1522 Float wordSpacing) { 1523 boolean autoKern = true; 1524 boolean doWordSpacing = false; 1525 boolean doLetterSpacing = false; 1526 float kernVal = 0f; 1527 float letterSpacingVal = 0f; 1528 1529 if ((kern != null) && (!kern.isNaN())) { 1530 kernVal = kern.floatValue(); 1531 autoKern = false; 1532 } 1534 if ((letterSpacing != null) && (!letterSpacing.isNaN())) { 1535 letterSpacingVal = letterSpacing.floatValue(); 1536 doLetterSpacing = true; 1537 } 1539 if ((wordSpacing != null) && (!wordSpacing.isNaN())) { 1540 doWordSpacing = true; 1541 } 1542 1543 int numGlyphs = gv.getNumGlyphs(); 1544 1545 float dx = 0f; 1546 float dy = 0f; 1547 Point2D newPositions[] = new Point2D [numGlyphs+1]; 1548 Point2D prevPos = gv.getGlyphPosition(0); 1549 int prevCode = gv.getGlyphCode(0); 1550 float x = (float) prevPos.getX(); 1551 float y = (float) prevPos.getY(); 1552 1553 Point2D lastCharAdvance 1554 = new Point2D.Double (advance.getX() - (gv.getGlyphPosition(numGlyphs-1).getX() - x), 1555 advance.getY() - (gv.getGlyphPosition(numGlyphs-1).getY() - y)); 1556 1557 try { 1558 GVTFont font = gv.getFont(); 1559 if ((numGlyphs > 1) && (doLetterSpacing || !autoKern)) { 1561 for (int i=1; i<=numGlyphs; ++i) { 1562 Point2D gpos = gv.getGlyphPosition(i); 1563 int currCode; 1564 currCode = (i == numGlyphs)?-1:gv.getGlyphCode(i); 1565 dx = (float)gpos.getX()-(float)prevPos.getX(); 1566 dy = (float)gpos.getY()-(float)prevPos.getY(); 1567 if (autoKern) { 1568 if (vertical) dy += letterSpacingVal; 1569 else dx += letterSpacingVal; 1570 } else { 1571 if (vertical) { 1574 float vKern = 0; 1575 if (currCode != -1) 1576 vKern = font.getVKern(prevCode, currCode); 1577 dy += kernVal - vKern + letterSpacingVal; 1578 } else { 1579 float hKern = 0; 1580 if (currCode != -1) 1581 hKern = font.getHKern(prevCode, currCode); 1582 dx += kernVal - hKern + letterSpacingVal; 1583 } 1584 } 1585 x += dx; 1586 y += dy; 1587 newPositions[i] = new Point2D.Float (x, y); 1588 prevPos = gpos; 1589 prevCode = currCode; 1590 } 1591 1592 for (int i=1; i<=numGlyphs; ++i) { if (newPositions[i] != null) { 1594 gv.setGlyphPosition(i, newPositions[i]); 1595 } 1596 } 1597 } 1598 1599 if (vertical) { 1601 lastCharAdvance.setLocation 1602 (lastCharAdvance.getX(), 1603 lastCharAdvance.getY() + kernVal + letterSpacingVal); 1604 } else { 1605 lastCharAdvance.setLocation 1606 (lastCharAdvance.getX() + kernVal + letterSpacingVal, 1607 lastCharAdvance.getY()); 1608 } 1609 1610 dx = 0f; 1612 dy = 0f; 1613 prevPos = gv.getGlyphPosition(0); 1614 x = (float) prevPos.getX(); 1615 y = (float) prevPos.getY(); 1616 1617 if ((numGlyphs > 1) && (doWordSpacing)) { 1618 for (int i = 1; i < numGlyphs; i++) { 1619 Point2D gpos = gv.getGlyphPosition(i); 1620 dx = (float)gpos.getX()-(float)prevPos.getX(); 1621 dy = (float)gpos.getY()-(float)prevPos.getY(); 1622 boolean inWS = false; 1623 int beginWS = i; 1625 int endWS = i; 1626 GVTGlyphMetrics gm = gv.getGlyphMetrics(i); 1627 1628 while ((gm.getBounds2D().getWidth()<0.01d) || gm.isWhitespace()) { 1630 if (!inWS) inWS = true; 1631 if (i == numGlyphs-1) { 1632 break; 1634 } 1635 ++i; 1636 ++endWS; 1637 gpos = gv.getGlyphPosition(i); 1638 gm = gv.getGlyphMetrics(i); 1639 } 1640 1641 if ( inWS ) { int nWS = endWS-beginWS; 1643 float px = (float) prevPos.getX(); 1644 float py = (float) prevPos.getY(); 1645 dx = (float) (gpos.getX() - px)/(nWS+1); 1646 dy = (float) (gpos.getY() - py)/(nWS+1); 1647 if (vertical) { 1648 dy += wordSpacing.floatValue()/(nWS+1); 1649 } else { 1650 dx += wordSpacing.floatValue()/(nWS+1); 1651 } 1652 for (int j=beginWS; j<=endWS; ++j) { 1653 x += dx; 1654 y += dy; 1655 newPositions[j] = new Point2D.Float (x, y); 1656 } 1657 } else { 1658 dx = (float) (gpos.getX()-prevPos.getX()); 1659 dy = (float) (gpos.getY()-prevPos.getY()); 1660 x += dx; 1661 y += dy; 1662 newPositions[i] = new Point2D.Float (x, y); 1663 } 1664 prevPos = gpos; 1665 } 1666 Point2D gPos = gv.getGlyphPosition(numGlyphs); 1667 x += (float) (gPos.getX()-prevPos.getX()); 1668 y += (float) (gPos.getY()-prevPos.getY()); 1669 newPositions[numGlyphs] = new Point2D.Float (x, y); 1670 1671 for (int i=1; i<=numGlyphs; ++i) { if (newPositions[i] != null) { 1673 gv.setGlyphPosition(i, newPositions[i]); 1674 } 1675 } 1676 } 1677 1678 } catch (Exception e) { 1679 e.printStackTrace(); 1680 } 1681 1682 double advX = gv.getGlyphPosition(numGlyphs-1).getX() 1684 - gv.getGlyphPosition(0).getX(); 1685 double advY = gv.getGlyphPosition(numGlyphs-1).getY() 1686 - gv.getGlyphPosition(0).getY(); 1687 Point2D newAdvance = new Point2D.Double (advX + lastCharAdvance.getX(), 1688 advY + lastCharAdvance.getY()); 1689 return newAdvance; 1690 } 1691 1692 1698 protected void applyStretchTransform(boolean stretchGlyphs) { 1699 if ((xScale == 1) && (yScale==1)) 1700 return; 1701 1702 AffineTransform scaleAT = 1703 AffineTransform.getScaleInstance(xScale, yScale); 1704 1705 int numGlyphs = gv.getNumGlyphs(); 1706 float [] gp = gv.getGlyphPositions(0, numGlyphs+1, null); 1707 1708 float initX = gp[0]; 1709 float initY = gp[1]; 1710 float dx = 0f; 1711 float dy = 0f; 1712 Point2D.Float pos = new Point2D.Float (); 1713 for (int i = 0; i <= numGlyphs; i++) { 1714 dx = gp[2*i] -initX; 1715 dy = gp[2*i+1]-initY; 1716 pos.x = initX+dx*xScale; 1717 pos.y = initY+dy*yScale; 1718 gv.setGlyphPosition(i, pos); 1719 1720 if ((stretchGlyphs) && (i != numGlyphs)) { 1721 AffineTransform glyphTransform = gv.getGlyphTransform(i); 1723 if (glyphTransform != null) { 1724 glyphTransform.preConcatenate(scaleAT); 1725 gv.setGlyphTransform(i, glyphTransform); 1726 } else { 1727 gv.setGlyphTransform (i, scaleAT); 1728 } 1729 } 1730 } 1731 1732 advance = new Point2D.Float ((float)(advance.getX()*xScale), 1733 (float)(advance.getY()*yScale)); 1734 layoutApplied = false; 1736 } 1737 1738 1742 protected void doPathLayout() { 1743 if (pathApplied) 1744 return; 1745 1746 if (!spacingApplied) 1747 adjustTextSpacing(); 1749 1750 getGlyphAdvances(); 1751 1752 if (textPath == null) { 1754 pathApplied = true; 1756 return; 1757 } 1758 1759 1760 boolean horizontal = !isVertical(); 1761 1762 boolean glyphOrientationAuto = isGlyphOrientationAuto(); 1763 int glyphOrientationAngle = 0; 1764 if (!glyphOrientationAuto) { 1765 glyphOrientationAngle = getGlyphOrientationAngle(); 1766 } 1767 1768 float pathLength = textPath.lengthOfPath(); 1769 float startOffset = textPath.getStartOffset(); 1770 int numGlyphs = gv.getNumGlyphs(); 1771 1772 for (int i = 0; i < numGlyphs; i++) { 1776 gv.setGlyphVisible(i, true); 1777 } 1778 1779 float glyphsLength; 1782 if (horizontal) { 1783 glyphsLength = (float) gv.getLogicalBounds().getWidth(); 1784 } else { 1785 glyphsLength = (float) gv.getLogicalBounds().getHeight(); 1786 } 1787 1788 if (pathLength == 0f || glyphsLength == 0f) { 1790 pathApplied = true; 1792 textPathAdvance = advance; 1793 return; 1794 } 1795 1796 Point2D firstGlyphPosition = gv.getGlyphPosition(0); 1801 float glyphOffset = 0; float currentPosition; 1803 if (horizontal) { 1804 glyphOffset = (float)(firstGlyphPosition.getY()); 1805 currentPosition = (float)(firstGlyphPosition.getX() + startOffset); 1806 } else { 1807 glyphOffset = (float)(firstGlyphPosition.getX()); 1808 currentPosition = (float)(firstGlyphPosition.getY() + startOffset); 1809 } 1810 1811 char ch = aci.first(); 1812 int start = aci.getBeginIndex(); 1813 int currentChar = 0; 1814 int lastGlyphDrawn = -1; 1815 float lastGlyphAdvance = 0; 1816 for (int i = 0; i < numGlyphs; i++) { 1818 1819 Point2D currentGlyphPosition = gv.getGlyphPosition(i); 1820 1821 1824 float glyphAdvance = 0; float nextGlyphOffset = 0; if (i < gv.getNumGlyphs()-1) { 1827 1828 Point2D nextGlyphPosition = gv.getGlyphPosition(i+1); 1829 if (horizontal) { 1830 glyphAdvance = (float)(nextGlyphPosition.getX() - 1831 currentGlyphPosition.getX()); 1832 nextGlyphOffset = (float)(nextGlyphPosition.getY() - 1833 currentGlyphPosition.getY()); 1834 } else { 1835 glyphAdvance = (float)(nextGlyphPosition.getY() - 1836 currentGlyphPosition.getY()); 1837 nextGlyphOffset = (float)(nextGlyphPosition.getX() - 1838 currentGlyphPosition.getX()); 1839 } 1840 } else { 1841 GVTGlyphMetrics gm = gv.getGlyphMetrics(i); 1843 if (horizontal) { 1844 if ((glyphOrientationAngle == 0) || 1845 (glyphOrientationAngle == 180)) { 1846 glyphAdvance = gm.getHorizontalAdvance(); 1847 } else { glyphAdvance = gm.getVerticalAdvance(); 1849 } 1850 } else { 1851 if (glyphOrientationAuto) { 1852 if (isLatinChar(ch)) { 1853 glyphAdvance = gm.getHorizontalAdvance(); 1854 } else { 1855 glyphAdvance = gm.getVerticalAdvance(); 1856 } 1857 } else { 1858 if ((glyphOrientationAngle == 0) || 1859 (glyphOrientationAngle == 180)) { 1860 glyphAdvance = gm.getVerticalAdvance(); 1861 } else { glyphAdvance = gm.getHorizontalAdvance(); 1863 } 1864 } 1865 } 1866 } 1867 1868 Rectangle2D glyphBounds = gv.getGlyphOutline(i).getBounds2D(); 1870 float glyphWidth = (float) glyphBounds.getWidth(); 1871 float glyphHeight = (float) glyphBounds.getHeight(); 1872 1873 float charMidPos; 1874 if (horizontal) { 1875 charMidPos = currentPosition + glyphWidth / 2f; 1876 } else { 1877 charMidPos = currentPosition + glyphHeight / 2f; 1878 1894 } 1895 1896 Point2D charMidPoint = textPath.pointAtLength(charMidPos); 1898 1899 if (charMidPoint != null) { 1901 1902 float angle = textPath.angleAtLength(charMidPos); 1904 1905 AffineTransform glyphPathTransform = new AffineTransform (); 1907 1908 if (horizontal) { 1910 glyphPathTransform.rotate(angle); 1911 } else { 1912 glyphPathTransform.rotate(angle-(Math.PI/2)); 1913 } 1914 1915 if (horizontal) { 1917 glyphPathTransform.translate(0, glyphOffset); 1918 } else { 1919 glyphPathTransform.translate(glyphOffset, 0); 1920 } 1921 1922 if (horizontal) { 1925 if (glyphOrientationAngle == 270) { 1926 glyphPathTransform.translate(glyphWidth / 2f, 0f); 1927 } else { 1928 glyphPathTransform.translate(-glyphWidth / 2f, 0f); 1930 } 1931 } else { 1932 if (glyphOrientationAuto) { 1933 if (isLatinChar(ch)) { 1934 glyphPathTransform.translate(0f, -glyphHeight/2f); 1935 } else { 1936 glyphPathTransform.translate(0f, glyphHeight/2f); 1937 } 1938 } else { 1939 if (glyphOrientationAngle == 0) { 1940 glyphPathTransform.translate(0, glyphHeight / 2f); 1941 } else { 1942 glyphPathTransform.translate(0, -glyphHeight / 2f); 1944 } 1945 } 1946 } 1947 1948 AffineTransform glyphTransform = gv.getGlyphTransform(i); 1950 if (glyphTransform != null) { 1951 glyphPathTransform.concatenate(glyphTransform); 1952 } 1953 1954 gv.setGlyphTransform(i, glyphPathTransform); 1955 gv.setGlyphPosition (i, charMidPoint); 1956 lastGlyphDrawn = i; 1959 lastGlyphAdvance = glyphAdvance; 1960 1961 } else { 1962 gv.setGlyphVisible(i, false); 1964 } 1965 currentPosition += glyphAdvance; 1966 glyphOffset += nextGlyphOffset; 1967 currentChar += gv.getCharacterCount(i,i); 1968 if (currentChar >= charMap.length) 1969 currentChar = charMap.length-1; 1970 ch = aci.setIndex(currentChar+start); 1971 } 1972 1973 if (lastGlyphDrawn > -1) { 1977 Point2D lastGlyphPos = gv.getGlyphPosition(lastGlyphDrawn); 1978 if (horizontal) { 1979 textPathAdvance = new Point2D.Double 1980 (lastGlyphPos.getX()+lastGlyphAdvance, 1981 lastGlyphPos.getY()); 1982 } else { 1983 textPathAdvance = new Point2D.Double 1984 (lastGlyphPos.getX(), 1985 lastGlyphPos.getY()+lastGlyphAdvance); 1986 } 1987 } else { 1988 textPathAdvance = new Point2D.Double (0,0); 1989 } 1990 1991 layoutApplied = false; 1993 spacingApplied = false; 1995 pathApplied = true; 1996 } 1997 1998 2006 protected boolean isLatinChar(char c) { 2007 2008 Character.UnicodeBlock block = Character.UnicodeBlock.of(c); 2009 2010 if (block == Character.UnicodeBlock.BASIC_LATIN || 2011 block == Character.UnicodeBlock.LATIN_1_SUPPLEMENT || 2012 block == Character.UnicodeBlock.LATIN_EXTENDED_ADDITIONAL || 2013 block == Character.UnicodeBlock.LATIN_EXTENDED_A || 2014 block == Character.UnicodeBlock.LATIN_EXTENDED_B || 2015 block == Character.UnicodeBlock.ARABIC || 2016 block == Character.UnicodeBlock.ARABIC_PRESENTATION_FORMS_A || 2017 block == Character.UnicodeBlock.ARABIC_PRESENTATION_FORMS_B) { 2018 return true; 2019 } 2020 return false; 2021 } 2022 2023 2026 protected boolean isGlyphOrientationAuto() { 2027 if (!isVertical()) return false; 2028 aci.first(); 2029 Integer vOrient = (Integer )aci.getAttribute(VERTICAL_ORIENTATION); 2030 if (vOrient != null) { 2031 return (vOrient == ORIENTATION_AUTO); 2032 } 2033 return true; 2034 } 2035 2036 2040 protected int getGlyphOrientationAngle() { 2041 2042 int glyphOrientationAngle = 0; 2043 2044 aci.first(); 2045 Float angle; 2046 2047 if (isVertical()) { 2048 angle = (Float )aci.getAttribute(VERTICAL_ORIENTATION_ANGLE); 2049 } else { 2050 angle = (Float )aci.getAttribute(HORIZONTAL_ORIENTATION_ANGLE); 2051 } 2052 2053 if (angle != null) { 2054 glyphOrientationAngle = (int)angle.floatValue(); 2055 } 2056 2057 if ((glyphOrientationAngle != 0) || (glyphOrientationAngle != 90) || 2059 (glyphOrientationAngle != 180) || (glyphOrientationAngle != 270)) { 2060 2061 while (glyphOrientationAngle < 0) { 2062 glyphOrientationAngle += 360; 2063 } 2064 2065 while (glyphOrientationAngle >= 360) { 2066 glyphOrientationAngle -= 360; 2067 } 2068 2069 if ((glyphOrientationAngle <= 45) || 2070 (glyphOrientationAngle > 315)) { 2071 glyphOrientationAngle = 0; 2072 } else if ((glyphOrientationAngle > 45) && 2073 (glyphOrientationAngle <= 135)) { 2074 glyphOrientationAngle = 90; 2075 } else if ((glyphOrientationAngle > 135) && 2076 (glyphOrientationAngle <= 225)) { 2077 glyphOrientationAngle = 180; 2078 } else { 2079 glyphOrientationAngle = 270; 2080 } 2081 } 2082 return glyphOrientationAngle; 2083 } 2084 2085 2092 public boolean hasCharacterIndex(int index){ 2093 2094 for (int n=0; n<charMap.length; n++) { 2095 if (index == charMap[n]) 2096 return true; 2097 } 2098 return false; 2099 } 2100 2101 2105 public boolean isAltGlyph(){ 2106 return this.isAltGlyph; 2107 } 2108} 2109 | Popular Tags |