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 892 public TextLayout getJustifiedLayout(float justificationWidth) { 893 894 if (justificationWidth <= 0) { 895 throw new IllegalArgumentException ("justificationWidth <= 0 passed to TextLayout.getJustifiedLayout()"); 896 } 897 898 if (justifyRatio == ALREADY_JUSTIFIED) { 899 throw new Error ("Can't justify again."); 900 } 901 902 ensureCache(); 904 int limit = characterCount; 906 while (limit > 0 && textLine.isCharWhitespace(limit-1)) { 907 --limit; 908 } 909 910 TextLine newLine = textLine.getJustifiedLine(justificationWidth, justifyRatio, 0, limit); 911 if (newLine != null) { 912 return new TextLayout (newLine, baseline, baselineOffsets, ALREADY_JUSTIFIED); 913 } 914 915 return this; 916 } 917 918 939 protected void handleJustify(float justificationWidth) { 940 } 942 943 944 954 public byte getBaseline() { 955 return baseline; 956 } 957 958 973 public float[] getBaselineOffsets() { 974 float[] offsets = new float[baselineOffsets.length]; 975 System.arraycopy(baselineOffsets, 0, offsets, 0, offsets.length); 976 return offsets; 977 } 978 979 985 public float getAdvance() { 986 if (optInfo != null) { 987 try { 988 return optInfo.getAdvance(); 989 } 990 catch (Error e) { 991 } 993 } 994 ensureCache(); 995 return lineMetrics.advance; 996 } 997 998 1005 public float getVisibleAdvance() { 1006 ensureCache(); 1007 return visibleAdvance; 1008 } 1009 1010 1019 public float getAscent() { 1020 if (optInfo != null) { 1021 return optInfo.getCoreMetrics().ascent; 1022 } 1023 ensureCache(); 1024 return lineMetrics.ascent; 1025 } 1026 1027 1035 public float getDescent() { 1036 if (optInfo != null) { 1037 return optInfo.getCoreMetrics().descent; 1038 } 1039 ensureCache(); 1040 return lineMetrics.descent; 1041 } 1042 1043 1063 public float getLeading() { 1064 if (optInfo != null) { 1065 return optInfo.getCoreMetrics().leading; 1066 } 1067 ensureCache(); 1068 return lineMetrics.leading; 1069 } 1070 1071 1079 public Rectangle2D getBounds() { 1080 if (optInfo != null) { 1081 return optInfo.getVisualBounds(); 1082 } 1083 1084 ensureCache(); 1085 1086 if (boundsRect == null) { 1087 Rectangle2D lineBounds = textLine.getBounds(); 1088 if (dx != 0 || dy != 0) { 1089 lineBounds.setRect(lineBounds.getX() - dx, 1090 lineBounds.getY() - dy, 1091 lineBounds.getWidth(), 1092 lineBounds.getHeight()); 1093 } 1094 boundsRect = lineBounds; 1095 } 1096 1097 Rectangle2D bounds = new Rectangle2D.Float (); 1098 bounds.setRect(boundsRect); 1099 1100 return bounds; 1101 } 1102 1103 1119 public boolean isLeftToRight() { 1120 return (optInfo != null) || textLine.isDirectionLTR(); 1121 } 1122 1123 1128 public boolean isVertical() { 1129 return isVerticalLine; 1130 } 1131 1132 1137 public int getCharacterCount() { 1138 return characterCount; 1139 } 1140 1141 1210 1211 private float[] getCaretInfo(int caret, 1212 Rectangle2D bounds, 1213 float[] info) { 1214 1215 float top1X, top2X; 1216 float bottom1X, bottom2X; 1217 1218 if (caret == 0 || caret == characterCount) { 1219 1220 float pos; 1221 int logIndex; 1222 if (caret == characterCount) { 1223 logIndex = textLine.visualToLogical(characterCount-1); 1224 pos = textLine.getCharLinePosition(logIndex) 1225 + textLine.getCharAdvance(logIndex); 1226 } 1227 else { 1228 logIndex = textLine.visualToLogical(caret); 1229 pos = textLine.getCharLinePosition(logIndex); 1230 } 1231 float angle = textLine.getCharAngle(logIndex); 1232 float shift = textLine.getCharShift(logIndex); 1233 pos += angle * shift; 1234 top1X = top2X = pos + angle*textLine.getCharAscent(logIndex); 1235 bottom1X = bottom2X = pos - angle*textLine.getCharDescent(logIndex); 1236 } 1237 else { 1238 1239 { 1240 int logIndex = textLine.visualToLogical(caret-1); 1241 float angle1 = textLine.getCharAngle(logIndex); 1242 float pos1 = textLine.getCharLinePosition(logIndex) 1243 + textLine.getCharAdvance(logIndex); 1244 if (angle1 != 0) { 1245 pos1 += angle1 * textLine.getCharShift(logIndex); 1246 top1X = pos1 + angle1*textLine.getCharAscent(logIndex); 1247 bottom1X = pos1 - angle1*textLine.getCharDescent(logIndex); 1248 } 1249 else { 1250 top1X = bottom1X = pos1; 1251 } 1252 } 1253 { 1254 int logIndex = textLine.visualToLogical(caret); 1255 float angle2 = textLine.getCharAngle(logIndex); 1256 float pos2 = textLine.getCharLinePosition(logIndex); 1257 if (angle2 != 0) { 1258 pos2 += angle2*textLine.getCharShift(logIndex); 1259 top2X = pos2 + angle2*textLine.getCharAscent(logIndex); 1260 bottom2X = pos2 - angle2*textLine.getCharDescent(logIndex); 1261 } 1262 else { 1263 top2X = bottom2X = pos2; 1264 } 1265 } 1266 } 1267 1268 float topX = (top1X + top2X) / 2; 1269 float bottomX = (bottom1X + bottom2X) / 2; 1270 1271 if (info == null) { 1272 info = new float[2]; 1273 } 1274 1275 if (isVerticalLine) { 1276 info[1] = (float) ((topX - bottomX) / bounds.getWidth()); 1277 info[0] = (float) (topX + (info[1]*bounds.getX())); 1278 } 1279 else { 1280 info[1] = (float) ((topX - bottomX) / bounds.getHeight()); 1281 info[0] = (float) (bottomX + (info[1]*bounds.getMaxY())); 1282 } 1283 1284 return info; 1285 } 1286 1287 1302 public float[] getCaretInfo(TextHitInfo hit, Rectangle2D bounds) { 1303 ensureCache(); 1304 checkTextHit(hit); 1305 1306 return getCaretInfoTestInternal(hit, bounds); 1307 } 1308 1309 private float[] getCaretInfoTestInternal(TextHitInfo hit, Rectangle2D bounds) { 1317 ensureCache(); 1318 checkTextHit(hit); 1319 1320 float[] info = new float[6]; 1321 1322 getCaretInfo(hitToCaret(hit), bounds, info); 1324 1325 double iangle, ixbase, p1x, p1y, p2x, p2y; 1327 1328 int charix = hit.getCharIndex(); 1329 boolean lead = hit.isLeadingEdge(); 1330 boolean ltr = textLine.isDirectionLTR(); 1331 boolean horiz = !isVertical(); 1332 1333 if (charix == -1 || charix == characterCount) { 1334 TextLineMetrics m = textLine.getMetrics(); 1337 boolean low = ltr == (charix == -1); 1338 iangle = 0; 1339 if (horiz) { 1340 p1x = p2x = low ? 0 : m.advance; 1341 p1y = -m.ascent; 1342 p2y = m.descent; 1343 } else { 1344 p1y = p2y = low ? 0 : m.advance; 1345 p1x = m.descent; 1346 p2x = m.ascent; 1347 } 1348 } else { 1349 CoreMetrics thiscm = textLine.getCoreMetricsAt(charix); 1350 iangle = thiscm.italicAngle; 1351 ixbase = textLine.getCharLinePosition(charix, lead); 1352 if (thiscm.baselineIndex < 0) { 1353 TextLineMetrics m = textLine.getMetrics(); 1355 if (horiz) { 1356 p1x = p2x = ixbase; 1357 if (thiscm.baselineIndex == GraphicAttribute.TOP_ALIGNMENT) { 1358 p1y = -m.ascent; 1359 p2y = p1y + thiscm.height; 1360 } else { 1361 p2y = m.descent; 1362 p1y = p2y - thiscm.height; 1363 } 1364 } else { 1365 p1y = p2y = ixbase; 1366 p1x = m.descent; 1367 p2x = m.ascent; 1368 } 1370 } else { 1371 float bo = baselineOffsets[thiscm.baselineIndex]; 1372 if (horiz) { 1373 ixbase += iangle * thiscm.ssOffset; 1374 p1x = ixbase + iangle * thiscm.ascent; 1375 p2x = ixbase - iangle * thiscm.descent; 1376 p1y = bo - thiscm.ascent; 1377 p2y = bo + thiscm.descent; 1378 } else { 1379 ixbase -= iangle * thiscm.ssOffset; 1380 p1y = ixbase + iangle * thiscm.ascent; 1381 p2y = ixbase - iangle * thiscm.descent; 1382 p1x = bo + thiscm.ascent; 1383 p2x = bo + thiscm.descent; 1384 } 1385 } 1386 } 1387 1388 info[2] = (float)p1x; 1389 info[3] = (float)p1y; 1390 info[4] = (float)p2x; 1391 info[5] = (float)p2y; 1392 1393 return info; 1394 } 1395 1396 1403 public float[] getCaretInfo(TextHitInfo hit) { 1404 1405 return getCaretInfo(hit, getNaturalBounds()); 1406 } 1407 1408 1416 private int hitToCaret(TextHitInfo hit) { 1417 1418 int hitIndex = hit.getCharIndex(); 1419 1420 if (hitIndex < 0) { 1421 return textLine.isDirectionLTR() ? 0 : characterCount; 1422 } else if (hitIndex >= characterCount) { 1423 return textLine.isDirectionLTR() ? characterCount : 0; 1424 } 1425 1426 int visIndex = textLine.logicalToVisual(hitIndex); 1427 1428 if (hit.isLeadingEdge() != textLine.isCharLTR(hitIndex)) { 1429 ++visIndex; 1430 } 1431 1432 return visIndex; 1433 } 1434 1435 1443 private TextHitInfo caretToHit(int caret) { 1444 1445 if (caret == 0 || caret == characterCount) { 1446 1447 if ((caret == characterCount) == textLine.isDirectionLTR()) { 1448 return TextHitInfo.leading(characterCount); 1449 } 1450 else { 1451 return TextHitInfo.trailing(-1); 1452 } 1453 } 1454 else { 1455 1456 int charIndex = textLine.visualToLogical(caret); 1457 boolean leading = textLine.isCharLTR(charIndex); 1458 1459 return leading? TextHitInfo.leading(charIndex) 1460 : TextHitInfo.trailing(charIndex); 1461 } 1462 } 1463 1464 private boolean caretIsValid(int caret) { 1465 1466 if (caret == characterCount || caret == 0) { 1467 return true; 1468 } 1469 1470 int offset = textLine.visualToLogical(caret); 1471 1472 if (!textLine.isCharLTR(offset)) { 1473 offset = textLine.visualToLogical(caret-1); 1474 if (textLine.isCharLTR(offset)) { 1475 return true; 1476 } 1477 } 1478 1479 1482 return textLine.caretAtOffsetIsValid(offset); 1483 } 1484 1485 1494 public TextHitInfo getNextRightHit(TextHitInfo hit) { 1495 ensureCache(); 1496 checkTextHit(hit); 1497 1498 int caret = hitToCaret(hit); 1499 1500 if (caret == characterCount) { 1501 return null; 1502 } 1503 1504 do { 1505 ++caret; 1506 } while (!caretIsValid(caret)); 1507 1508 return caretToHit(caret); 1509 } 1510 1511 1525 public TextHitInfo getNextRightHit(int offset, CaretPolicy policy) { 1526 1527 if (offset < 0 || offset > characterCount) { 1528 throw new IllegalArgumentException ("Offset out of bounds in TextLayout.getNextRightHit()"); 1529 } 1530 1531 if (policy == null) { 1532 throw new IllegalArgumentException ("Null CaretPolicy passed to TextLayout.getNextRightHit()"); 1533 } 1534 1535 TextHitInfo hit1 = TextHitInfo.afterOffset(offset); 1536 TextHitInfo hit2 = hit1.getOtherHit(); 1537 1538 TextHitInfo nextHit = getNextRightHit(policy.getStrongCaret(hit1, hit2, this)); 1539 1540 if (nextHit != null) { 1541 TextHitInfo otherHit = getVisualOtherHit(nextHit); 1542 return policy.getStrongCaret(otherHit, nextHit, this); 1543 } 1544 else { 1545 return null; 1546 } 1547 } 1548 1549 1562 public TextHitInfo getNextRightHit(int offset) { 1563 1564 return getNextRightHit(offset, DEFAULT_CARET_POLICY); 1565 } 1566 1567 1576 public TextHitInfo getNextLeftHit(TextHitInfo hit) { 1577 ensureCache(); 1578 checkTextHit(hit); 1579 1580 int caret = hitToCaret(hit); 1581 1582 if (caret == 0) { 1583 return null; 1584 } 1585 1586 do { 1587 --caret; 1588 } while(!caretIsValid(caret)); 1589 1590 return caretToHit(caret); 1591 } 1592 1593 1607 public TextHitInfo getNextLeftHit(int offset, CaretPolicy policy) { 1608 1609 if (policy == null) { 1610 throw new IllegalArgumentException ("Null CaretPolicy passed to TextLayout.getNextLeftHit()"); 1611 } 1612 1613 if (offset < 0 || offset > characterCount) { 1614 throw new IllegalArgumentException ("Offset out of bounds in TextLayout.getNextLeftHit()"); 1615 } 1616 1617 TextHitInfo hit1 = TextHitInfo.afterOffset(offset); 1618 TextHitInfo hit2 = hit1.getOtherHit(); 1619 1620 TextHitInfo nextHit = getNextLeftHit(policy.getStrongCaret(hit1, hit2, this)); 1621 1622 if (nextHit != null) { 1623 TextHitInfo otherHit = getVisualOtherHit(nextHit); 1624 return policy.getStrongCaret(otherHit, nextHit, this); 1625 } 1626 else { 1627 return null; 1628 } 1629 } 1630 1631 1644 public TextHitInfo getNextLeftHit(int offset) { 1645 1646 return getNextLeftHit(offset, DEFAULT_CARET_POLICY); 1647 } 1648 1649 1655 public TextHitInfo getVisualOtherHit(TextHitInfo hit) { 1656 1657 ensureCache(); 1658 checkTextHit(hit); 1659 1660 int hitCharIndex = hit.getCharIndex(); 1661 1662 int charIndex; 1663 boolean leading; 1664 1665 if (hitCharIndex == -1 || hitCharIndex == characterCount) { 1666 1667 int visIndex; 1668 if (textLine.isDirectionLTR() == (hitCharIndex == -1)) { 1669 visIndex = 0; 1670 } 1671 else { 1672 visIndex = characterCount-1; 1673 } 1674 1675 charIndex = textLine.visualToLogical(visIndex); 1676 1677 if (textLine.isDirectionLTR() == (hitCharIndex == -1)) { 1678 leading = textLine.isCharLTR(charIndex); 1680 } 1681 else { 1682 leading = !textLine.isCharLTR(charIndex); 1684 } 1685 } 1686 else { 1687 1688 int visIndex = textLine.logicalToVisual(hitCharIndex); 1689 1690 boolean movedToRight; 1691 if (textLine.isCharLTR(hitCharIndex) == hit.isLeadingEdge()) { 1692 --visIndex; 1693 movedToRight = false; 1694 } 1695 else { 1696 ++visIndex; 1697 movedToRight = true; 1698 } 1699 1700 if (visIndex > -1 && visIndex < characterCount) { 1701 charIndex = textLine.visualToLogical(visIndex); 1702 leading = movedToRight == textLine.isCharLTR(charIndex); 1703 } 1704 else { 1705 charIndex = 1706 (movedToRight == textLine.isDirectionLTR())? characterCount : -1; 1707 leading = charIndex == characterCount; 1708 } 1709 } 1710 1711 return leading? TextHitInfo.leading(charIndex) : 1712 TextHitInfo.trailing(charIndex); 1713 } 1714 1715 private double[] getCaretPath(TextHitInfo hit, Rectangle2D bounds) { 1716 float[] info = getCaretInfo(hit, bounds); 1717 return new double[] { info[2], info[3], info[4], info[5] }; 1718 } 1719 1720 1729 private double[] getCaretPath(int caret, Rectangle2D bounds, 1730 boolean clipToBounds) { 1731 1732 float[] info = getCaretInfo(caret, bounds, null); 1733 1734 double pos = info[0]; 1735 double slope = info[1]; 1736 1737 double x0, y0, x1, y1; 1738 double x2 = -3141.59, y2 = -2.7; 1740 double left = bounds.getX(); 1741 double right = left + bounds.getWidth(); 1742 double top = bounds.getY(); 1743 double bottom = top + bounds.getHeight(); 1744 1745 boolean threePoints = false; 1746 1747 if (isVerticalLine) { 1748 1749 if (slope >= 0) { 1750 x0 = left; 1751 x1 = right; 1752 } 1753 else { 1754 x1 = left; 1755 x0 = right; 1756 } 1757 1758 y0 = pos + x0 * slope; 1759 y1 = pos + x1 * slope; 1760 1761 1763 if (clipToBounds) { 1764 if (y0 < top) { 1765 if (slope <= 0 || y1 <= top) { 1766 y0 = y1 = top; 1767 } 1768 else { 1769 threePoints = true; 1770 y0 = top; 1771 y2 = top; 1772 x2 = x1 + (top-y1)/slope; 1773 if (y1 > bottom) { 1774 y1 = bottom; 1775 } 1776 } 1777 } 1778 else if (y1 > bottom) { 1779 if (slope >= 0 || y0 >= bottom) { 1780 y0 = y1 = bottom; 1781 } 1782 else { 1783 threePoints = true; 1784 y1 = bottom; 1785 y2 = bottom; 1786 x2 = x0 + (bottom-x1)/slope; 1787 } 1788 } 1789 } 1790 1791 } 1792 else { 1793 1794 if (slope >= 0) { 1795 y0 = bottom; 1796 y1 = top; 1797 } 1798 else { 1799 y1 = bottom; 1800 y0 = top; 1801 } 1802 1803 x0 = pos - y0 * slope; 1804 x1 = pos - y1 * slope; 1805 1806 1808 if (clipToBounds) { 1809 if (x0 < left) { 1810 if (slope <= 0 || x1 <= left) { 1811 x0 = x1 = left; 1812 } 1813 else { 1814 threePoints = true; 1815 x0 = left; 1816 x2 = left; 1817 y2 = y1 - (left-x1)/slope; 1818 if (x1 > right) { 1819 x1 = right; 1820 } 1821 } 1822 } 1823 else if (x1 > right) { 1824 if (slope >= 0 || x0 >= right) { 1825 x0 = x1 = right; 1826 } 1827 else { 1828 threePoints = true; 1829 x1 = right; 1830 x2 = right; 1831 y2 = y0 - (right-x0)/slope; 1832 } 1833 } 1834 } 1835 } 1836 1837 return threePoints? 1838 new double[] { x0, y0, x2, y2, x1, y1 } : 1839 new double[] { x0, y0, x1, y1 }; 1840 } 1841 1842 1843 private static GeneralPath pathToShape(double[] path, boolean close) { 1844 GeneralPath result = new GeneralPath (GeneralPath.WIND_EVEN_ODD, path.length); 1845 result.moveTo((float)path[0], (float)path[1]); 1846 for (int i = 2; i < path.length; i += 2) { 1847 result.lineTo((float)path[i], (float)path[i+1]); 1848 } 1849 if (close) { 1850 result.closePath(); 1851 } 1852 1853 return result; 1854 } 1855 1856 1864 public Shape getCaretShape(TextHitInfo hit, Rectangle2D bounds) { 1865 ensureCache(); 1866 checkTextHit(hit); 1867 1868 if (bounds == null) { 1869 throw new IllegalArgumentException ("Null Rectangle2D passed to TextLayout.getCaret()"); 1870 } 1871 1872 1876 return pathToShape(getCaretPath(hit, bounds), false); 1877 1878 } 1881 1882 1888 public Shape getCaretShape(TextHitInfo hit) { 1889 1890 return getCaretShape(hit, getNaturalBounds()); 1891 } 1892 1893 1898 private final TextHitInfo getStrongHit(TextHitInfo hit1, TextHitInfo hit2) { 1899 1900 1907 byte hit1Level = getCharacterLevel(hit1.getCharIndex()); 1908 byte hit2Level = getCharacterLevel(hit2.getCharIndex()); 1909 1910 if (hit1Level == hit2Level) { 1911 if (hit2.isLeadingEdge() && !hit1.isLeadingEdge()) { 1912 return hit2; 1913 } 1914 else { 1915 return hit1; 1916 } 1917 } 1918 else { 1919 return (hit1Level < hit2Level)? hit1 : hit2; 1920 } 1921 } 1922 1923 1930 public byte getCharacterLevel(int index) { 1931 1932 if (index < -1 || index > characterCount) { 1934 throw new IllegalArgumentException ("Index is out of range in getCharacterLevel."); 1935 } 1936 1937 if (optInfo != null) { 1938 return 0; 1939 } 1940 1941 ensureCache(); 1942 if (index == -1 || index == characterCount) { 1943 return (byte) (textLine.isDirectionLTR()? 0 : 1); 1944 } 1945 1946 return textLine.getCharLevel(index); 1947 } 1948 1949 1958 public Shape [] getCaretShapes(int offset, Rectangle2D bounds, CaretPolicy policy) { 1959 1960 ensureCache(); 1961 1962 if (offset < 0 || offset > characterCount) { 1963 throw new IllegalArgumentException ("Offset out of bounds in TextLayout.getCaretShapes()"); 1964 } 1965 1966 if (bounds == null) { 1967 throw new IllegalArgumentException ("Null Rectangle2D passed to TextLayout.getCaretShapes()"); 1968 } 1969 1970 if (policy == null) { 1971 throw new IllegalArgumentException ("Null CaretPolicy passed to TextLayout.getCaretShapes()"); 1972 } 1973 1974 Shape [] result = new Shape [2]; 1975 1976 TextHitInfo hit = TextHitInfo.afterOffset(offset); 1977 1978 int hitCaret = hitToCaret(hit); 1979 1982 Shape hitShape = pathToShape(getCaretPath(hit, bounds), false); 1983 TextHitInfo otherHit = hit.getOtherHit(); 1984 int otherCaret = hitToCaret(otherHit); 1985 1986 if (hitCaret == otherCaret) { 1987 result[0] = hitShape; 1988 } 1989 else { 1993 Shape otherShape = pathToShape(getCaretPath(otherHit, bounds), false); 1994 1995 TextHitInfo strongHit = policy.getStrongCaret(hit, otherHit, this); 1996 boolean hitIsStrong = strongHit.equals(hit); 1997 1998 if (hitIsStrong) { result[0] = hitShape; 2000 result[1] = otherShape; 2001 } 2002 else { 2003 result[0] = otherShape; 2004 result[1] = hitShape; 2005 } 2006 } 2007 2008 return result; 2009 } 2010 2011 2020 public Shape [] getCaretShapes(int offset, Rectangle2D bounds) { 2021 return getCaretShapes(offset, bounds, DEFAULT_CARET_POLICY); 2023 } 2024 2025 2034 public Shape [] getCaretShapes(int offset) { 2035 return getCaretShapes(offset, getNaturalBounds(), DEFAULT_CARET_POLICY); 2037 } 2038 2039 private GeneralPath boundingShape(double[] path0, double[] path1) { 2043 2044 2051 GeneralPath result = pathToShape(path0, false); 2052 2053 boolean sameDirection; 2054 2055 if (isVerticalLine) { 2056 sameDirection = (path0[1] > path0[path0.length-1]) == 2057 (path1[1] > path1[path1.length-1]); 2058 } 2059 else { 2060 sameDirection = (path0[0] > path0[path0.length-2]) == 2061 (path1[0] > path1[path1.length-2]); 2062 } 2063 2064 int start; 2065 int limit; 2066 int increment; 2067 2068 if (sameDirection) { 2069 start = path1.length-2; 2070 limit = -2; 2071 increment = -2; 2072 } 2073 else { 2074 start = 0; 2075 limit = path1.length; 2076 increment = 2; 2077 } 2078 2079 for (int i = start; i != limit; i += increment) { 2080 result.lineTo((float)path1[i], (float)path1[i+1]); 2081 } 2082 2083 result.closePath(); 2084 2085 return result; 2086 } 2087 2088 private GeneralPath caretBoundingShape(int caret0, 2091 int caret1, 2092 Rectangle2D bounds) { 2093 2094 if (caret0 > caret1) { 2095 int temp = caret0; 2096 caret0 = caret1; 2097 caret1 = temp; 2098 } 2099 2100 return boundingShape(getCaretPath(caret0, bounds, true), 2101 getCaretPath(caret1, bounds, true)); 2102 } 2103 2104 2109 private GeneralPath leftShape(Rectangle2D bounds) { 2110 2111 double[] path0; 2112 if (isVerticalLine) { 2113 path0 = new double[] { bounds.getX(), bounds.getY(), 2114 bounds.getX() + bounds.getWidth(), 2115 bounds.getY() }; 2116 } else { 2117 path0 = new double[] { bounds.getX(), 2118 bounds.getY() + bounds.getHeight(), 2119 bounds.getX(), bounds.getY() }; 2120 } 2121 2122 double[] path1 = getCaretPath(0, bounds, true); 2123 2124 return boundingShape(path0, path1); 2125 } 2126 2127 2131 private GeneralPath rightShape(Rectangle2D bounds) { 2132 double[] path1; 2133 if (isVerticalLine) { 2134 path1 = new double[] { 2135 bounds.getX(), 2136 bounds.getY() + bounds.getHeight(), 2137 bounds.getX() + bounds.getWidth(), 2138 bounds.getY() + bounds.getHeight() 2139 }; 2140 } else { 2141 path1 = new double[] { 2142 bounds.getX() + bounds.getWidth(), 2143 bounds.getY() + bounds.getHeight(), 2144 bounds.getX() + bounds.getWidth(), 2145 bounds.getY() 2146 }; 2147 } 2148 2149 double[] path0 = getCaretPath(characterCount, bounds, true); 2150 2151 return boundingShape(path0, path1); 2152 } 2153 2154 2163 public int[] getLogicalRangesForVisualSelection(TextHitInfo firstEndpoint, 2164 TextHitInfo secondEndpoint) { 2165 ensureCache(); 2166 2167 checkTextHit(firstEndpoint); 2168 checkTextHit(secondEndpoint); 2169 2170 2172 boolean[] included = new boolean[characterCount]; 2173 2174 int startIndex = hitToCaret(firstEndpoint); 2175 int limitIndex = hitToCaret(secondEndpoint); 2176 2177 if (startIndex > limitIndex) { 2178 int t = startIndex; 2179 startIndex = limitIndex; 2180 limitIndex = t; 2181 } 2182 2183 2189 2190 if (startIndex < limitIndex) { 2191 int visIndex = startIndex; 2192 while (visIndex < limitIndex) { 2193 included[textLine.visualToLogical(visIndex)] = true; 2194 ++visIndex; 2195 } 2196 } 2197 2198 2202 int count = 0; 2203 boolean inrun = false; 2204 for (int i = 0; i < characterCount; i++) { 2205 if (included[i] != inrun) { 2206 inrun = !inrun; 2207 if (inrun) { 2208 count++; 2209 } 2210 } 2211 } 2212 2213 int[] ranges = new int[count * 2]; 2214 count = 0; 2215 inrun = false; 2216 for (int i = 0; i < characterCount; i++) { 2217 if (included[i] != inrun) { 2218 ranges[count++] = i; 2219 inrun = !inrun; 2220 } 2221 } 2222 if (inrun) { 2223 ranges[count++] = characterCount; 2224 } 2225 2226 return ranges; 2227 } 2228 2229 2263 public Shape getVisualHighlightShape(TextHitInfo firstEndpoint, 2264 TextHitInfo secondEndpoint, 2265 Rectangle2D bounds) 2266 { 2267 ensureCache(); 2268 2269 checkTextHit(firstEndpoint); 2270 checkTextHit(secondEndpoint); 2271 2272 if(bounds == null) { 2273 throw new IllegalArgumentException ("Null Rectangle2D passed to TextLayout.getVisualHighlightShape()"); 2274 } 2275 2276 GeneralPath result = new GeneralPath (GeneralPath.WIND_EVEN_ODD); 2277 2278 int firstCaret = hitToCaret(firstEndpoint); 2279 int secondCaret = hitToCaret(secondEndpoint); 2280 2281 result.append(caretBoundingShape(firstCaret, secondCaret, bounds), 2282 false); 2283 2284 if (firstCaret == 0 || secondCaret == 0) { 2285 result.append(leftShape(bounds), false); 2286 } 2287 2288 if (firstCaret == characterCount || secondCaret == characterCount) { 2289 result.append(rightShape(bounds), false); 2290 } 2291 2292 return result; 2294 } 2295 2296 2305 public Shape getVisualHighlightShape(TextHitInfo firstEndpoint, 2306 TextHitInfo secondEndpoint) { 2307 return getVisualHighlightShape(firstEndpoint, secondEndpoint, getNaturalBounds()); 2308 } 2309 2310 2347 public Shape getLogicalHighlightShape(int firstEndpoint, 2348 int secondEndpoint, 2349 Rectangle2D bounds) { 2350 if (bounds == null) { 2351 throw new IllegalArgumentException ("Null Rectangle2D passed to TextLayout.getLogicalHighlightShape()"); 2352 } 2353 2354 ensureCache(); 2355 2356 if (firstEndpoint > secondEndpoint) { 2357 int t = firstEndpoint; 2358 firstEndpoint = secondEndpoint; 2359 secondEndpoint = t; 2360 } 2361 2362 if(firstEndpoint < 0 || secondEndpoint > characterCount) { 2363 throw new IllegalArgumentException ("Range is invalid in TextLayout.getLogicalHighlightShape()"); 2364 } 2365 2366 GeneralPath result = new GeneralPath (GeneralPath.WIND_EVEN_ODD); 2367 2368 int[] carets = new int[10]; int count = 0; 2370 2371 if (firstEndpoint < secondEndpoint) { 2372 int logIndex = firstEndpoint; 2373 do { 2374 carets[count++] = hitToCaret(TextHitInfo.leading(logIndex)); 2375 boolean ltr = textLine.isCharLTR(logIndex); 2376 2377 do { 2378 logIndex++; 2379 } while (logIndex < secondEndpoint && textLine.isCharLTR(logIndex) == ltr); 2380 2381 int hitCh = logIndex; 2382 carets[count++] = hitToCaret(TextHitInfo.trailing(hitCh - 1)); 2383 2384 if (count == carets.length) { 2385 int[] temp = new int[carets.length + 10]; 2386 System.arraycopy(carets, 0, temp, 0, count); 2387 carets = temp; 2388 } 2389 } while (logIndex < secondEndpoint); 2390 } 2391 else { 2392 count = 2; 2393 carets[0] = carets[1] = hitToCaret(TextHitInfo.leading(firstEndpoint)); 2394 } 2395 2396 2398 for (int i = 0; i < count; i += 2) { 2399 result.append(caretBoundingShape(carets[i], carets[i+1], bounds), 2400 false); 2401 } 2402 2403 if (firstEndpoint != secondEndpoint) { 2404 if ((textLine.isDirectionLTR() && firstEndpoint == 0) || (!textLine.isDirectionLTR() && 2405 secondEndpoint == characterCount)) { 2406 result.append(leftShape(bounds), false); 2407 } 2408 2409 if ((textLine.isDirectionLTR() && secondEndpoint == characterCount) || 2410 (!textLine.isDirectionLTR() && firstEndpoint == 0)) { 2411 result.append(rightShape(bounds), false); 2412 } 2413 } 2414 2415 return result; 2416 } 2417 2418 2431 public Shape getLogicalHighlightShape(int firstEndpoint, int secondEndpoint) { 2432 2433 return getLogicalHighlightShape(firstEndpoint, secondEndpoint, getNaturalBounds()); 2434 } 2435 2436 2446 public Shape getBlackBoxBounds(int firstEndpoint, int secondEndpoint) { 2447 ensureCache(); 2448 2449 if (firstEndpoint > secondEndpoint) { 2450 int t = firstEndpoint; 2451 firstEndpoint = secondEndpoint; 2452 secondEndpoint = t; 2453 } 2454 2455 if (firstEndpoint < 0 || secondEndpoint > characterCount) { 2456 throw new IllegalArgumentException ("Invalid range passed to TextLayout.getBlackBoxBounds()"); 2457 } 2458 2459 2463 2464 GeneralPath result = new GeneralPath (GeneralPath.WIND_NON_ZERO); 2465 2466 if (firstEndpoint < characterCount) { 2467 for (int logIndex = firstEndpoint; 2468 logIndex < secondEndpoint; 2469 logIndex++) { 2470 2471 Rectangle2D r = textLine.getCharBounds(logIndex); 2472 if (!r.isEmpty()) { 2473 result.append(r, false); 2474 } 2475 } 2476 } 2477 2478 if (dx != 0 || dy != 0) { 2479 AffineTransform translate = new AffineTransform (); 2480 translate.setToTranslation(dx, dy); 2481 result = (GeneralPath ) result.createTransformedShape(translate); 2482 } 2483 2484 return result; 2486 } 2487 2488 2495 private float caretToPointDistance(float[] caretInfo, float x, float y) { 2496 2498 float lineDistance = isVerticalLine? y : x; 2499 float distanceOffBaseline = isVerticalLine? -x : y; 2500 2501 return lineDistance - caretInfo[0] + 2502 (distanceOffBaseline*caretInfo[1]); 2503 } 2504 2505 2521 public TextHitInfo hitTestChar(float x, float y, Rectangle2D bounds) { 2522 2524 if (isVertical()) { 2525 if (y < bounds.getMinY()) { 2526 return TextHitInfo.leading(0); 2527 } else if (y >= bounds.getMaxY()) { 2528 return TextHitInfo.trailing(characterCount-1); 2529 } 2530 } else { 2531 if (x < bounds.getMinX()) { 2532 return isLeftToRight() ? TextHitInfo.leading(0) : TextHitInfo.trailing(characterCount-1); 2533 } else if (x >= bounds.getMaxX()) { 2534 return isLeftToRight() ? TextHitInfo.trailing(characterCount-1) : TextHitInfo.leading(0); 2535 } 2536 } 2537 2538 2548 double distance = Double.MAX_VALUE; 2549 int index = 0; 2550 int trail = -1; 2551 CoreMetrics lcm = null; 2552 float icx = 0, icy = 0, ia = 0, cy = 0, dya = 0, ydsq = 0; 2553 2554 for (int i = 0; i < characterCount; ++i) { 2555 if (!textLine.caretAtOffsetIsValid(i)) { 2556 continue; 2557 } 2558 if (trail == -1) { 2559 trail = i; 2560 } 2561 CoreMetrics cm = textLine.getCoreMetricsAt(i); 2562 if (cm != lcm) { 2563 lcm = cm; 2564 if (cm.baselineIndex == GraphicAttribute.TOP_ALIGNMENT) { 2566 cy = -(textLine.getMetrics().ascent - cm.ascent) + cm.ssOffset; 2567 } else if (cm.baselineIndex == GraphicAttribute.BOTTOM_ALIGNMENT) { 2568 cy = textLine.getMetrics().descent - cm.descent + cm.ssOffset; 2569 } else { 2570 cy = cm.effectiveBaselineOffset(baselineOffsets) + cm.ssOffset; 2571 } 2572 float dy = (cm.descent - cm.ascent) / 2 - cy; 2573 dya = dy * cm.italicAngle; 2574 cy += dy; 2575 ydsq = (cy - y)*(cy - y); 2576 } 2577 float cx = textLine.getCharXPosition(i); 2578 float ca = textLine.getCharAdvance(i); 2579 float dx = ca / 2; 2580 cx += dx - dya; 2581 2582 double nd = Math.sqrt(4*(cx - x)*(cx - x) + ydsq); 2584 if (nd < distance) { 2585 distance = nd; 2586 index = i; 2587 trail = -1; 2588 icx = cx; icy = cy; ia = cm.italicAngle; 2589 } 2590 } 2591 boolean left = x < icx - (y - icy) * ia; 2592 boolean leading = textLine.isCharLTR(index) == left; 2593 if (trail == -1) { 2594 trail = characterCount; 2595 } 2596 TextHitInfo result = leading ? TextHitInfo.leading(index) : 2597 TextHitInfo.trailing(trail-1); 2598 return result; 2599 } 2600 2601 2612 public TextHitInfo hitTestChar(float x, float y) { 2613 2614 return hitTestChar(x, y, getNaturalBounds()); 2615 } 2616 2617 2621 public int hashCode() { 2622 if (hashCodeCache == 0) { 2623 ensureCache(); 2624 hashCodeCache = textLine.hashCode(); 2625 } 2626 return hashCodeCache; 2627 } 2628 2629 2638 public boolean equals(Object obj) { 2639 return (obj instanceof TextLayout ) && equals((TextLayout )obj); 2640 } 2641 2642 2651 public boolean equals(TextLayout rhs) { 2652 2653 if (rhs == null) { 2654 return false; 2655 } 2656 if (rhs == this) { 2657 return true; 2658 } 2659 2660 ensureCache(); 2661 return textLine.equals(rhs.textLine); 2662 } 2663 2664 2669 public String toString() { 2670 ensureCache(); 2671 return textLine.toString(); 2672 } 2673 2674 2686 public void draw(Graphics2D g2, float x, float y) { 2687 2688 if (g2 == null) { 2689 throw new IllegalArgumentException ("Null Graphics2D passed to TextLayout.draw()"); 2690 } 2691 2692 if (optInfo != null) { 2693 if (optInfo.draw(g2, x, y)) { return; 2695 } 2696 initTextLine(); 2698 } 2699 textLine.draw(g2, x - dx, y - dy); 2700 } 2701 2702 2705 TextLine getTextLineForTesting() { 2706 2707 return textLine; 2708 } 2709 2710 2716 private static int sameBaselineUpTo(Font font, char[] text, 2717 int start, int limit) { 2718 return limit; 2720 2727 } 2728 2729 static byte getBaselineFromGraphic(GraphicAttribute graphic) { 2730 2731 byte alignment = (byte) graphic.getAlignment(); 2732 2733 if (alignment == GraphicAttribute.BOTTOM_ALIGNMENT || 2734 alignment == GraphicAttribute.TOP_ALIGNMENT) { 2735 2736 return (byte)GraphicAttribute.ROMAN_BASELINE; 2737 } 2738 else { 2739 return alignment; 2740 } 2741 } 2742 2743 2751 public Shape getOutline(AffineTransform tx) { 2752 ensureCache(); 2753 return textLine.getOutline(tx); 2754 } 2755} 2756 | Popular Tags |