| 1 18 package org.apache.batik.gvt.font; 19 20 import java.awt.Color ; 21 import java.awt.Graphics2D ; 22 import java.awt.Paint ; 23 import java.awt.RenderingHints ; 24 import java.awt.Shape ; 25 import java.awt.Stroke ; 26 import java.awt.font.FontRenderContext ; 27 import java.awt.font.GlyphJustificationInfo ; 28 import java.awt.font.GlyphMetrics ; 29 import java.awt.font.GlyphVector ; 30 import java.awt.font.TextAttribute ; 31 import java.awt.geom.AffineTransform ; 32 import java.awt.geom.GeneralPath ; 33 import java.awt.geom.Point2D ; 34 import java.awt.geom.Rectangle2D ; 35 import java.text.AttributedCharacterIterator ; 36 import java.text.CharacterIterator ; 37 38 import org.apache.batik.gvt.text.ArabicTextHandler; 39 import org.apache.batik.gvt.text.GVTAttributedCharacterIterator; 40 import org.apache.batik.gvt.text.TextPaintInfo; 41 42 48 public class AWTGVTGlyphVector implements GVTGlyphVector { 49 50 public static final AttributedCharacterIterator.Attribute PAINT_INFO 51 = GVTAttributedCharacterIterator.TextAttribute.PAINT_INFO; 52 53 private GlyphVector awtGlyphVector; 54 private AWTGVTFont gvtFont; 55 private CharacterIterator ci; 56 57 private Point2D [] defaultGlyphPositions; 59 private Point2D.Float [] glyphPositions; 60 61 private AffineTransform [] glyphTransforms; 64 65 private Shape [] glyphOutlines; 67 private Shape [] glyphVisualBounds; 68 private Shape [] glyphLogicalBounds; 69 private boolean[] glyphVisible; 70 private GVTGlyphMetrics [] glyphMetrics; 71 private GeneralPath outline; 72 private Rectangle2D visualBounds; 73 private Rectangle2D logicalBounds; 74 private Rectangle2D bounds2D; 75 private float scaleFactor; 76 private float ascent; 77 private float descent; 78 private TextPaintInfo cacheTPI; 79 80 93 public AWTGVTGlyphVector(GlyphVector glyphVector, 94 AWTGVTFont font, 95 float scaleFactor, 96 CharacterIterator ci) { 97 98 this.awtGlyphVector = glyphVector; 99 this.gvtFont = font; 100 this.scaleFactor = scaleFactor; 101 this.ci = ci; 102 103 GVTLineMetrics lineMetrics = gvtFont.getLineMetrics 104 ("By", awtGlyphVector.getFontRenderContext()); 105 106 ascent = lineMetrics.getAscent(); 107 descent = lineMetrics.getDescent(); 108 109 outline = null; 110 visualBounds = null; 111 logicalBounds = null; 112 bounds2D = null; 113 int numGlyphs = glyphVector.getNumGlyphs(); 114 glyphPositions = new Point2D.Float [numGlyphs+1]; 115 glyphTransforms = new AffineTransform [numGlyphs]; 116 glyphOutlines = new Shape [numGlyphs]; 117 glyphVisualBounds = new Shape [numGlyphs]; 118 glyphLogicalBounds = new Shape [numGlyphs]; 119 glyphVisible = new boolean [numGlyphs]; 120 glyphMetrics = new GVTGlyphMetrics[numGlyphs]; 121 122 for (int i = 0; i < numGlyphs; i++) { 123 glyphVisible[i] = true; 124 } 125 } 126 127 130 public GVTFont getFont() { 131 return gvtFont; 132 } 133 134 137 public FontRenderContext getFontRenderContext() { 138 return awtGlyphVector.getFontRenderContext(); 139 } 140 141 144 public int getGlyphCode(int glyphIndex) { 145 return awtGlyphVector.getGlyphCode(glyphIndex); 146 } 147 148 151 public int[] getGlyphCodes(int beginGlyphIndex, int numEntries, 152 int[] codeReturn) { 153 return awtGlyphVector.getGlyphCodes(beginGlyphIndex, numEntries, 154 codeReturn); 155 } 156 157 161 public GlyphJustificationInfo getGlyphJustificationInfo(int glyphIndex) { 162 return awtGlyphVector.getGlyphJustificationInfo(glyphIndex); 163 } 164 165 168 public Rectangle2D getBounds2D(AttributedCharacterIterator aci) { 169 aci.first(); 170 TextPaintInfo tpi = (TextPaintInfo)aci.getAttribute(PAINT_INFO); 171 if ((bounds2D != null) && 172 TextPaintInfo.equivilent(tpi, cacheTPI)) 173 return bounds2D; 174 175 if (tpi == null) 176 return null; 177 if (!tpi.visible) 178 return null; 179 180 cacheTPI = new TextPaintInfo(tpi); 181 Shape outline = null; 182 if (tpi.fillPaint != null) { 183 outline = getOutline(); 184 bounds2D = outline.getBounds2D(); 185 } 186 187 Stroke stroke = tpi.strokeStroke; 190 Paint paint = tpi.strokePaint; 191 if ((stroke != null) && (paint != null)) { 192 if (outline == null) 193 outline = getOutline(); 194 Rectangle2D strokeBounds 195 = stroke.createStrokedShape(outline).getBounds2D(); 196 if (bounds2D == null) 197 bounds2D = strokeBounds; 198 else 199 bounds2D = bounds2D.createUnion(strokeBounds); 200 } 201 if (bounds2D == null) 202 return null; 203 204 if ((bounds2D.getWidth() == 0) || 205 (bounds2D.getHeight() == 0)) 206 bounds2D = null; 207 208 return bounds2D; 209 } 210 211 215 public Rectangle2D getLogicalBounds() { 216 if (logicalBounds == null) { 217 computeGlyphLogicalBounds(); 219 } 220 return logicalBounds; 221 } 222 223 227 public Shape getGlyphLogicalBounds(int glyphIndex) { 228 if (glyphLogicalBounds[glyphIndex] == null && 229 glyphVisible[glyphIndex]) { 230 231 computeGlyphLogicalBounds(); 232 } 233 return glyphLogicalBounds[glyphIndex]; 234 } 235 236 241 private void computeGlyphLogicalBounds() { 242 243 Shape [] tempLogicalBounds = new Shape [getNumGlyphs()]; 244 boolean[] rotated = new boolean[getNumGlyphs()]; 245 246 double maxWidth = -1; 247 double maxHeight = -1; 248 for (int i = 0; i < getNumGlyphs(); i++) { 249 250 if (!glyphVisible[i]) { 251 tempLogicalBounds[i] = null; 253 continue; 254 } 255 256 AffineTransform glyphTransform = getGlyphTransform(i); 257 GVTGlyphMetrics glyphMetrics = getGlyphMetrics(i); 258 259 float glyphX = 0; 260 float glyphY = -ascent/scaleFactor; 261 float glyphWidth = (glyphMetrics.getHorizontalAdvance()/ 262 scaleFactor); 263 float glyphHeight = (glyphMetrics.getVerticalAdvance()/ 264 scaleFactor); 265 Rectangle2D glyphBounds = new Rectangle2D.Double (glyphX, 266 glyphY, 267 glyphWidth, 268 glyphHeight); 269 270 if (glyphBounds.isEmpty()) { 271 if (i > 0) { 272 rotated [i] = rotated [i-1]; 275 } else { 276 rotated [i] = true; 277 } 278 } else { 279 Point2D p1 = new Point2D.Double (glyphBounds.getMinX(), 282 glyphBounds.getMinY()); 283 Point2D p2 = new Point2D.Double (glyphBounds.getMaxX(), 284 glyphBounds.getMinY()); 285 Point2D p3 = new Point2D.Double (glyphBounds.getMinX(), 286 glyphBounds.getMaxY()); 287 288 Point2D gpos = getGlyphPosition(i); 289 AffineTransform tr = AffineTransform.getTranslateInstance 290 (gpos.getX(), gpos.getY()); 291 292 if (glyphTransform != null) 293 tr.concatenate(glyphTransform); 294 tr.scale(scaleFactor, scaleFactor); 295 296 tempLogicalBounds[i] = tr.createTransformedShape(glyphBounds); 297 298 Point2D tp1 = new Point2D.Double (); 299 Point2D tp2 = new Point2D.Double (); 300 Point2D tp3 = new Point2D.Double (); 301 tr.transform(p1, tp1); 302 tr.transform(p2, tp2); 303 tr.transform(p3, tp3); 304 double tdx12 = tp1.getX()-tp2.getX(); 305 double tdx13 = tp1.getX()-tp3.getX(); 306 double tdy12 = tp1.getY()-tp2.getY(); 307 double tdy13 = tp1.getY()-tp3.getY(); 308 309 if (((Math.abs(tdx12) < 0.001) && (Math.abs(tdy13) < 0.001)) || 310 ((Math.abs(tdx13) < 0.001) && (Math.abs(tdy12) < 0.001))) { 311 rotated[i] = false; 313 } else { 314 rotated [i] = true; 315 } 316 317 Rectangle2D rectBounds; 318 rectBounds = tempLogicalBounds[i].getBounds2D(); 319 if (rectBounds.getWidth() > maxWidth) 320 maxWidth = rectBounds.getWidth(); 321 if (rectBounds.getHeight() > maxHeight) 322 maxHeight = rectBounds.getHeight(); 323 } 324 } 325 326 GeneralPath logicalBoundsPath = new GeneralPath (); 328 for (int i = 0; i < getNumGlyphs(); i++) { 329 if (tempLogicalBounds[i] != null) { 330 logicalBoundsPath.append(tempLogicalBounds[i], false); 331 } 332 } 333 334 logicalBounds = logicalBoundsPath.getBounds2D(); 335 336 if (logicalBounds.getHeight() < maxHeight*1.5) { 337 for (int i = 0; i < getNumGlyphs(); i++) { 339 if (rotated[i]) continue; 342 if (tempLogicalBounds[i] == null) continue; 343 344 Rectangle2D glyphBounds = tempLogicalBounds[i].getBounds2D(); 345 346 double x = glyphBounds.getMinX(); 347 double width = glyphBounds.getWidth(); 348 349 if ((i < getNumGlyphs()-1) && 350 (tempLogicalBounds[i+1] != null)) { 351 Rectangle2D ngb = tempLogicalBounds[i+1].getBounds2D(); 353 354 if (ngb.getX() > x) { 355 double nw = ngb.getX() - x; 356 if ((nw < width*1.15) && (nw > width*.85)) { 357 double delta = (nw-width)*.5; 358 width += delta; 359 ngb.setRect(ngb.getX()-delta, ngb.getY(), 360 ngb.getWidth()+delta, ngb.getHeight()); 361 } 362 } 363 } 364 tempLogicalBounds[i] = new Rectangle2D.Double  365 (x, logicalBounds.getMinY(), 366 width, logicalBounds.getHeight()); 367 } 368 } else if (logicalBounds.getWidth() < maxWidth*1.5) { 369 for (int i = 0; i < getNumGlyphs(); i++) { 371 if (rotated[i]) continue; 374 if (tempLogicalBounds[i] == null) continue; 375 376 Rectangle2D glyphBounds = tempLogicalBounds[i].getBounds2D(); 377 double y = glyphBounds.getMinY(); 378 double height = glyphBounds.getHeight(); 379 380 if ((i < getNumGlyphs()-1) && 381 (tempLogicalBounds[i+1] != null)) { 382 Rectangle2D ngb = tempLogicalBounds[i+1].getBounds2D(); 384 if (ngb.getY() > y) { double nh = ngb.getY() - y; 386 if ((nh < height*1.15) && (nh > height*.85)) { 387 double delta = (nh-height)*.5; 388 height += delta; 389 ngb.setRect(ngb.getX(), ngb.getY()-delta, 390 ngb.getWidth(), ngb.getHeight()+delta); 391 } 392 } 393 } 394 tempLogicalBounds[i] = new Rectangle2D.Double  395 (logicalBounds.getMinX(), y, 396 logicalBounds.getWidth(), height); 397 } 398 } 399 400 for (int i = 0; i < getNumGlyphs(); i++) { 401 glyphLogicalBounds[i] = tempLogicalBounds[i]; 402 } 403 } 404 405 409 public GVTGlyphMetrics getGlyphMetrics(int glyphIndex) { 410 if (glyphMetrics[glyphIndex] != null) 411 return glyphMetrics[glyphIndex]; 412 413 Point2D glyphPos = defaultGlyphPositions[glyphIndex]; 415 char c = ci.setIndex(ci.getBeginIndex()+glyphIndex); 416 ci.setIndex(ci.getBeginIndex()); 417 AWTGlyphGeometryCache.Value v = AWTGVTFont.getGlyphGeometry 418 (gvtFont, c, awtGlyphVector, glyphIndex, glyphPos); 419 Rectangle2D gmB = v.getBounds2D(); 420 422 Rectangle2D bounds = new Rectangle2D.Double  423 (gmB.getX() * scaleFactor, gmB.getY() * scaleFactor, 424 gmB.getWidth() * scaleFactor, gmB.getHeight() * scaleFactor); 425 426 float adv = (float)(defaultGlyphPositions[glyphIndex+1].getX()- 430 defaultGlyphPositions[glyphIndex] .getX()); 431 glyphMetrics[glyphIndex] = new GVTGlyphMetrics 432 (adv*scaleFactor, (ascent+descent), 433 bounds, GlyphMetrics.STANDARD); 434 435 return glyphMetrics[glyphIndex]; 436 } 437 438 442 public Shape getGlyphOutline(int glyphIndex) { 443 if (glyphOutlines[glyphIndex] == null) { 444 447 Point2D glyphPos = defaultGlyphPositions[glyphIndex]; 449 char c = ci.setIndex(ci.getBeginIndex()+glyphIndex); 450 ci.setIndex(ci.getBeginIndex()); 451 AWTGlyphGeometryCache.Value v = AWTGVTFont.getGlyphGeometry 452 (gvtFont, c, awtGlyphVector, glyphIndex, glyphPos); 453 Shape glyphOutline = v.getOutline(); 454 456 AffineTransform tr = AffineTransform.getTranslateInstance 457 (getGlyphPosition(glyphIndex).getX(), 458 getGlyphPosition(glyphIndex).getY()); 459 460 AffineTransform glyphTransform = getGlyphTransform(glyphIndex); 461 462 if (glyphTransform != null) { 463 tr.concatenate(glyphTransform); 464 } 465 486 tr.scale(scaleFactor, scaleFactor); 487 glyphOutlines[glyphIndex]=tr.createTransformedShape(glyphOutline); 488 } 489 490 return glyphOutlines[glyphIndex]; 491 } 492 493 private static final boolean outlinesPositioned; 496 private static final boolean drawGlyphVectorWorks; 499 private static final boolean glyphVectorTransformWorks; 502 503 static { 504 String s = System.getProperty("java.specification.version"); 505 if ("1.4".compareTo(s) <= 0) { 506 outlinesPositioned = true; 507 drawGlyphVectorWorks = true; 508 glyphVectorTransformWorks = true; 509 } else if ("Mac OS X".equals(System.getProperty("os.name"))) { 510 outlinesPositioned = true; 511 drawGlyphVectorWorks = false; 512 glyphVectorTransformWorks = false; 513 } else { 514 outlinesPositioned = false; 515 drawGlyphVectorWorks = true; 516 glyphVectorTransformWorks = false; 517 } 518 } 519 520 static boolean outlinesPositioned() { 523 return outlinesPositioned; 524 } 525 526 529 public Point2D getGlyphPosition(int glyphIndex) { 530 return glyphPositions[glyphIndex]; 531 } 532 533 536 public float[] getGlyphPositions(int beginGlyphIndex, 537 int numEntries, 538 float[] positionReturn) { 539 540 if (positionReturn == null) { 541 positionReturn = new float[numEntries*2]; 542 } 543 544 for (int i = beginGlyphIndex; i < (beginGlyphIndex+numEntries); i++) { 545 Point2D glyphPos = getGlyphPosition(i); 546 positionReturn[(i-beginGlyphIndex)*2] = (float)glyphPos.getX(); 547 positionReturn[(i-beginGlyphIndex)*2 + 1] = (float)glyphPos.getY(); 548 } 549 550 return positionReturn; 551 } 552 553 556 public AffineTransform getGlyphTransform(int glyphIndex) { 557 return glyphTransforms[glyphIndex]; 558 } 559 560 563 public Shape getGlyphVisualBounds(int glyphIndex) { 564 if (glyphVisualBounds[glyphIndex] == null) { 565 569 Point2D glyphPos = defaultGlyphPositions[glyphIndex]; 571 char c = ci.setIndex(ci.getBeginIndex()+glyphIndex); 572 ci.setIndex(ci.getBeginIndex()); 573 AWTGlyphGeometryCache.Value v = AWTGVTFont.getGlyphGeometry 574 (gvtFont, c, awtGlyphVector, glyphIndex, glyphPos); 575 Rectangle2D glyphBounds = v.getOutlineBounds2D(); 576 578 AffineTransform tr = AffineTransform.getTranslateInstance 579 (getGlyphPosition(glyphIndex).getX(), 580 getGlyphPosition(glyphIndex).getY()); 581 582 AffineTransform glyphTransform = getGlyphTransform(glyphIndex); 583 if (glyphTransform != null) { 584 tr.concatenate(glyphTransform); 585 } 586 tr.scale(scaleFactor, scaleFactor); 587 glyphVisualBounds[glyphIndex] = 588 tr.createTransformedShape(glyphBounds); 589 } 590 591 return glyphVisualBounds[glyphIndex]; 592 } 593 594 597 public int getNumGlyphs() { 598 return awtGlyphVector.getNumGlyphs(); 599 } 600 601 605 public Shape getOutline() { 606 if (outline != null) 607 return outline; 608 609 outline = new GeneralPath (); 610 for (int i = 0; i < getNumGlyphs(); i++) { 611 if (glyphVisible[i]) { 612 Shape glyphOutline = getGlyphOutline(i); 613 outline.append(glyphOutline, false); 614 } 615 } 616 return outline; 617 } 618 619 623 public Shape getOutline(float x, float y) { 624 Shape outline = getOutline(); 625 AffineTransform tr = AffineTransform.getTranslateInstance(x,y); 626 outline = tr.createTransformedShape(outline); 627 return outline; 628 } 629 630 635 public Rectangle2D getGeometricBounds() { 636 if (visualBounds == null) { 637 Shape outline = getOutline(); 638 visualBounds = outline.getBounds2D(); 639 } 640 return visualBounds; 641 } 642 643 646 public void performDefaultLayout() { 647 if (defaultGlyphPositions == null) { 648 awtGlyphVector.performDefaultLayout(); 649 defaultGlyphPositions = new Point2D.Float [getNumGlyphs()+1]; 650 for (int i = 0; i <= getNumGlyphs(); i++) 651 defaultGlyphPositions[i] = awtGlyphVector.getGlyphPosition(i); 652 } 653 654 outline = null; 655 visualBounds = null; 656 logicalBounds = null; 657 bounds2D = null; 658 float shiftLeft = 0; 659 int i=0; 660 for (; i < getNumGlyphs(); i++) { 661 glyphTransforms [i] = null; 662 glyphVisualBounds [i] = null; 663 glyphLogicalBounds[i] = null; 664 glyphOutlines [i] = null; 665 glyphMetrics [i] = null; 666 Point2D glyphPos = defaultGlyphPositions[i]; 667 float x = (float)((glyphPos.getX() * scaleFactor)-shiftLeft); 668 float y = (float) (glyphPos.getY() * scaleFactor); 669 670 char c = ci.setIndex(i + ci.getBeginIndex()); 673 705 if (glyphPositions[i] == null) { 706 glyphPositions[i] = new Point2D.Float (x,y); 707 } else { 708 glyphPositions[i].x = x; 709 glyphPositions[i].y = y; 710 } 711 713 } 714 715 Point2D glyphPos = defaultGlyphPositions[i]; 717 glyphPositions[i] = new Point2D.Float  718 ((float)((glyphPos.getX() * scaleFactor)-shiftLeft), 719 (float) (glyphPos.getY() * scaleFactor)); 720 } 721 722 725 public void setGlyphPosition(int glyphIndex, Point2D newPos) { 726 glyphPositions[glyphIndex].x = (float)newPos.getX(); 727 glyphPositions[glyphIndex].y = (float)newPos.getY(); 728 outline = null; 729 visualBounds = null; 730 logicalBounds = null; 731 bounds2D = null; 732 733 if (glyphIndex != getNumGlyphs()) { 734 glyphVisualBounds [glyphIndex] = null; 735 glyphLogicalBounds[glyphIndex] = null; 736 glyphOutlines [glyphIndex] = null; 737 glyphMetrics [glyphIndex] = null; 738 } 739 } 740 741 744 public void setGlyphTransfor
|