| 1 7 8 22 23 package java.awt.font; 24 25 import java.awt.Color ; 26 import java.awt.Font ; 27 import java.awt.Graphics2D ; 28 import java.awt.Shape ; 29 import java.awt.font.NumericShaper ; 30 import java.awt.font.TextLine.TextLineMetrics ; 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.AttributedString ; 36 import java.text.AttributedCharacterIterator ; 37 import java.text.AttributedCharacterIterator.Attribute; 38 import java.util.Map ; 39 import java.util.HashMap ; 40 import java.util.Hashtable ; 41 import sun.font.AdvanceCache; 42 import sun.font.CoreMetrics; 43 import sun.font.Decoration; 44 import sun.font.FontLineMetrics; 45 import sun.font.FontResolver; 46 import sun.font.GraphicComponent; 47 import sun.text.CodePointIterator; 48 49 199 public final class TextLayout implements Cloneable { 200 201 private int characterCount; 202 private boolean isVerticalLine = false; 203 private byte baseline; 204 private float[] baselineOffsets; private TextLine textLine; 206 207 private TextLine.TextLineMetrics lineMetrics = null; 210 private float visibleAdvance; 211 private int hashCodeCache; 212 213 216 static class OptInfo implements Decoration.Label { 217 private static final float MAGIC_ADVANCE = -12345.67f; 218 219 private FontRenderContext frc; 221 private char[] chars; 222 private Font font; 223 private CoreMetrics metrics; 224 private Map attrs; 225 226 private float advance; 228 private Rectangle2D vb; 229 private Decoration decoration; 230 private String str; 231 232 private OptInfo(FontRenderContext frc, char[] chars, Font font, CoreMetrics metrics, Map attrs) { 233 this.frc = frc; 234 this.chars = chars; 235 this.font = font; 236 this.metrics = metrics; 237 this.attrs = attrs; 238 239 if (attrs != null) { 240 this.attrs = new HashMap (attrs); } 242 243 this.advance = MAGIC_ADVANCE; 244 } 245 246 TextLine createTextLine() { 247 return TextLine.fastCreateTextLine(frc, chars, font, metrics, attrs); 248 } 249 250 float getAdvance() { 251 if (advance == MAGIC_ADVANCE) { 252 AdvanceCache adv = AdvanceCache.get(font, frc); 253 advance = adv.getAdvance(chars, 0, chars.length); } 255 return advance; 256 } 257 258 public CoreMetrics getCoreMetrics() { 260 return metrics; 261 } 262 263 public Rectangle2D getLogicalBounds() { 265 return new Rectangle2D.Float (0, -metrics.ascent, getAdvance(), metrics.height); 266 } 267 268 public void handleDraw(Graphics2D g2d, float x, float y) { 270 if (str == null) { 271 str = new String (chars, 0, chars.length); 272 } 273 g2d.drawString(str, x , y); 274 } 275 276 public Rectangle2D handleGetCharVisualBounds(int index) { 278 throw new InternalError (); 280 } 281 282 public Rectangle2D handleGetVisualBounds() { 284 AdvanceCache adv = AdvanceCache.get(font, frc); 285 return adv.getVisualBounds(chars, 0, chars.length); 286 } 287 288 public Shape handleGetOutline(float x, float y) { 290 throw new InternalError (); 292 } 293 294 boolean draw(Graphics2D g2d, float x, float y) { 296 if (g2d.getFontRenderContext().equals(frc)) { 299 Font oldFont = g2d.getFont(); 300 g2d.setFont(font); 301 302 getDecoration().drawTextAndDecorations(this, g2d, x, y); 303 304 g2d.setFont(oldFont); 305 306 return true; 307 } 308 return false; 309 } 310 311 Rectangle2D getVisualBounds() { 312 if (vb == null) { 313 vb = getDecoration().getVisualBounds(this); 314 } 315 return (Rectangle2D )vb.clone(); 316 } 317 318 Decoration getDecoration() { 319 if (decoration == null) { 320 if (attrs == null) { 321 decoration = Decoration.getDecoration(null); 322 } else { 323 decoration = Decoration.getDecoration(StyledParagraph.addInputMethodAttrs(attrs)); 324 } 325 } 326 return decoration; 327 } 328 329 static OptInfo create(FontRenderContext frc, char[] chars, Font font, CoreMetrics metrics, Map attrs) { 330 335 if (!font.isTransformed() && AdvanceCache.supportsText(chars)) { 336 if (attrs == null || attrs.get(TextAttribute.CHAR_REPLACEMENT) == null) { 337 return new OptInfo(frc, chars, font, metrics, attrs); 338 } 339 } 340 return null; 341 } 342 } 343 private OptInfo optInfo; 344 345 350 private boolean cacheIsValid = false; 351 352 353 private float justifyRatio; 356 357 private static final float ALREADY_JUSTIFIED = -53.9f; 361 362 private static float dx; 372 private static float dy; 373 374 378 private Rectangle2D naturalBounds = null; 379 380 384 private Rectangle2D boundsRect = null; 385 386 390 private boolean caretsInLigaturesAreAllowed = false; 391 392 409 public static class CaretPolicy { 410 411 414 public CaretPolicy() { 415 } 416 417 428 public TextHitInfo getStrongCaret(TextHitInfo hit1, 429 TextHitInfo hit2, 430 TextLayout layout) { 431 432 return layout.getStrongHit(hit1, hit2); 434 } 435 } 436 437 445 public static final CaretPolicy DEFAULT_CARET_POLICY = new CaretPolicy(); 446 447 464 public TextLayout(String string, Font font, FontRenderContext frc) { 465 466 if (font == null) { 467 throw new IllegalArgumentException ("Null font passed to TextLayout constructor."); 468 } 469 470 if (string == null) { 471 throw new IllegalArgumentException ("Null string passed to TextLayout constructor."); 472 } 473 474 if (string.length() == 0) { 475 throw new IllegalArgumentException ("Zero length string passed to TextLayout constructor."); 476 } 477 478 char[] text = string.toCharArray(); 479 if (sameBaselineUpTo(font, text, 0, text.length) == text.length) { 480 fastInit(text, font, null, frc); 481 } else { 482 AttributedString as = new AttributedString (string); 483 as.addAttribute(TextAttribute.FONT, font); 484 standardInit(as.getIterator(), text, frc); 485 } 486 } 487 488 505 public TextLayout(String string, Map <? extends Attribute,?> attributes, 506 FontRenderContext frc) 507 { 508 509 if (string == null) { 510 throw new IllegalArgumentException ("Null string passed to TextLayout constructor."); 511 } 512 513 if (attributes == null) { 514 throw new IllegalArgumentException ("Null map passed to TextLayout constructor."); 515 } 516 517 if (string.length() == 0) { 518 throw new IllegalArgumentException ("Zero length string passed to TextLayout constructor."); 519 } 520 521 char[] text = string.toCharArray(); 522 Font font = singleFont(text, 0, text.length, attributes); 523 if (font != null) { 524 fastInit(text, font, attributes, frc); 525 } else { 526 AttributedString as = new AttributedString (string, attributes); 527 standardInit(as.getIterator(), text, frc); 528 } 529 } 530 531 538 private static Font singleFont(char[] text, 539 int start, 540 int limit, 541 Map attributes) { 542 543 if (attributes.get(TextAttribute.CHAR_REPLACEMENT) != null) { 544 return null; 545 } 546 547 Font font = (Font )attributes.get(TextAttribute.FONT); 548 if (font == null) { 549 if (attributes.get(TextAttribute.FAMILY) != null) { 550 font = Font.getFont(attributes); 551 if (font.canDisplayUpTo(text, start, limit) != -1) { 552 return null; 553 } 554 } else { 555 FontResolver resolver = FontResolver.getInstance(); 556 CodePointIterator iter = CodePointIterator.create(text, start, limit); 557 int fontIndex = resolver.nextFontRunIndex(iter); 558 if (iter.charIndex() == limit) { 559 font = resolver.getFont(fontIndex, attributes); 560 } 561 } 562 } 563 564 if (sameBaselineUpTo(font, text, start, limit) != limit) { 565 return null; 566 } 567 568 return font; 569 } 570 571 585 public TextLayout(AttributedCharacterIterator text, FontRenderContext frc) { 586 587 if (text == null) { 588 throw new IllegalArgumentException ("Null iterator passed to TextLayout constructor."); 589 } 590 591 int start = text.getBeginIndex(); 592 int limit = text.getEndIndex(); 593 if (start == limit) { 594 throw new IllegalArgumentException ("Zero length iterator passed to TextLayout constructor."); 595 } 596 597 int len = limit - start; 598 text.first(); 599 char[] chars = new char[len]; 600 int n = 0; 601 for (char c = text.first(); c != text.DONE; c = text.next()) { 602 chars[n++] = c; 603 } 604 605 text.first(); 606 if (text.getRunLimit() == limit) { 607 608 Map attributes = text.getAttributes(); 609 Font font = singleFont(chars, 0, len, attributes); 610 if (font != null) { 611 fastInit(chars, font, attributes, frc); 612 return; 613 } 614 } 615 616 standardInit(text, chars, frc); 617 } 618 619 631 TextLayout(TextLine textLine, 632 byte baseline, 633 float[] baselineOffsets, 634 float justifyRatio) { 635 636 this.characterCount = textLine.characterCount(); 637 this.baseline = baseline; 638 this.baselineOffsets = baselineOffsets; 639 this.textLine = textLine; 640 this.justifyRatio = justifyRatio; 641 } 642 643 646 private void paragraphInit(byte aBaseline, CoreMetrics lm, Map paragraphAttrs, char[] text) { 647 648 baseline = aBaseline; 649 650 baselineOffsets = TextLine.getNormalizedOffsets(lm.baselineOffsets, baseline); 652 653 justifyRatio = TextLine.getJustifyRatio(paragraphAttrs); 654 655 if (paragraphAttrs != null) { 656 Object o = paragraphAttrs.get(TextAttribute.NUMERIC_SHAPING); 657 if (o != null) { 658 try { 659 NumericShaper shaper = (NumericShaper )o; 660 shaper.shape(text, 0, text.length); 661 } 662 catch (ClassCastException e) { 663 } 664 } 665 } 666 } 667 668 674 private void fastInit(char[] chars, Font font, Map attrs, FontRenderContext frc) { 675 isVerticalLine = false; 678 679 LineMetrics lm = font.getLineMetrics(chars, 0, chars.length, frc); 680 CoreMetrics cm = CoreMetrics.get(lm); 681 byte glyphBaseline = (byte) cm.baselineIndex; 682 683 if (attrs == null) { 684 baseline = glyphBaseline; 685 baselineOffsets = cm.baselineOffsets; 686 justifyRatio = 1.0f; 687 } else { 688 paragraphInit(glyphBaseline, cm, attrs, chars); 689 } 690 691 characterCount = chars.length; 692 693 optInfo = OptInfo.create(frc, chars, font, cm, attrs); 694 if (optInfo == null) { 695 textLine = TextLine.fastCreateTextLine(frc, chars, font, cm, attrs); 696 } 697 } 698 699 private void initTextLine() { 700 textLine = optInfo.createTextLine(); 701 optInfo = null; 702 } 703 704 709 private void standardInit(AttributedCharacterIterator text, char[] chars, FontRenderContext frc) { 710 711 characterCount = chars.length; 712 713 { 715 720 Map paragraphAttrs = text.getAttributes(); 721 722 boolean haveFont = TextLine.advanceToFirstFont(text); 723 724 if (haveFont) { 725 Font defaultFont = TextLine.getFontAtCurrentPos(text); 726 int charsStart = text.getIndex() - text.getBeginIndex(); 727 LineMetrics lm = defaultFont.getLineMetrics(chars, charsStart, charsStart+1, frc); 728 CoreMetrics cm = CoreMetrics.get(lm); 729 paragraphInit((byte)cm.baselineIndex, cm, paragraphAttrs, chars); 730 } 731 else { 732 735 GraphicAttribute graphic = (GraphicAttribute ) 736 paragraphAttrs.get(TextAttribute.CHAR_REPLACEMENT); 737 byte defaultBaseline = getBaselineFromGraphic(graphic); 738 CoreMetrics cm = GraphicComponent.createCoreMetrics(graphic); 739 paragraphInit(defaultBaseline, cm, paragraphAttrs, chars); 740 } 741 } 742 743 textLine = TextLine.standardCreateTextLine(frc, text, chars, baselineOffsets); 744 } 745 746 751 private void ensureCache() { 752 if (!cacheIsValid) { 753 buildCache(); 754 } 755 } 756 757 private void buildCache() { 758 if (textLine == null) { 759 initTextLine(); 760 } 761 762 lineMetrics = textLine.getMetrics(); 763 764 if (textLine.isDirectionLTR()) { 766 767 int lastNonSpace = characterCount-1; 768 while (lastNonSpace != -1) { 769 int logIndex = textLine.visualToLogical(lastNonSpace); 770 if (!textLine.isCharSpace(logIndex)) { 771 break; 772 } 773 else { 774 --lastNonSpace; 775 } 776 } 777 if (lastNonSpace == characterCount-1) { 778 visibleAdvance = lineMetrics.advance; 779 } 780 else if (lastNonSpace == -1) { 781 visibleAdvance = 0; 782 } 783 else { 784 int logIndex = textLine.visualToLogical(lastNonSpace); 785 visibleAdvance = textLine.getCharLinePosition(logIndex) 786 + textLine.getCharAdvance(logIndex); 787 } 788 } 789 else { 790 791 int leftmostNonSpace = 0; 792 while (leftmostNonSpace != characterCount) { 793 int logIndex = textLine.visualToLogical(leftmostNonSpace); 794 if (!textLine.isCharSpace(logIndex)) { 795 break; 796 } 797 else { 798 ++leftmostNonSpace; 799 } 800 } 801 if (leftmostNonSpace == characterCount) { 802 visibleAdvance = 0; 803 } 804 else if (leftmostNonSpace == 0) { 805 visibleAdvance = lineMetrics.advance; 806 } 807 else { 808 int logIndex = textLine.visualToLogical(leftmostNonSpace); 809 float pos = textLine.getCharLinePosition(logIndex); 810 visibleAdvance = lineMetrics.advance - pos; 811 } 812 } 813 814 naturalBounds = null; 816 boundsRect = null; 817 818 hashCodeCache = 0; 820 821 cacheIsValid = true; 822 } 823 824 828 private Rectangle2D getNaturalBounds() { 829 ensureCache(); 830 831 if (naturalBounds == null) { 832 naturalBounds = textLine.getItalicBounds(); 833 } 834 835 return naturalBounds; 836 } 837 838 841 protected Object clone() { 842 854 try { 855 return super.clone(); 856 } 857 catch (CloneNotSupportedException e) { 858 throw new InternalError (); 859 } 860 } 861 862 866 private void checkTextHit(TextHitInfo hit) { 867 if (hit == null) { 868 throw new IllegalArgumentException ("TextHitInfo is null."); 869 } 870 871 if (hit.getInsertionIndex() < 0 || 872 hit.getInsertionIndex() > characterCount) { 873 throw new IllegalArgumentException ("TextHitInfo is out of range"); 874 } 875 } 876 877 |