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 if (!doAdjust) { 715 visualAdvance = new Point2D.Float 720 ((float)(chunk.advance.getX() + lastW - 721 lastMetrics.getHorizontalAdvance()), 722 (float)(chunk.advance.getY() + lastH - 723 lastMetrics.getVerticalAdvance())); 724 } else { 725 Point2D advance = chunk.advance; 726 727 if (layout.isVertical()) { 731 if (lengthAdj == ADJUST_SPACING) { 732 yScale = (float) 733 ((length.floatValue()-lastH)/ 734 (advance.getY()-lastMetrics.getVerticalAdvance())); 735 } else { 736 double adv = (advance.getY()- 737 lastMetrics.getVerticalAdvance() + lastH); 738 yScale = (float)(length.floatValue()/adv); 739 } 740 visualAdvance = new Point2D.Float (0, length.floatValue()); 741 } else { 742 if (lengthAdj == ADJUST_SPACING) { 743 xScale = (float) 744 ((length.floatValue()-lastW)/ 745 (advance.getX()-lastMetrics.getHorizontalAdvance())); 746 } else { 747 double adv = (advance.getX() + lastW - 748 lastMetrics.getHorizontalAdvance()); 749 xScale = (float)(length.floatValue()/adv); 750 } 751 visualAdvance = new Point2D.Float (length.floatValue(), 0); 752 } 753 754 Point2D.Float adv = new Point2D.Float (0,0); 757 for (int n=chunk.begin; n<chunk.end; ++n) { 758 r = (TextRun) textRuns.get(n); 759 layout = r.getLayout(); 760 layout.setScale(xScale, yScale, lengthAdj==ADJUST_SPACING); 761 Point2D lAdv = layout.getAdvance2D(); 762 adv.x += (float)lAdv.getX(); 763 adv.y += (float)lAdv.getY(); 764 } 765 chunk.advance = adv; 766 } 767 768 float dx = 0f; 769 float dy = 0f; 770 switch(anchorType){ 771 case TextNode.Anchor.ANCHOR_MIDDLE: 772 dx = (float) (-visualAdvance.getX()/2d); 773 dy = (float) (-visualAdvance.getY()/2d); 774 break; 775 case TextNode.Anchor.ANCHOR_END: 776 dx = (float) (-visualAdvance.getX()); 777 dy = (float) (-visualAdvance.getY()); 778 break; 779 default: 780 break; 781 } 783 784 786 r = (TextRun) textRuns.get(chunk.begin); 787 layout = r.getLayout(); 788 AttributedCharacterIterator runaci = r.getACI(); 789 runaci.first(); 790 boolean vertical = layout.isVertical(); 791 Float runX = (Float ) runaci.getAttribute(XPOS); 792 Float runY = (Float ) runaci.getAttribute(YPOS); 793 TextPath textPath = (TextPath) runaci.getAttribute(TEXTPATH); 794 795 float absX = (float)location.getX(); 798 float absY = (float)location.getY(); 799 float tpShiftX = 0; 801 float tpShiftY = 0; 802 803 if ((runX != null) && (!runX.isNaN())) { 806 absX = runX.floatValue(); 807 tpShiftX = absX; 808 } 809 810 if ((runY != null) && (!runY.isNaN())) { 811 absY = runY.floatValue(); 812 tpShiftY = absY; 813 } 814 815 if (vertical) { 818 absY += dy; 819 tpShiftY += dy; 820 tpShiftX = 0; 821 } else { 822 absX += dx; 823 tpShiftX += dx; 824 tpShiftY = 0; 825 } 826 827 for (int n=chunk.begin; n<chunk.end; ++n) { 831 r = (TextRun) textRuns.get(n); 832 layout = r.getLayout(); 833 runaci = r.getACI(); 834 runaci.first(); 835 textPath = (TextPath) runaci.getAttribute(TEXTPATH); 836 if (vertical) { 837 runX = (Float ) runaci.getAttribute(XPOS); 838 if ((runX != null) && (!runX.isNaN())) { 839 absX = runX.floatValue(); 840 } 841 } else { 842 runY = (Float ) runaci.getAttribute(YPOS); 843 if ((runY != null) && (!runY.isNaN())) { 844 absY = runY.floatValue(); 845 } 846 } 847 848 if (textPath == null) { 849 layout.setOffset(new Point2D.Float (absX, absY)); 850 851 Point2D ladv = layout.getAdvance2D(); 852 absX += ladv.getX(); 853 absY += ladv.getY(); 854 } else { 855 layout.setOffset(new Point2D.Float (tpShiftX, tpShiftY)); 856 857 Point2D ladv = layout.getAdvance2D(); 858 tpShiftX += (float)ladv.getX(); 859 tpShiftY += (float)ladv.getY(); 860 861 ladv = layout.getTextPathAdvance(); 862 absX = (float)ladv.getX(); 863 absY = (float)ladv.getY(); 864 } 865 } 866 return new Point2D.Float (absX, absY); 867 } 868 869 872 protected void paintDecorations(List textRuns, 873 Graphics2D g2d, 874 int decorationType) { 875 Paint prevPaint = null; 876 Paint prevStrokePaint = null; 877 Stroke prevStroke = null; 878 Rectangle2D decorationRect = null; 879 double yLoc = 0, height = 0; 880 881 for (int i = 0; i < textRuns.size(); i++) { 882 TextRun textRun = (TextRun)textRuns.get(i); 883 AttributedCharacterIterator runaci = textRun.getACI(); 884 runaci.first(); 885 886 TextPaintInfo tpi = (TextPaintInfo)runaci.getAttribute(PAINT_INFO); 887 if ((tpi != null) && (tpi.composite != null)) { 888 g2d.setComposite(tpi.composite); 889 } 890 891 Paint paint = null; 892 Stroke stroke = null; 893 Paint strokePaint = null; 894 if (tpi != null) { 895 switch (decorationType) { 896 case TextSpanLayout.DECORATION_UNDERLINE : 897 paint = tpi.underlinePaint; 898 stroke = tpi.underlineStroke; 899 strokePaint = tpi.underlineStrokePaint; 900 break; 901 case TextSpanLayout.DECORATION_OVERLINE : 902 paint = tpi.overlinePaint; 903 stroke = tpi.overlineStroke; 904 strokePaint = tpi.overlineStrokePaint; 905 break; 906 case TextSpanLayout.DECORATION_STRIKETHROUGH : 907 paint = tpi.strikethroughPaint; 908 stroke = tpi.strikethroughStroke; 909 strokePaint = tpi.strikethroughStrokePaint; 910 break; 911 default: 912 return; 914 } 915 } 916 917 if (textRun.isFirstRunInChunk()) { 918 Shape s = textRun.getLayout().getDecorationOutline 919 (decorationType); 920 Rectangle2D r2d = s.getBounds2D(); 921 yLoc = r2d.getY(); 922 height = r2d.getHeight(); 923 } 924 925 if (textRun.isFirstRunInChunk() || 926 (paint != prevPaint) || 927 (stroke != prevStroke) || 928 (strokePaint != prevStrokePaint)) { 929 if (decorationRect != null) { 931 932 if (prevPaint != null) { 933 g2d.setPaint(prevPaint); 935 g2d.fill(decorationRect); 936 } 937 if (prevStroke != null && prevStrokePaint != null) { 938 g2d.setPaint(prevStrokePaint); 940 g2d.setStroke(prevStroke); 941 g2d.draw(decorationRect); 942 } 943 decorationRect = null; 944 } 945 } 946 947 if ((paint != null || strokePaint != null) 948 && !textRun.getLayout().isVertical() 949 && !textRun.getLayout().isOnATextPath()) { 950 951 956 Shape decorationShape = 957 textRun.getLayout().getDecorationOutline(decorationType); 958 if (decorationRect == null) { 959 Rectangle2D r2d = decorationShape.getBounds2D(); 961 decorationRect = new Rectangle2D.Double 962 (r2d.getX(), yLoc, r2d.getWidth(), height); 963 } else { 964 Rectangle2D bounds = decorationShape.getBounds2D(); 966 double minX = Math.min(decorationRect.getX(), 967 bounds.getX()); 968 double maxX = Math.max(decorationRect.getMaxX(), 969 bounds.getMaxX()); 970 decorationRect.setRect(minX, yLoc, maxX-minX, height); 971 } 972 } 973 prevPaint = paint; 974 prevStroke = stroke; 975 prevStrokePaint = strokePaint; 976 } 977 978 980 if (decorationRect != null) { 981 982 if (prevPaint != null) { 983 g2d.setPaint(prevPaint); 985 g2d.fill(decorationRect); 986 } 987 if (prevStroke != null && prevStrokePaint != null) { 988 g2d.setPaint(prevStrokePaint); 990 g2d.setStroke(prevStroke); 991 g2d.draw(decorationRect); 992 } 993 } 994 } 995 996 997 1000 protected void paintTextRuns(List textRuns, 1001 Graphics2D g2d) { 1002 for (int i = 0; i < textRuns.size(); i++) { 1003 TextRun textRun = (TextRun)textRuns.get(i); 1004 AttributedCharacterIterator runaci = textRun.getACI(); 1005 runaci.first(); 1006 1007 TextPaintInfo tpi = (TextPaintInfo)runaci.getAttribute(PAINT_INFO); 1008 if ((tpi != null) && (tpi.composite != null)) { 1009 g2d.setComposite(tpi.composite); 1010 } 1011 textRun.getLayout().draw(g2d); 1012 } 1013 } 1014 1015 1019 public Shape getOutline(TextNode node) { 1020 1021 GeneralPath outline = null; 1022 AttributedCharacterIterator aci = node.getAttributedCharacterIterator(); 1023 1024 List textRuns = getTextRuns(node, aci); 1026 1027 1030 for (int i = 0; i < textRuns.size(); ++i) { 1031 TextRun textRun = (TextRun)textRuns.get(i); 1032 TextSpanLayout textRunLayout = textRun.getLayout(); 1033 GeneralPath textRunOutline = 1034 new GeneralPath (textRunLayout.getOutline()); 1035 1036 if (outline == null) { 1037 outline = textRunOutline; 1038 } else { 1039 outline.setWindingRule(GeneralPath.WIND_NON_ZERO); 1040 outline.append(textRunOutline, false); 1041 } 1042 } 1043 1044 Shape underline = getDecorationOutline 1046 (textRuns, TextSpanLayout.DECORATION_UNDERLINE); 1047 1048 Shape strikeThrough = getDecorationOutline 1049 (textRuns, TextSpanLayout.DECORATION_STRIKETHROUGH); 1050 1051 Shape overline = getDecorationOutline 1052 (textRuns, TextSpanLayout.DECORATION_OVERLINE); 1053 1054 if (underline != null) { 1055 if (outline == null) { 1056 outline = new GeneralPath (underline); 1057 } else { 1058 outline.setWindingRule(GeneralPath.WIND_NON_ZERO); 1059 outline.append(underline, false); 1060 } 1061 } 1062 if (strikeThrough != null) { 1063 if (outline == null) { 1064 outline = new GeneralPath (strikeThrough); 1065 } else { 1066 outline.setWindingRule(GeneralPath.WIND_NON_ZERO); 1067 outline.append(strikeThrough, false); 1068 } 1069 } 1070 if (overline != null) { 1071 if (outline == null) { 1072 outline = new GeneralPath (overline); 1073 } else { 1074 outline.setWindingRule(GeneralPath.WIND_NON_ZERO); 1075 outline.append(overline, false); 1076 } 1077 } 1078 1079 return outline; 1080 } 1081 1082 1083 1087 public Rectangle2D getBounds2D(TextNode node) { 1088 AttributedCharacterIterator aci; 1089 aci = node.getAttributedCharacterIterator(); 1090 1091 List textRuns = getTextRuns(node, aci); 1093 1094 Rectangle2D bounds = null; 1095 for (int i = 0; i < textRuns.size(); ++i) { 1098 TextRun textRun = (TextRun)textRuns.get(i); 1099 AttributedCharacterIterator textRunACI = textRun.getACI(); 1100 TextSpanLayout textRunLayout = textRun.getLayout(); 1101 Rectangle2D runBounds = textRunLayout.getBounds2D(); 1102 if (runBounds != null) { 1103 if (bounds == null) 1104 bounds = runBounds; 1105 else 1106 bounds = bounds.createUnion(runBounds); 1107 } 1108 } 1109 1110 1111 Shape underline = getDecorationStrokeOutline 1113 (textRuns, TextSpanLayout.DECORATION_UNDERLINE); 1114 1115 if (underline != null) { 1116 if (bounds == null) 1117 bounds = underline.getBounds2D(); 1118 else 1119 bounds = bounds.createUnion(underline.getBounds2D()); 1120 } 1121 1122 Shape strikeThrough = getDecorationStrokeOutline 1123 (textRuns, TextSpanLayout.DECORATION_STRIKETHROUGH); 1124 if (strikeThrough != null) { 1125 if (bounds == null) 1126 bounds = strikeThrough.getBounds2D(); 1127 else 1128 bounds = bounds.createUnion(strikeThrough.getBounds2D()); 1129 } 1130 1131 Shape overline = getDecorationStrokeOutline 1132 (textRuns, TextSpanLayout.DECORATION_OVERLINE); 1133 if (overline != null) { 1134 if (bounds == null) 1135 bounds = overline.getBounds2D(); 1136 else 1137 bounds = bounds.createUnion(overline.getBounds2D()); 1138 } 1139 return bounds; 1140 } 1141 1142 1143 1152 protected Shape getDecorationOutline(List textRuns, int decorationType) { 1153 1154 GeneralPath outline = null; 1155 1156 Paint prevPaint = null; 1157 Paint prevStrokePaint = null; 1158 Stroke prevStroke = null; 1159 Rectangle2D decorationRect = null; 1160 double yLoc = 0, height = 0; 1161 1162 for (int i = 0; i < textRuns.size(); i++) { 1163 TextRun textRun = (TextRun)textRuns.get(i); 1164 AttributedCharacterIterator runaci = textRun.getACI(); 1165 runaci.first(); 1166 1167 Paint paint = null; 1168 Stroke stroke = null; 1169 Paint strokePaint = null; 1170 TextPaintInfo tpi = (TextPaintInfo)runaci.getAttribute(PAINT_INFO); 1171 if (tpi != null) { 1172 switch (decorationType) { 1173 case TextSpanLayout.DECORATION_UNDERLINE : 1174 paint = tpi.underlinePaint; 1175 stroke = tpi.underlineStroke; 1176 strokePaint = tpi.underlineStrokePaint; 1177 break; 1178 case TextSpanLayout.DECORATION_OVERLINE : 1179 paint = tpi.overlinePaint; 1180 stroke = tpi.overlineStroke; 1181 strokePaint = tpi.overlineStrokePaint; 1182 break; 1183 case TextSpanLayout.DECORATION_STRIKETHROUGH : 1184 paint = tpi.strikethroughPaint; 1185 stroke = tpi.strikethroughStroke; 1186 strokePaint = tpi.strikethroughStrokePaint; 1187 break; 1188 default: 1189 return null; 1191 } 1192 } 1193 1194 if (textRun.isFirstRunInChunk()) { 1195 Shape s = textRun.getLayout().getDecorationOutline 1196 (decorationType); 1197 Rectangle2D r2d = s.getBounds2D(); 1198 yLoc = r2d.getY(); 1199 height = r2d.getHeight(); 1200 } 1201 1202 if (textRun.isFirstRunInChunk() || 1203 paint != prevPaint || 1204 stroke != prevStroke || 1205 strokePaint != prevStrokePaint) { 1206 1207 if (decorationRect != null) { 1210 if (outline == null) { 1211 outline = new GeneralPath (decorationRect); 1212 } else { 1213 outline.append(decorationRect, false); 1214 } 1215 decorationRect = null; 1216 } 1217 } 1218 1219 if ((paint != null || strokePaint != null) 1220 && !textRun.getLayout().isVertical() 1221 && !textRun.getLayout().isOnATextPath()) { 1222 1223 1227 Shape decorationShape = 1228 textRun.getLayout().getDecorationOutline(decorationType); 1229 if (decorationRect == null) { 1230 Rectangle2D r2d = decorationShape.getBounds2D(); 1232 decorationRect = new Rectangle2D.Double 1233 (r2d.getX(), yLoc, r2d.getWidth(), height); 1234 } else { 1235 Rectangle2D bounds = decorationShape.getBounds2D(); 1237 double minX = Math.min(decorationRect.getX(), 1238 bounds.getX()); 1239 double maxX = Math.max(decorationRect.getMaxX(), 1240 bounds.getMaxX()); 1241 decorationRect.setRect(minX, yLoc, maxX-minX, height); 1242 } 1243 } 1244 1245 prevPaint = paint; 1246 prevStroke = stroke; 1247 prevStrokePaint = strokePaint; 1248 } 1249 1250 if (decorationRect != null) { 1252 if (outline == null) { 1253 outline = new GeneralPath (decorationRect); 1254 } else { 1255 outline.append(decorationRect, false); 1256 } 1257 } 1258 1259 return outline; 1260 } 1261 1262 1272 protected Shape getDecorationStrokeOutline 1273 (List textRuns, int decorationType) { 1274 1275 GeneralPath outline = null; 1276 1277 Paint prevPaint = null; 1278 Paint prevStrokePaint = null; 1279 Stroke prevStroke = null; 1280 Rectangle2D decorationRect = null; 1281 double yLoc = 0, height = 0; 1282 1283 for (int i = 0; i < textRuns.size(); i++) { 1284 1285 TextRun textRun = (TextRun)textRuns.get(i); 1286 AttributedCharacterIterator runaci = textRun.getACI(); 1287 runaci.first(); 1288 1289 Paint paint = null; 1290 Stroke stroke = null; 1291 Paint strokePaint = null; 1292 TextPaintInfo tpi = (TextPaintInfo)runaci.getAttribute(PAINT_INFO); 1293 if (tpi != null) { 1294 switch (decorationType) { 1295 case TextSpanLayout.DECORATION_UNDERLINE : 1296 paint = tpi.underlinePaint; 1297 stroke = tpi.underlineStroke; 1298 strokePaint = tpi.underlineStrokePaint; 1299 break; 1300 case TextSpanLayout.DECORATION_OVERLINE : 1301 paint = tpi.overlinePaint; 1302 stroke = tpi.overlineStroke; 1303 strokePaint = tpi.overlineStrokePaint; 1304 break; 1305 case TextSpanLayout.DECORATION_STRIKETHROUGH : 1306 paint = tpi.strikethroughPaint; 1307 stroke = tpi.strikethroughStroke; 1308 strokePaint = tpi.strikethroughStrokePaint; 1309 break; 1310 default: 1311 return null; 1313 } 1314 } 1315 1316 if (textRun.isFirstRunInChunk()) { 1317 Shape s = textRun.getLayout().getDecorationOutline 1318 (decorationType); 1319 Rectangle2D r2d = s.getBounds2D(); 1320 yLoc = r2d.getY(); 1321 height = r2d.getHeight(); 1322 } 1323 1324 if (textRun.isFirstRunInChunk() || 1325 paint != prevPaint || 1326 stroke != prevStroke || 1327 strokePaint != prevStrokePaint) { 1328 1329 if (decorationRect != null) { 1332 1333 Shape s = null; 1334 if (prevStroke != null && 1335 prevStrokePaint != null) 1336 s = prevStroke.createStrokedShape(decorationRect); 1337 else if (prevPaint != null) 1338 s = decorationRect; 1339 if (s != null) { 1340 if (outline == null) 1341 outline = new GeneralPath (s); 1342 else 1343 outline.append(s, false); 1344 } 1345 decorationRect = null; 1346 } 1347 } 1348 1349 if ((paint != null || strokePaint != null) 1350 && !textRun.getLayout().isVertical() 1351 && !textRun.getLayout().isOnATextPath()) { 1352 1353 1357 Shape decorationShape = 1358 textRun.getLayout().getDecorationOutline(decorationType); 1359 1360 if (decorationRect == null) { 1361 Rectangle2D r2d = decorationShape.getBounds2D(); 1363 decorationRect = new Rectangle2D.Double 1364 (r2d.getX(), yLoc, r2d.getWidth(), height); 1365 } else { 1366 Rectangle2D bounds = decorationShape.getBounds2D(); 1368 double minX = Math.min(decorationRect.getX(), 1369 bounds.getX()); 1370 double maxX = Math.max(decorationRect.getMaxX(), 1371 bounds.getMaxX()); 1372 decorationRect.setRect(minX, yLoc, maxX-minX, height); 1373 } 1374 } 1375 1376 prevPaint = paint; 1377 prevStroke = stroke; 1378 prevStrokePaint = strokePaint; 1379 } 1380 1381 if (decorationRect != null) { 1384 Shape s = null; 1385 if (prevStroke != null && 1386 prevStrokePaint != null) 1387 s = prevStroke.createStrokedShape(decorationRect); 1388 else if (prevPaint != null) 1389 s = decorationRect; 1390 if (s != null) { 1391 if (outline == null) 1392 outline = new GeneralPath (s); 1393 else 1394 outline.append(s, false); 1395 } 1396 } 1397 1398 return outline; 1399 } 1400 1401 1402 public Mark getMark(TextNode node, int index, boolean leadingEdge) { 1403 AttributedCharacterIterator aci; 1404 aci = node.getAttributedCharacterIterator(); 1405 if ((index < aci.getBeginIndex()) || 1406 (index > aci.getEndIndex())) 1407 return null; 1408 1409 TextHit textHit = new TextHit(index, leadingEdge); 1410 return new BasicTextPainter.BasicMark(node, textHit); 1411 } 1412 1413 protected Mark hitTest(double x, double y, TextNode node) { 1414 AttributedCharacterIterator aci; 1415 aci = node.getAttributedCharacterIterator(); 1416 1417 List textRuns = getTextRuns(node, aci); 1419 1420 for (int i = 0; i < textRuns.size(); ++i) { 1422 TextRun textRun = (TextRun)textRuns.get(i); 1423 TextSpanLayout layout = textRun.getLayout(); 1424 TextHit textHit = layout.hitTestChar((float) x, (float) y); 1425 if (textHit != null && layout.getBounds2D().contains(x,y)) { 1426 return new BasicTextPainter.BasicMark(node, textHit); 1427 } 1428 } 1429 1430 return null; 1431 } 1432 1433 1436 public Mark selectFirst(TextNode node) { 1437 AttributedCharacterIterator aci; 1438 aci = node.getAttributedCharacterIterator(); 1439 TextHit textHit = new TextHit(aci.getBeginIndex(), false); 1440 return new BasicTextPainter.BasicMark(node, textHit); 1441 } 1442 1443 1446 public Mark selectLast(TextNode node) { 1447 AttributedCharacterIterator aci; 1448 aci = node.getAttributedCharacterIterator(); 1449 TextHit textHit = new TextHit(aci.getEndIndex()-1, false); 1450 return new BasicTextPainter.BasicMark(node, textHit); 1451 } 1452 1453 1460 public int[] getSelected(Mark startMark, 1461 Mark finishMark) { 1462 1463 if (startMark == null || finishMark == null) { 1464 return null; 1465 } 1466 BasicTextPainter.BasicMark start; 1467 BasicTextPainter.BasicMark finish; 1468 try { 1469 start = (BasicTextPainter.BasicMark) startMark; 1470 finish = (BasicTextPainter.BasicMark) finishMark; 1471 } catch (ClassCastException cce) { 1472 throw new Error 1473 ("This Mark was not instantiated by this TextPainter class!"); 1474 } 1475 1476 TextNode textNode = start.getTextNode(); 1477 if (textNode != finish.getTextNode()) 1478 throw new Error ("Markers are from different TextNodes!"); 1479 1480 AttributedCharacterIterator aci; 1481 aci = textNode.getAttributedCharacterIterator(); 1482 1483 int[] result = new int[2]; 1484 result[0] = start.getHit().getCharIndex(); 1485 result[1] = finish.getHit().getCharIndex(); 1486 1487 List textRuns = getTextRuns(textNode, aci); 1489 Iterator trI = textRuns.iterator(); 1490 int startGlyphIndex = -1; 1491 int endGlyphIndex = -1; 1492 TextSpanLayout startLayout=null, endLayout=null; 1493 while (trI.hasNext()) { 1494 TextRun tr = (TextRun)trI.next(); 1495 TextSpanLayout tsl = tr.getLayout(); 1496 if (startGlyphIndex == -1) { 1497 startGlyphIndex = tsl.getGlyphIndex(result[0]); 1498 if (startGlyphIndex != -1) 1499 startLayout = tsl; 1500 } 1501 1502 if (endGlyphIndex == -1) { 1503 endGlyphIndex = tsl.getGlyphIndex(result[1]); 1504 if (endGlyphIndex != -1) 1505 endLayout = tsl; 1506 } 1507 if ((startGlyphIndex != -1) && (endGlyphIndex != -1)) 1508 break; 1509 } 1510 if ((startLayout == null) || (endLayout == null)) 1511 return null; 1512 1513 int startCharCount = startLayout.getCharacterCount 1514 (startGlyphIndex, startGlyphIndex); 1515 int endCharCount = endLayout.getCharacterCount 1516 (endGlyphIndex, endGlyphIndex); 1517 if (startCharCount > 1) { 1518 if (result[0] > result[1] && startLayout.isLeftToRight()) { 1519 result[0] += startCharCount-1; 1520 } else if (result[1] > result[0] && !startLayout.isLeftToRight()) { 1521 result[0] -= startCharCount-1; 1522 } 1523 } 1524 if (endCharCount > 1) { 1525 if (result[1] > result[0] && endLayout.isLeftToRight()) { 1526 result[1] += endCharCount-1; 1527 } else if (result[0] > result[1] && !endLayout.isLeftToRight()) { 1528 result[1] -= endCharCount-1; 1529 } 1530 } 1531 1532 return result; 1533 } 1534 1535 1541 public Shape getHighlightShape(Mark beginMark, Mark endMark) { 1542 1543 if (beginMark == null || endMark == null) { 1544 return null; 1545 } 1546 1547 BasicTextPainter.BasicMark begin; 1548 BasicTextPainter.BasicMark end; 1549 try { 1550 begin = (BasicTextPainter.BasicMark) beginMark; 1551 end = (BasicTextPainter.BasicMark) endMark; 1552 } catch (ClassCastException cce) { 1553 throw new Error 1554 ("This Mark was not instantiated by this TextPainter class!"); 1555 } 1556 1557 TextNode textNode = begin.getTextNode(); 1558 if (textNode != end.getTextNode()) 1559 throw new Error ("Markers are from different TextNodes!"); 1560 if (textNode == null) 1561 return null; 1562 1563 int beginIndex = begin.getHit().getCharIndex(); 1564 int endIndex = end.getHit().getCharIndex(); 1565 if (beginIndex > endIndex) { 1566 BasicTextPainter.BasicMark tmpMark = begin; 1568 begin = end; end = tmpMark; 1569 1570 int tmpIndex = beginIndex; 1571 beginIndex = endIndex; endIndex = tmpIndex; 1572 } 1573 1574 List textRuns = getTextRuns 1576 (textNode, textNode.getAttributedCharacterIterator()); 1577 1578 GeneralPath highlightedShape = new GeneralPath (); 1579 1580 for (int i = 0; i < textRuns.size(); ++i) { 1583 TextRun textRun = (TextRun)textRuns.get(i); 1584 TextSpanLayout layout = textRun.getLayout(); 1585 1586 Shape layoutHighlightedShape = layout.getHighlightShape 1587 (beginIndex, endIndex); 1588 1589 if (( layoutHighlightedShape != null) && 1592 (!layoutHighlightedShape.getBounds().isEmpty())) { 1593 highlightedShape.append(layoutHighlightedShape, false); 1594 } 1595 } 1596 return highlightedShape; 1597 } 1598 1599 1601 class TextChunk { 1602 1603 public int begin; 1604 public int end; 1605 public Point2D advance; 1606 1607 public TextChunk(int begin, int end, Point2D advance) { 1608 this.begin = begin; 1609 this.end = end; 1610 this.advance = new Point2D.Float ((float) advance.getX(), 1611 (float) advance.getY()); 1612 } 1613 } 1614 1615 1616 1620 public class TextRun { 1621 1622 protected AttributedCharacterIterator aci; 1623 protected TextSpanLayout layout; 1624 protected int anchorType; 1625 protected boolean firstRunInChunk; 1626 protected Float length; 1627 protected Integer lengthAdjust; 1628 1629 public TextRun(TextSpanLayout layout, 1630 AttributedCharacterIterator aci, 1631 boolean firstRunInChunk) { 1632 1633 this.layout = layout; 1634 this.aci = aci; 1635 this.aci.first(); 1636 this.firstRunInChunk = firstRunInChunk; 1637 this.anchorType = TextNode.Anchor.ANCHOR_START; 1638 1639 TextNode.Anchor anchor = (TextNode.Anchor) aci.getAttribute 1640 (GVTAttributedCharacterIterator.TextAttribute.ANCHOR_TYPE); 1641 if (anchor != null) { 1642 this.anchorType = anchor.getType(); 1643 } 1644 1645 if (aci.getAttribute(WRITING_MODE) == WRITING_MODE_RTL) { 1648 if (anchorType == TextNode.Anchor.ANCHOR_START) { 1649 anchorType = TextNode.Anchor.ANCHOR_END; 1650 } else if (anchorType == TextNode.Anchor.ANCHOR_END) { 1651 anchorType = TextNode.Anchor.ANCHOR_START; 1652 } 1653 } 1655 1656 length = (Float ) aci.getAttribute 1657 (GVTAttributedCharacterIterator.TextAttribute.BBOX_WIDTH); 1658 lengthAdjust = (Integer ) aci.getAttribute 1659 (GVTAttributedCharacterIterator.TextAttribute.LENGTH_ADJUST); 1660 } 1661 1662 public AttributedCharacterIterator getACI() { 1663 return aci; 1664 } 1665 1666 public TextSpanLayout getLayout() { 1667 return layout; 1668 } 1669 1670 public int getAnchorType() { 1671 return anchorType; 1672 } 1673 1674 public Float getLength() { 1675 return length; 1676 } 1677 1678 public Integer getLengthAdjust() { 1679 return lengthAdjust; 1680 } 1681 1682 public boolean isFirstRunInChunk() { 1683 return firstRunInChunk; 1684 } 1685 1686 } 1687} 1688 | Popular Tags |