1 18 package org.apache.batik.gvt.font; 19 20 import java.awt.Graphics2D ; 21 import java.awt.Shape ; 22 import java.awt.font.FontRenderContext ; 23 import java.awt.font.GlyphJustificationInfo ; 24 import java.awt.geom.AffineTransform ; 25 import java.awt.geom.GeneralPath ; 26 import java.awt.geom.Point2D ; 27 import java.awt.geom.Rectangle2D ; 28 import java.text.AttributedCharacterIterator ; 29 30 import org.apache.batik.gvt.text.ArabicTextHandler; 31 import org.apache.batik.gvt.text.GVTAttributedCharacterIterator; 32 import org.apache.batik.gvt.text.TextPaintInfo; 33 34 40 public final class SVGGVTGlyphVector implements GVTGlyphVector { 41 42 public static final AttributedCharacterIterator.Attribute PAINT_INFO 43 = GVTAttributedCharacterIterator.TextAttribute.PAINT_INFO; 44 45 private GVTFont font; 46 private Glyph[] glyphs; 47 private FontRenderContext frc; 48 private GeneralPath outline; 49 private Rectangle2D logicalBounds; 50 private Rectangle2D bounds2D; 51 private Shape [] glyphLogicalBounds; 52 private boolean[] glyphVisible; 53 private Point2D endPos; 54 private TextPaintInfo cacheTPI; 55 56 64 public SVGGVTGlyphVector(GVTFont font, Glyph[] glyphs, 65 FontRenderContext frc) { 66 this.font = font; 67 this.glyphs = glyphs; 68 this.frc = frc; 69 outline = null; 70 bounds2D = null; 71 logicalBounds = null; 72 glyphLogicalBounds = new Shape [glyphs.length]; 73 glyphVisible = new boolean[glyphs.length]; 74 for (int i = 0; i < glyphs.length; i++) { 75 glyphVisible[i] = true; 76 } 77 78 endPos = glyphs[glyphs.length-1].getPosition(); 79 endPos = new Point2D.Float 80 ((float)(endPos.getX()+glyphs[glyphs.length-1].getHorizAdvX()), 81 (float)endPos.getY()); 82 } 83 84 87 public GVTFont getFont() { 88 return font; 89 } 90 91 94 public FontRenderContext getFontRenderContext() { 95 return frc; 96 } 97 98 101 public int getGlyphCode(int glyphIndex) throws IndexOutOfBoundsException { 102 if (glyphIndex < 0 || glyphIndex > (glyphs.length-1)) { 103 throw new IndexOutOfBoundsException ("glyphIndex " + glyphIndex 104 + " is out of bounds, should be between 0 and " 105 + (glyphs.length-1)); 106 } 107 return glyphs[glyphIndex].getGlyphCode(); 108 } 109 110 113 public int[] getGlyphCodes(int beginGlyphIndex, int numEntries, 114 int[] codeReturn) 115 throws IndexOutOfBoundsException , 116 IllegalArgumentException { 117 if (numEntries < 0) { 118 throw new IllegalArgumentException ("numEntries argument value, " 119 + numEntries + ", is illegal. It must be > 0."); 120 } 121 if (beginGlyphIndex < 0) { 122 throw new IndexOutOfBoundsException ("beginGlyphIndex " + beginGlyphIndex 123 + " is out of bounds, should be between 0 and " 124 + (glyphs.length-1)); 125 } 126 if ((beginGlyphIndex+numEntries) > glyphs.length) { 127 throw new IndexOutOfBoundsException ("beginGlyphIndex + numEntries (" 128 + beginGlyphIndex + "+" + numEntries 129 + ") exceeds the number of glpyhs in this GlyphVector"); 130 } 131 if (codeReturn == null) { 132 codeReturn = new int[numEntries]; 133 } 134 for (int i = beginGlyphIndex; i < (beginGlyphIndex+numEntries); i++) { 135 codeReturn[i-beginGlyphIndex] = glyphs[i].getGlyphCode(); 136 } 137 return codeReturn; 138 } 139 140 144 public GlyphJustificationInfo getGlyphJustificationInfo(int glyphIndex) { 145 if (glyphIndex < 0 || (glyphIndex > glyphs.length-1)) { 146 throw new IndexOutOfBoundsException ("glyphIndex: " + glyphIndex 147 + ", is out of bounds. Should be between 0 and " + (glyphs.length-1) + "."); 148 } 149 return null; 150 } 151 152 156 public Shape getGlyphLogicalBounds(int glyphIndex) { 157 if (glyphLogicalBounds[glyphIndex] == null && glyphVisible[glyphIndex]) { 158 computeGlyphLogicalBounds(); 159 } 160 return glyphLogicalBounds[glyphIndex]; 161 } 162 163 164 private void computeGlyphLogicalBounds() { 165 166 float ascent = 0; 167 float descent = 0; 168 if (font != null) { 169 GVTLineMetrics lineMetrics = font.getLineMetrics("By", frc); 171 ascent = lineMetrics.getAscent(); 172 descent = lineMetrics.getDescent(); 173 if (descent < 0) { 174 descent = -descent; 176 } 177 } 178 179 if (ascent == 0) { 180 float maxAscent = 0; 181 float maxDescent = 0; 182 for (int i = 0; i < getNumGlyphs(); i++) { 183 if (!glyphVisible[i]) continue; 184 GVTGlyphMetrics glyphMetrics = getGlyphMetrics(i); 185 Rectangle2D glyphBounds = glyphMetrics.getBounds2D(); 186 ascent = (float)(-glyphBounds.getMinY()); 187 descent = (float)(glyphBounds.getHeight()-ascent); 188 if (ascent > maxAscent) maxAscent = ascent; 189 if (descent > maxDescent) maxDescent = descent; 190 } 191 ascent = maxAscent; 192 descent = maxDescent; 193 } 194 195 Shape [] tempLogicalBounds = new Shape [getNumGlyphs()]; 196 boolean[] rotated = new boolean[getNumGlyphs()]; 197 198 double maxWidth = -1; 199 double maxHeight = -1; 200 201 for (int i = 0; i < getNumGlyphs(); i++) { 202 203 if (!glyphVisible[i]) { 204 tempLogicalBounds[i] = null; 206 continue; 207 } 208 209 AffineTransform glyphTransform = getGlyphTransform(i); 210 GVTGlyphMetrics glyphMetrics = getGlyphMetrics(i); 211 Rectangle2D glyphBounds = new Rectangle2D.Double 212 (0, -ascent, glyphMetrics.getHorizontalAdvance(), 213 ascent+descent); 214 215 if (glyphBounds.isEmpty()) { 216 if (i > 0) { 219 rotated[i] = rotated[i-1]; 220 } else { 221 rotated [i] = true; 222 } 223 } else { 224 Point2D p1 = new Point2D.Double (glyphBounds.getMinX(), 227 glyphBounds.getMinY()); 228 Point2D p2 = new Point2D.Double (glyphBounds.getMaxX(), 229 glyphBounds.getMinY()); 230 Point2D p3 = new Point2D.Double (glyphBounds.getMinX(), 231 glyphBounds.getMaxY()); 232 Point2D gpos = getGlyphPosition(i); 233 AffineTransform tr = AffineTransform.getTranslateInstance 234 (gpos.getX(), gpos.getY()); 235 236 if (glyphTransform != null) 237 tr.concatenate(glyphTransform); 238 239 tempLogicalBounds[i] = 240 tr.createTransformedShape(glyphBounds); 241 242 Point2D tp1 = new Point2D.Double (); 243 Point2D tp2 = new Point2D.Double (); 244 Point2D tp3 = new Point2D.Double (); 245 tr.transform(p1, tp1); 246 tr.transform(p2, tp2); 247 tr.transform(p3, tp3); 248 double tdx12 = tp1.getX()-tp2.getX(); 249 double tdx13 = tp1.getX()-tp3.getX(); 250 double tdy12 = tp1.getY()-tp2.getY(); 251 double tdy13 = tp1.getY()-tp3.getY(); 252 253 if ((Math.abs(tdx12) < 0.001) && 254 (Math.abs(tdy13) < 0.001)) { 255 rotated[i] = false; 258 double dx13 = p1.getX()-p3.getX(); 259 double dy12 = p1.getY()-p2.getY(); 260 261 } else if ((Math.abs(tdx13) < 0.001) && 262 (Math.abs(tdy12) < 0.001)) { 263 rotated[i] = false; 266 double dx12 = p1.getX()-p2.getX(); 267 double dy13 = p1.getY()-p3.getY(); 268 } else { 269 rotated [i] = true; 270 } 271 272 Rectangle2D rectBounds; 273 rectBounds = tempLogicalBounds[i].getBounds2D(); 274 if (rectBounds.getWidth() > maxWidth) 275 maxWidth = rectBounds.getWidth(); 276 if (rectBounds.getHeight() > maxHeight) 277 maxHeight = rectBounds.getHeight(); 278 } 279 } 280 281 GeneralPath logicalBoundsPath = new GeneralPath (); 283 for (int i = 0; i < getNumGlyphs(); i++) { 284 if (tempLogicalBounds[i] != null) { 285 logicalBoundsPath.append(tempLogicalBounds[i], false); 286 } 287 } 288 Rectangle2D fullBounds = logicalBoundsPath.getBounds2D(); 289 290 if (fullBounds.getHeight() < maxHeight*1.5) { 291 for (int i = 0; i < getNumGlyphs(); i++) { 293 if (rotated[i]) continue; 296 if (tempLogicalBounds[i] == null) continue; 297 298 Rectangle2D glyphBounds = tempLogicalBounds[i].getBounds2D(); 299 300 double x = glyphBounds.getMinX(); 301 double width = glyphBounds.getWidth(); 302 303 if ((i < getNumGlyphs()-1) && 304 (tempLogicalBounds[i+1] != null)) { 305 Rectangle2D nextGlyphBounds = 307 tempLogicalBounds[i+1].getBounds2D(); 308 Rectangle2D ngb = tempLogicalBounds[i+1].getBounds2D(); 310 311 if (ngb.getX() > x) { 312 double nw = ngb.getX() - x; 313 if ((nw < width*1.15) && (nw > width*.85)) { 314 double delta = (nw-width)*.5; 315 width += delta; 316 ngb.setRect(ngb.getX()-delta, ngb.getY(), 317 ngb.getWidth()+delta, ngb.getHeight()); 318 } 319 } 320 } 321 tempLogicalBounds[i] = new Rectangle2D.Double 322 (x, fullBounds.getMinY(), 323 width, fullBounds.getHeight()); 324 } 325 } else if (fullBounds.getWidth() < maxWidth*1.5) { 326 for (int i = 0; i < getNumGlyphs(); i++) { 328 if (rotated[i]) continue; 331 if (tempLogicalBounds[i] == null) continue; 332 333 Rectangle2D glyphBounds = tempLogicalBounds[i].getBounds2D(); 334 double y = glyphBounds.getMinY(); 335 double height = glyphBounds.getHeight(); 336 337 if ((i < getNumGlyphs()-1) && 338 (tempLogicalBounds[i+1] != null)) { 339 Rectangle2D ngb = tempLogicalBounds[i+1].getBounds2D(); 341 if (ngb.getY() > y) { double nh = ngb.getY() - y; 343 if ((nh < height*1.15) && (nh > height*.85)) { 344 double delta = (nh-height)*.5; 345 height += delta; 346 ngb.setRect(ngb.getX(), ngb.getY()-delta, 347 ngb.getWidth(), ngb.getHeight()+delta); 348 } 349 } 350 } 351 tempLogicalBounds[i] = new Rectangle2D.Double 352 (fullBounds.getMinX(), y, 353 fullBounds.getWidth(), height); 354 } 355 } 356 357 for (int i = 0; i < getNumGlyphs(); i++) { 358 glyphLogicalBounds[i] = tempLogicalBounds[i]; 359 } 360 } 361 362 366 public GVTGlyphMetrics getGlyphMetrics(int idx) { 367 368 if (idx < 0 || (idx > glyphs.length-1)) 369 throw new IndexOutOfBoundsException 370 ("idx: " + idx + ", is out of bounds. Should be between 0 and " 371 + (glyphs.length-1) + "."); 372 373 if (idx < glyphs.length - 1) { 378 if (font != null) { 380 float hkern = font.getHKern(glyphs[idx].getGlyphCode(), 381 glyphs[idx+1].getGlyphCode()); 382 float vkern = font.getVKern(glyphs[idx].getGlyphCode(), 383 glyphs[idx+1].getGlyphCode()); 384 return glyphs[idx].getGlyphMetrics(hkern, vkern); 385 } 386 } 387 388 return glyphs[idx].getGlyphMetrics(); 390 } 391 392 396 public Shape getGlyphOutline(int glyphIndex) { 397 if (glyphIndex < 0 || (glyphIndex > glyphs.length-1)) { 398 throw new IndexOutOfBoundsException ("glyphIndex: " + glyphIndex 399 + ", is out of bounds. Should be between 0 and " + (glyphs.length-1) + "."); 400 } 401 return glyphs[glyphIndex].getOutline(); 402 } 403 404 407 public Point2D getGlyphPosition(int glyphIndex) { 408 if (glyphIndex == glyphs.length) 409 return endPos; 410 411 if (glyphIndex < 0 || (glyphIndex > glyphs.length-1)) { 412 throw new IndexOutOfBoundsException ("glyphIndex: " + glyphIndex 413 + ", is out of bounds. Should be between 0 and " + (glyphs.length-1) + "."); 414 } 415 return glyphs[glyphIndex].getPosition(); 416 } 417 418 419 422 public float[] getGlyphPositions(int beginGlyphIndex, int numEntries, 423 float[] positionReturn) { 424 if (numEntries < 0) { 425 throw new IllegalArgumentException ("numEntries argument value, " 426 + numEntries + ", is illegal. It must be > 0."); 427 } 428 if (beginGlyphIndex < 0) { 429 throw new IndexOutOfBoundsException ("beginGlyphIndex " + beginGlyphIndex 430 + " is out of bounds, should be between 0 and " 431 + (glyphs.length-1)); 432 } 433 if ((beginGlyphIndex+numEntries) > glyphs.length+1) { 434 throw new IndexOutOfBoundsException ("beginGlyphIndex + numEntries (" 435 + beginGlyphIndex + "+" + numEntries 436 + ") exceeds the number of glpyhs in this GlyphVector"); 437 } 438 if (positionReturn == null) { 439 positionReturn = new float[numEntries*2]; 440 } 441 if ((beginGlyphIndex+numEntries) == glyphs.length+1) { 442 numEntries--; 443 positionReturn[numEntries*2] = (float)endPos.getX(); 444 positionReturn[numEntries*2+1] = (float)endPos.getY(); 445 } 446 for (int i = beginGlyphIndex; i < (beginGlyphIndex+numEntries); i++) { 447 Point2D glyphPos; 448 glyphPos = glyphs[i].getPosition(); 449 positionReturn[(i-beginGlyphIndex)*2] = (float)glyphPos.getX(); 450 positionReturn[(i-beginGlyphIndex)*2 + 1] = (float)glyphPos.getY(); 451 } 452 return positionReturn; 453 } 454 455 458 public AffineTransform getGlyphTransform(int glyphIndex) { 459 if (glyphIndex < 0 || (glyphIndex > glyphs.length-1)) { 460 throw new IndexOutOfBoundsException ("glyphIndex: " + glyphIndex 461 + ", is out of bounds. Should be between 0 and " + (glyphs.length-1) + "."); 462 } 463 return glyphs[glyphIndex].getTransform(); 464 } 465 466 469 public Shape getGlyphVisualBounds(int glyphIndex) { 470 if (glyphIndex < 0 || (glyphIndex > glyphs.length-1)) { 471 throw new IndexOutOfBoundsException ("glyphIndex: " + glyphIndex 472 + ", is out of bounds. Should be between 0 and " + (glyphs.length-1) + "."); 473 } 474 return glyphs[glyphIndex].getOutline(); 475 } 476 477 480 public Rectangle2D getBounds2D(AttributedCharacterIterator aci) { 481 aci.first(); 483 TextPaintInfo tpi = (TextPaintInfo)aci.getAttribute(PAINT_INFO); 484 if ((bounds2D != null) && 485 TextPaintInfo.equivilent(tpi, cacheTPI)) 486 return bounds2D; 487 488 Rectangle2D b=null; 489 if (tpi.visible) { 490 for (int i = 0; i < getNumGlyphs(); i++) { 491 if (!glyphVisible[i]) continue; 492 493 Rectangle2D glyphBounds = glyphs[i].getBounds2D(); 494 if (glyphBounds == null) continue; 496 if (b == null) b=glyphBounds; 497 else b = glyphBounds.createUnion(b); 498 } 499 } 500 501 bounds2D = b; 502 if ( bounds2D == null ){ 503 bounds2D = new Rectangle2D.Float (); 504 } 505 cacheTPI = new TextPaintInfo(tpi); 506 return bounds2D; 507 } 508 509 513 public Rectangle2D getLogicalBounds() { 514 if (logicalBounds == null) { 515 GeneralPath logicalBoundsPath = new GeneralPath (); 516 for (int i = 0; i < getNumGlyphs(); i++) { 517 Shape glyphLogicalBounds = getGlyphLogicalBounds(i); 518 if (glyphLogicalBounds != null) { 519 logicalBoundsPath.append(glyphLogicalBounds, false); 520 } 521 } 522 logicalBounds = logicalBoundsPath.getBounds2D(); 523 } 524 return logicalBounds; 525 } 526 527 530 public int getNumGlyphs() { 531 if (glyphs != null) { 532 return glyphs.length; 533 } 534 return 0; 535 } 536 537 541 public Shape getOutline() { 542 if (outline == null) { 543 outline = new GeneralPath (); 544 for (int i = 0; i < glyphs.length; i++) { 545 if (glyphVisible[i]) { 546 Shape glyphOutline = glyphs[i].getOutline(); 547 if (glyphOutline != null) { 548 outline.append(glyphOutline, false); 549 } 550 } 551 } 552 } 553 return outline; 554 } 555 556 560 public Shape getOutline(float x, float y) { 561 Shape outline = getOutline(); 562 AffineTransform tr = AffineTransform.getTranslateInstance(x,y); 563 Shape translatedOutline = tr.createTransformedShape(outline); 564 return translatedOutline; 565 } 566 567 572 public Rectangle2D getGeometricBounds() { 573 return getOutline().getBounds2D(); 574 } 575 576 580 public void performDefaultLayout() { 581 logicalBounds = null; 582 outline = null; 583 bounds2D = null; 584 585 float currentX = 0; 586 float currentY = 0; 587 for (int i = 0; i < glyphs.length; i++) { 588 Glyph g = glyphs[i]; 589 g.setTransform(null); 590 glyphLogicalBounds[i] = null; 591 592 String uni = g.getUnicode(); 593 if ((uni != null) && (uni.length() != 0) && 594 ArabicTextHandler.arabicCharTransparent(uni.charAt(0))) { 595 int j; 596 for (j=i+1; j<glyphs.length; j++) { 597 uni = glyphs[j].getUnicode(); 598 if ((uni == null) || (uni.length() == 0)) break; 599 char ch = uni.charAt(0); 600 if (!ArabicTextHandler.arabicCharTransparent(ch)) 601 break; 602 } 603 if (j != glyphs.length) { 604 Glyph bg = glyphs[j]; 605 float rEdge = (float)(currentX + bg.getHorizAdvX()); 606 for (int k=i; k<j; k++) { 607 g = glyphs[k]; 608 g.setTransform(null); 609 glyphLogicalBounds[i] = null; 610 g.setPosition(new Point2D.Float (rEdge-g.getHorizAdvX(), 611 currentY)); 612 } 613 i = j; 614 g = bg; 615 } 616 } 617 618 g.setPosition(new Point2D.Float (currentX, currentY)); 619 currentX += g.getHorizAdvX(); 620 } 621 endPos = new Point2D.Float (currentX, currentY); 622 } 623 624 627 public void setGlyphPosition(int glyphIndex, Point2D newPos) 628 throws IndexOutOfBoundsException { 629 if (glyphIndex == glyphs.length) { 630 endPos = (Point2D )newPos.clone(); 631 return; 632 } 633 634 if (glyphIndex < 0 || (glyphIndex > glyphs.length-1)) { 635 throw new IndexOutOfBoundsException ("glyphIndex: " + glyphIndex 636 + ", is out of bounds. Should be between 0 and " + (glyphs.length-1) + "."); 637 } 638 glyphs[glyphIndex].setPosition(newPos); 639 glyphLogicalBounds[glyphIndex] = null; 640 outline = null; 641 bounds2D = null; 642 logicalBounds = null; 643 } 644 645 648 public void setGlyphTransform(int glyphIndex, AffineTransform newTX) { 649 if (glyphIndex < 0 || (glyphIndex > glyphs.length-1)) { 650 throw new IndexOutOfBoundsException ("glyphIndex: " + glyphIndex 651 + ", is out of bounds. Should be between 0 and " + (glyphs.length-1) + "."); 652 } 653 glyphs[glyphIndex].setTransform(newTX); 654 glyphLogicalBounds[glyphIndex] = null; 655 outline = null; 656 bounds2D = null; 657 logicalBounds = null; 658 } 659 660 663 public void setGlyphVisible(int glyphIndex, boolean visible) { 664 if (visible == glyphVisible[glyphIndex]) 665 return; 666 667 glyphVisible[glyphIndex] = visible; 668 outline = null; 669 bounds2D = null; 670 logicalBounds = null; 671 glyphLogicalBounds[glyphIndex] = null; 672 } 673 674 677 public boolean isGlyphVisible(int glyphIndex) { 678 return glyphVisible[glyphIndex]; 679 } 680 681 688 public int getCharacterCount(int startGlyphIndex, int endGlyphIndex) { 689 int numChars = 0; 690 if (startGlyphIndex < 0) { 691 startGlyphIndex = 0; 692 } 693 if (endGlyphIndex > glyphs.length-1) { 694 endGlyphIndex = glyphs.length-1; 695 } 696 for (int i = startGlyphIndex; i <= endGlyphIndex; i++) { 697 Glyph glyph = glyphs[i]; 698 if (glyph.getGlyphCode() == -1) { 699 numChars++; 701 } else { 702 String glyphUnicode = glyph.getUnicode(); 703 numChars += glyphUnicode.length(); 704 } 705 } 706 return numChars; 707 } 708 709 712 public void draw(Graphics2D graphics2D, 713 AttributedCharacterIterator aci) { 714 aci.first(); 715 TextPaintInfo tpi = (TextPaintInfo)aci.getAttribute(PAINT_INFO); 716 if (!tpi.visible) return; 717 718 for (int i = 0; i < glyphs.length; i++) { 719 if (glyphVisible[i]) { 720 glyphs[i].draw(graphics2D); 721 } 722 } 723 } 724 } 725 726 | Popular Tags |