| 1 18 19 package org.apache.batik.gvt.renderer; 20 21 import java.awt.Composite ; 22 import java.awt.Graphics2D ; 23 import java.awt.Paint ; 24 import java.awt.RenderingHints ; 25 import java.awt.Shape ; 26 import java.awt.Stroke ; 27 import java.awt.font.FontRenderContext ; 28 import java.awt.font.TextAttribute ; 29 import java.awt.geom.GeneralPath ; 30 import java.awt.geom.Point2D ; 31 import java.awt.geom.Rectangle2D ; 32 import java.text.AttributedCharacterIterator ; 33 import java.text.AttributedString ; 34 import java.text.CharacterIterator ; 35 import java.util.ArrayList ; 36 import java.util.HashSet ; 37 import java.util.Iterator ; 38 import java.util.List ; 39 import java.util.Set ; 40 import java.util.Vector ; 41 42 import org.apache.batik.gvt.TextNode; 43 import org.apache.batik.gvt.TextPainter; 44 import org.apache.batik.gvt.font.FontFamilyResolver; 45 import org.apache.batik.gvt.font.GVTFont; 46 import org.apache.batik.gvt.font.GVTFontFamily; 47 import org.apache.batik.gvt.font.GVTGlyphMetrics; 48 import org.apache.batik.gvt.font.UnresolvedFontFamily; 49 import org.apache.batik.gvt.text.AttributedCharacterSpanIterator; 50 import org.apache.batik.gvt.text.BidiAttributedCharacterIterator; 51 import org.apache.batik.gvt.text.GVTAttributedCharacterIterator; 52 import org.apache.batik.gvt.text.Mark; 53 import org.apache.batik.gvt.text.TextHit; 54 import org.apache.batik.gvt.text.TextPath; 55 import org.apache.batik.gvt.text.TextPaintInfo; 56 import org.apache.batik.gvt.text.TextSpanLayout; 57 58 59 71 public class StrokingTextPainter extends BasicTextPainter { 72 73 public static final 74 AttributedCharacterIterator.Attribute PAINT_INFO = 75 GVTAttributedCharacterIterator.TextAttribute.PAINT_INFO; 76 77 public static final 78 AttributedCharacterIterator.Attribute FLOW_REGIONS = 79 GVTAttributedCharacterIterator.TextAttribute.FLOW_REGIONS; 80 81 public static final 82 AttributedCharacterIterator.Attribute FLOW_PARAGRAPH = 83 GVTAttributedCharacterIterator.TextAttribute.FLOW_PARAGRAPH; 84 85 public static final 86 AttributedCharacterIterator.Attribute TEXT_COMPOUND_DELIMITER 87 = GVTAttributedCharacterIterator.TextAttribute.TEXT_COMPOUND_DELIMITER; 88 89 public static final 90 AttributedCharacterIterator.Attribute GVT_FONT 91 = GVTAttributedCharacterIterator.TextAttribute.GVT_FONT; 92 93 public static final 94 AttributedCharacterIterator.Attribute GVT_FONT_FAMILIES 95 = GVTAttributedCharacterIterator.TextAttribute.GVT_FONT_FAMILIES; 96 97 public static final 98 AttributedCharacterIterator.Attribute BIDI_LEVEL 99 = GVTAttributedCharacterIterator.TextAttribute.BIDI_LEVEL; 100 101 public static final 102 AttributedCharacterIterator.Attribute XPOS 103 = GVTAttributedCharacterIterator.TextAttribute.X; 104 105 public static final 106 AttributedCharacterIterator.Attribute YPOS 107 = GVTAttributedCharacterIterator.TextAttribute.Y; 108 109 public static final 110 AttributedCharacterIterator.Attribute TEXTPATH 111 = GVTAttributedCharacterIterator.TextAttribute.TEXTPATH; 112 113 114 public static final AttributedCharacterIterator.Attribute WRITING_MODE 115 = GVTAttributedCharacterIterator.TextAttribute.WRITING_MODE; 116 117 public static final Integer WRITING_MODE_TTB 118 = GVTAttributedCharacterIterator.TextAttribute.WRITING_MODE_TTB; 119 120 public static final Integer WRITING_MODE_RTL 121 = GVTAttributedCharacterIterator.TextAttribute.WRITING_MODE_RTL; 122 123 public static final 124 AttributedCharacterIterator.Attribute ANCHOR_TYPE 125 = GVTAttributedCharacterIterator.TextAttribute.ANCHOR_TYPE; 126 127 public static final Integer ADJUST_SPACING = 128 GVTAttributedCharacterIterator.TextAttribute.ADJUST_SPACING; 129 public static final Integer ADJUST_ALL = 130 GVTAttributedCharacterIterator.TextAttribute.ADJUST_ALL; 131 public static final GVTAttributedCharacterIterator.TextAttribute ALT_GLYPH_HANDLER = 132 GVTAttributedCharacterIterator.TextAttribute.ALT_GLYPH_HANDLER; 133 134 static Set extendedAtts = new HashSet (); 135 136 static { 137 extendedAtts.add(FLOW_PARAGRAPH); 138 extendedAtts.add(TEXT_COMPOUND_DELIMITER); 139 extendedAtts.add(GVT_FONT); 140 } 142 143 146 protected static TextPainter singleton = new StrokingTextPainter(); 147 148 151 public static TextPainter getInstance() { 152 return singleton; 153 } 154 155 161 public void paint(TextNode node, Graphics2D g2d) { 162 AttributedCharacterIterator aci; 163 aci = node.getAttributedCharacterIterator(); 164 165 List textRuns = getTextRuns(node, aci); 166 167 paintDecorations(textRuns, g2d, TextSpanLayout.DECORATION_UNDERLINE); 170 paintDecorations(textRuns, g2d, TextSpanLayout.DECORATION_OVERLINE); 171 paintTextRuns(textRuns, g2d); 172 paintDecorations 173 (textRuns, g2d, TextSpanLayout.DECORATION_STRIKETHROUGH); 174 } 175 176 protected void printAttrs(AttributedCharacterIterator aci) { 177 aci.first(); 178 int start = aci.getBeginIndex(); 179 System.out.print("AttrRuns: "); 180 while (aci.current() != CharacterIterator.DONE) { 181 int end = aci.getRunLimit(); 182 System.out.print(""+(end-start)+", "); 183 aci.setIndex(end); 184 start = end; 185 } 186 System.out.println(""); 187 } 188 189 public List getTextRuns(TextNode node, AttributedCharacterIterator aci) { 191 List textRuns = node.getTextRuns(); 192 if (textRuns != null) { 193 return textRuns; 194 } 195 196 AttributedCharacterIterator [] chunkACIs = getTextChunkACIs(aci); 197 textRuns = computeTextRuns(node, aci, chunkACIs); 198 199 node.setTextRuns(textRuns); 204 return textRuns; 205 } 206 207 public List computeTextRuns(TextNode node, 208 AttributedCharacterIterator aci, 209 AttributedCharacterIterator [] chunkACIs) { 210 int [][] chunkCharMaps = new int[chunkACIs.length][]; 211 212 int chunkStart = aci.getBeginIndex(); 216 for (int i = 0; i < chunkACIs.length; i++) { 217 BidiAttributedCharacterIterator iter; 218 iter = new BidiAttributedCharacterIterator 219 (chunkACIs[i], fontRenderContext, chunkStart); 220 chunkACIs [i] = iter; 221 chunkCharMaps[i] = iter.getCharMap(); 222 chunkACIs [i] = createModifiedACIForFontMatching 226 (node, chunkACIs[i]); 227 228 chunkStart += (chunkACIs[i].getEndIndex()- 229 chunkACIs[i].getBeginIndex()); 230 } 234 235 List textRuns = new ArrayList (); 237 TextChunk chunk, prevChunk=null; 238 int currentChunk = 0; 239 240 Point2D location = node.getLocation(); 241 do { 242 chunkACIs[currentChunk].first(); 245 246 chunk = getTextChunk(node, 247 chunkACIs[currentChunk], 248 chunkCharMaps[currentChunk], 249 textRuns, 250 prevChunk); 251 252 chunkACIs[currentChunk].first(); 254 if (chunk != null) { 255 location = adjustChunkOffsets(location, textRuns, chunk); 256 } 257 prevChunk = chunk; 258 currentChunk++; 259 260 } while (chunk != null && currentChunk < chunkACIs.length); 261 262 return textRuns; 263 } 264 265 269 protected AttributedCharacterIterator [] getTextChunkACIs 270 (AttributedCharacterIterator aci) { 271 272 List aciList = new ArrayList (); 273 int chunkStartIndex = aci.getBeginIndex(); 274 aci.first(); 275 Object writingMode = aci.getAttribute(WRITING_MODE); 276 boolean vertical = (writingMode == WRITING_MODE_TTB); 277 278 while (aci.setIndex(chunkStartIndex) != CharacterIterator.DONE) { 279 TextPath prevTextPath = null; 280 for (int start=chunkStartIndex, end=0; 281 aci.setIndex(start) != CharacterIterator.DONE; start=end) { 282 283 TextPath textPath = (TextPath) aci.getAttribute(TEXTPATH); 284 285 if (start != chunkStartIndex) { 286 if (vertical) { 292 Float runY = (Float ) aci.getAttribute(YPOS); 293 if ((runY != null) && !runY.isNaN()) 295 break; } else { 297 Float runX = (Float ) aci.getAttribute(XPOS); 298 if ((runX != null) && !runX.isNaN()) 300 break; } 302 303 if ((prevTextPath == null) && (textPath != null)) 305 break; 307 if ((prevTextPath != null) && (textPath == null)) 311 break; 312 } 313 314 prevTextPath = textPath; 315 316 if (aci.getAttribute(FLOW_PARAGRAPH) != null) { 319 end = aci.getRunLimit(FLOW_PARAGRAPH); 320 aci.setIndex(end); 322 break; 323 } 324 325 end = aci.getRunLimit(TEXT_COMPOUND_DELIMITER); 327 328 if (start != chunkStartIndex) 329 continue; 333 334 TextNode.Anchor anchor; 337 anchor = (TextNode.Anchor) aci.getAttribute(ANCHOR_TYPE); 338 if (anchor == TextNode.Anchor.START) 339 continue; 340 341 if (vertical) { 347 Float runY = (Float ) aci.getAttribute(YPOS); 348 if ((runY == null) || runY.isNaN()) 350 continue; 352 } else { 353 Float runX = (Float ) aci.getAttribute(XPOS); 354 if ((runX == null) || runX.isNaN()) 356 continue; 358 } 359 360 for (int i=start+1; i< end; i++) { 363 aci.setIndex(i); 364 if (vertical) { 365 Float runY = (Float ) aci.getAttribute(YPOS); 366 if ((runY == null) || runY.isNaN()) 367 break; 368 } else { 369 Float runX = (Float ) aci.getAttribute(XPOS); 370 if ((runX == null) || runX.isNaN()) 371 break; 372 } 373 aciList.add(new AttributedCharacterSpanIterator 374 (aci, i-1, i)); 375 chunkStartIndex = i; 376 } 377 } 378 379 int chunkEndIndex = aci.getIndex(); 381 aciList.add(new AttributedCharacterSpanIterator 384 (aci, chunkStartIndex, chunkEndIndex)); 385 386 chunkStartIndex = chunkEndIndex; 387 } 388 389 AttributedCharacterIterator [] aciArray = 391 new AttributedCharacterIterator [aciList.size()]; 392 Iterator iter = aciList.iterator(); 393 for (int i=0; iter.hasNext(); ++i) { 394 aciArray[i] = (AttributedCharacterIterator )iter.next(); 395 } 396 return aciArray; 397 } 398 399 410 protected AttributedCharacterIterator createModifiedACIForFontMatching 411 (TextNode node, AttributedCharacterIterator aci) { 412 413 aci.first(); 414 AttributedString as = null; 415 int asOff = 0; 416 int begin = aci.getBeginIndex(); 417 boolean moreChunks = true; 418 int start, end = aci.getRunStart(TEXT_COMPOUND_DELIMITER); 419 while (moreChunks) { 420 start = end; 421 end = aci.getRunLimit(TEXT_COMPOUND_DELIMITER); 422 int aciLength = end-start; 423 424 List fontFamilies; 425 fontFamilies = (List )aci.getAttributes().get(GVT_FONT_FAMILIES); 426 427 if (fontFamilies == null ) { 428 asOff += aciLength; 430 moreChunks = (aci.setIndex(end) != AttributedCharacterIterator.DONE); 431 continue; 432 } 433 434 List resolvedFontFamilies = new ArrayList (fontFamilies.size()); 436 for (int i = 0; i < fontFamilies.size(); i++) { 437 GVTFontFamily fontFamily = (GVTFontFamily)fontFamilies.get(i); 438 if (fontFamily instanceof UnresolvedFontFamily) { 439 fontFamily = FontFamilyResolver.resolve 440 ((UnresolvedFontFamily)fontFamily); 441 } 442 if (fontFamily != null) resolvedFontFamilies.add(fontFamily); 444 } 445 446 if (resolvedFontFamilies.size() == 0) { 449 resolvedFontFamilies.add(FontFamilyResolver.defaultFont); 450 } 451 452 float fontSize = 12; 454 Float fsFloat = (Float )aci.getAttributes().get(TextAttribute.SIZE); 455 if (fsFloat != null) { 456 fontSize = fsFloat.floatValue(); 457 } 458 459 boolean[] fontAssigned = new boolean[aciLength]; 462 463 if (as == null) 464 as = new AttributedString (aci); 465 466 GVTFont defaultFont = null; 467 int numSet=0; 468 int firstUnset=start; 469 boolean firstUnsetSet; 470 for (int i = 0; i < resolvedFontFamilies.size(); i++) { 471 int currentIndex = firstUnset; 474 firstUnsetSet = false; 475 aci.setIndex(currentIndex); 476 477 GVTFontFamily ff; 478 ff = ((GVTFontFamily)resolvedFontFamilies.get(i)); 479 GVTFont font = ff.deriveFont(fontSize, aci); 480 if (defaultFont == null) 481 defaultFont = font; 482 483 while (currentIndex < end) { 484 int displayUpToIndex = font.canDisplayUpTo 485 (aci, currentIndex, end); 486 487 Object altGlyphElement = aci.getAttributes().get(ALT_GLYPH_HANDLER); 488 if ( altGlyphElement != null ){ 489 displayUpToIndex = -1; 492 } 493 494 if (displayUpToIndex == -1) { 495 displayUpToIndex = end; 497 } 498 499 if (displayUpToIndex <= currentIndex) { 500 if (!firstUnsetSet) { 501 firstUnset = currentIndex; 502 firstUnsetSet = true; 503 } 504 currentIndex++; 506 } else { 507 int runStart = -1; 511 for (int j = currentIndex; j < displayUpToIndex; j++) { 512 if (fontAssigned[j - start]) { 513 if (runStart != -1) { 514 as.addAttribute(GVT_FONT, font, 516 runStart-begin, j-begin); 517 runStart=-1; 518 } 519 } else { 520 if (runStart == -1) 521 runStart = j; 522 } 523 fontAssigned[j - start] = true; 524 numSet++; 525 } 526 if (runStart != -1) { 527 as.addAttribute(GVT_FONT, font, 529 runStart-begin, 530 displayUpToIndex-begin); 531 } 532 533 currentIndex = displayUpToIndex+1; 536 } 537 } 538 539 if (numSet == aciLength) break; 541 } 542 543 int runStart = -1; 545 GVTFontFamily prevFF = null; 546 GVTFont prevF = defaultFont; 547 for (int i = 0; i < aciLength; i++) { 548 if (fontAssigned[i]) { 549 if (runStart != -1) { 550 as.addAttribute(GVT_FONT, prevF, 552 runStart+asOff, i+asOff); 553 runStart = -1; 554 prevF = null; 555 prevFF = null; 556 } 557 } else { 558 char c = aci.setIndex(start+i); 559 GVTFontFamily fontFamily; 560 fontFamily = FontFamilyResolver.getFamilyThatCanDisplay(c); 561 563 if (runStart == -1) { 564 runStart = i; 566 prevFF = fontFamily; 567 if (prevFF == null) 568 prevF = defaultFont; 569 else 570 prevF = fontFamily.deriveFont(fontSize, aci); 571 } else if (prevFF != fontFamily) { 572 as.addAttribute(GVT_FONT, prevF, 575 runStart+asOff, i+asOff); 576 577 runStart = i; 578 prevFF = fontFamily; 579 if (prevFF == null) 580 prevF = defaultFont; 581 else 582 prevF = fontFamily.deriveFont(fontSize, aci); 583 } 584 } 585 } 586 if (runStart != -1) { 587 as.addAttribute(GVT_FONT, prevF, 589 runStart+asOff, aciLength+asOff); 590 } 591 592 asOff += aciLength; 593 if (aci.setIndex(end) == AttributedCharacterIterator.DONE) { 594 moreChunks = false; 595 } 596 start = end; 597 } 598 if (as != null) 599 return as.getIterator(); 600 601 return aci; 603 } 604 605 606 protected TextChunk getTextChunk(TextNode node, 607 AttributedCharacterIterator aci, 608 int [] charMap, 609 List textRuns, 610 TextChunk prevChunk) { 611 int beginChunk = 0; 612 if (prevChunk != null) 613 beginChunk = prevChunk.end; 614 int endChunk = beginChunk; 615 int begin = aci.getIndex(); 616 if (aci.current() == CharacterIterator.DONE) 618 return null; 619 620 Point2D.Float offset = new Point2D.Float (0,0); 623 Point2D.Float advance = new Point2D.Float (0,0); 624 boolean isChunkStart = true; 625 TextSpanLayout layout = null; 626 do { 627 int start = aci.getRunStart(extendedAtts); 628 int end = aci.getRunLimit(extendedAtts); 629 630 AttributedCharacterIterator runaci; 631 runaci = new AttributedCharacterSpanIterator(aci, start, end); 632 633 int [] subCharMap = new int[end-start]; 634 for (int i=0; i<subCharMap.length; i++) { 635 subCharMap[i] = charMap[i+start-begin]; 636 } 637 638 FontRenderContext frc = fontRenderContext; 639 RenderingHints rh = node.getRenderingHints(); 640 if ((rh != null) && 643 (rh.get(RenderingHints.KEY_TEXT_ANTIALIASING) == 644 RenderingHints.VALUE_TEXT_ANTIALIAS_OFF)) { 645 frc = aaOffFontRenderContext; 648 } 649 650 layout = getTextLayoutFactory().createTextLayout 651 (runaci, subCharMap, offset, frc); 652 653 textRuns.add(new TextRun(layout, runaci, isChunkStart)); 654 657 Point2D layoutAdvance = layout.getAdvance2D(); 658 advance.x += (float)layoutAdvance.getX(); 660 advance.y += (float)layoutAdvance.getY(); 661 662 ++endChunk; 663 if (aci.setIndex(end) == CharacterIterator.DONE) break; 664 isChunkStart = false; 665 } while (true); 666 667 return new TextChunk(beginChunk, endChunk, advance); 671 } 672 673 674 675 679 protected Point2D adjustChunkOffsets(Point2D location, 680 List textRuns, 681 TextChunk chunk) { 682 TextRun r = (TextRun) textRuns.get(chunk.begin); 683 int anchorType = r.getAnchorType(); 684 Float length = r.getLength(); 685 Integer lengthAdj = r.getLengthAdjust(); 686 687 boolean doAdjust = true; 688 if ((length == null) || length.isNaN()) 689 doAdjust = false; 690 691 int numChars = 0; 692 for (int n=chunk.begin; n<chunk.end; ++n) { 693 r = (TextRun) textRuns.get(n); 694 AttributedCharacterIterator aci = r.getACI(); 695 numChars += aci.getEndIndex()-aci.getBeginIndex(); 696 } 697 if ((lengthAdj == 698 GVTAttributedCharacterIterator.TextAttribute.ADJUST_SPACING) && 699 (numChars == 1)) 700 doAdjust = false; 701 702 float xScale = 1; 703 float yScale = 1; 704 705 r = (TextRun)textRuns.get(chunk.end-1); 706 TextSpanLayout layout = r.getLayout(); 707 GVTGlyphMetrics lastMetrics = 708 layout.getGlyphMetrics(layout.getGlyphCount()-1); 709 Rectangle2D lastBounds = lastMetrics.getBounds2D(); 710 float lastW = (float)(lastBounds.getWidth()+lastBounds.getX()); 711 float lastH = (float)(lastBounds.getHeight()); 712 Point2D visualAdvance; 713 714 &nbs
|