1 /* 2 * Copyright (c) 1998, 2011, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 /* 27 * (C) Copyright IBM Corp. 1998-2003, All Rights Reserved 28 * 29 */ 30 31 package java.awt.font; 32 33 import java.awt.Color; 34 import java.awt.Font; 35 import java.awt.Graphics2D; 36 import java.awt.Rectangle; 37 import java.awt.Shape; 38 import java.awt.geom.AffineTransform; 39 import java.awt.geom.GeneralPath; 40 import java.awt.geom.Point2D; 41 import java.awt.geom.Rectangle2D; 42 import java.awt.im.InputMethodHighlight; 43 import java.awt.image.BufferedImage; 44 import java.text.Annotation; 45 import java.text.AttributedCharacterIterator; 46 import java.text.AttributedCharacterIterator.Attribute; 47 import java.text.Bidi; 48 import java.text.CharacterIterator; 49 import java.util.Hashtable; 50 import java.util.Map; 51 import sun.font.AttributeValues; 52 import sun.font.BidiUtils; 53 import sun.font.CodePointIterator; 54 import sun.font.CoreMetrics; 55 import sun.font.Decoration; 56 import sun.font.FontLineMetrics; 57 import sun.font.FontResolver; 58 import sun.font.GraphicComponent; 59 import sun.font.LayoutPathImpl; 60 import sun.font.LayoutPathImpl.EmptyPath; 61 import sun.font.LayoutPathImpl.SegmentPathBuilder; 62 import sun.font.TextLabelFactory; 63 import sun.font.TextLineComponent; 64 65 import java.awt.geom.Line2D; 66 67 final class TextLine { 68 69 static final class TextLineMetrics { 70 public final float ascent; 71 public final float descent; 72 public final float leading; 73 public final float advance; 74 75 public TextLineMetrics(float ascent, 76 float descent, 77 float leading, 78 float advance) { 79 this.ascent = ascent; 80 this.descent = descent; 81 this.leading = leading; 82 this.advance = advance; 83 } 84 } 85 86 private TextLineComponent[] fComponents; 87 private float[] fBaselineOffsets; 88 private int[] fComponentVisualOrder; // if null, ltr 89 private float[] locs; // x,y pairs for components in visual order 90 private char[] fChars; 91 private int fCharsStart; 92 private int fCharsLimit; 93 private int[] fCharVisualOrder; // if null, ltr 94 private int[] fCharLogicalOrder; // if null, ltr 95 private byte[] fCharLevels; // if null, 0 96 private boolean fIsDirectionLTR; 97 private LayoutPathImpl lp; 98 private boolean isSimple; 99 private Rectangle pixelBounds; 100 private FontRenderContext frc; 101 102 private TextLineMetrics fMetrics = null; // built on demand in getMetrics 103 104 public TextLine(FontRenderContext frc, 105 TextLineComponent[] components, 106 float[] baselineOffsets, 107 char[] chars, 108 int charsStart, 109 int charsLimit, 110 int[] charLogicalOrder, 111 byte[] charLevels, 112 boolean isDirectionLTR) { 113 114 int[] componentVisualOrder = computeComponentOrder(components, 115 charLogicalOrder); 116 117 this.frc = frc; 118 fComponents = components; 119 fBaselineOffsets = baselineOffsets; 120 fComponentVisualOrder = componentVisualOrder; 121 fChars = chars; 122 fCharsStart = charsStart; 123 fCharsLimit = charsLimit; 124 fCharLogicalOrder = charLogicalOrder; 125 fCharLevels = charLevels; 126 fIsDirectionLTR = isDirectionLTR; 127 checkCtorArgs(); 128 129 init(); 130 } 131 132 private void checkCtorArgs() { 133 134 int checkCharCount = 0; 135 for (int i=0; i < fComponents.length; i++) { 136 checkCharCount += fComponents[i].getNumCharacters(); 137 } 138 139 if (checkCharCount != this.characterCount()) { 140 throw new IllegalArgumentException("Invalid TextLine! " + 141 "char count is different from " + 142 "sum of char counts of components."); 143 } 144 } 145 146 private void init() { 147 148 // first, we need to check for graphic components on the TOP or BOTTOM baselines. So 149 // we perform the work that used to be in getMetrics here. 150 151 float ascent = 0; 152 float descent = 0; 153 float leading = 0; 154 float advance = 0; 155 156 // ascent + descent must not be less than this value 157 float maxGraphicHeight = 0; 158 float maxGraphicHeightWithLeading = 0; 159 160 // walk through EGA's 161 TextLineComponent tlc; 162 boolean fitTopAndBottomGraphics = false; 163 164 isSimple = true; 165 166 for (int i = 0; i < fComponents.length; i++) { 167 tlc = fComponents[i]; 168 169 isSimple &= tlc.isSimple(); 170 171 CoreMetrics cm = tlc.getCoreMetrics(); 172 173 byte baseline = (byte)cm.baselineIndex; 174 175 if (baseline >= 0) { 176 float baselineOffset = fBaselineOffsets[baseline]; 177 178 ascent = Math.max(ascent, -baselineOffset + cm.ascent); 179 180 float gd = baselineOffset + cm.descent; 181 descent = Math.max(descent, gd); 182 183 leading = Math.max(leading, gd + cm.leading); 184 } 185 else { 186 fitTopAndBottomGraphics = true; 187 float graphicHeight = cm.ascent + cm.descent; 188 float graphicHeightWithLeading = graphicHeight + cm.leading; 189 maxGraphicHeight = Math.max(maxGraphicHeight, graphicHeight); 190 maxGraphicHeightWithLeading = Math.max(maxGraphicHeightWithLeading, 191 graphicHeightWithLeading); 192 } 193 } 194 195 if (fitTopAndBottomGraphics) { 196 if (maxGraphicHeight > ascent + descent) { 197 descent = maxGraphicHeight - ascent; 198 } 199 if (maxGraphicHeightWithLeading > ascent + leading) { 200 leading = maxGraphicHeightWithLeading - ascent; 201 } 202 } 203 204 leading -= descent; 205 206 // we now know enough to compute the locs, but we need the final loc 207 // for the advance before we can create the metrics object 208 209 if (fitTopAndBottomGraphics) { 210 // we have top or bottom baselines, so expand the baselines array 211 // full offsets are needed by CoreMetrics.effectiveBaselineOffset 212 fBaselineOffsets = new float[] { 213 fBaselineOffsets[0], 214 fBaselineOffsets[1], 215 fBaselineOffsets[2], 216 descent, 217 -ascent 218 }; 219 } 220 221 float x = 0; 222 float y = 0; 223 CoreMetrics pcm = null; 224 225 boolean needPath = false; 226 locs = new float[fComponents.length * 2 + 2]; 227 228 for (int i = 0, n = 0; i < fComponents.length; ++i, n += 2) { 229 tlc = fComponents[getComponentLogicalIndex(i)]; 230 CoreMetrics cm = tlc.getCoreMetrics(); 231 232 if ((pcm != null) && 233 (pcm.italicAngle != 0 || cm.italicAngle != 0) && // adjust because of italics 234 (pcm.italicAngle != cm.italicAngle || 235 pcm.baselineIndex != cm.baselineIndex || 236 pcm.ssOffset != cm.ssOffset)) { 237 238 // 1) compute the area of overlap - min effective ascent and min effective descent 239 // 2) compute the x positions along italic angle of ascent and descent for left and right 240 // 3) compute maximum left - right, adjust right position by this value 241 // this is a crude form of kerning between textcomponents 242 243 // note glyphvectors preposition glyphs based on offset, 244 // so tl doesn't need to adjust glyphvector position 245 // 1) 246 float pb = pcm.effectiveBaselineOffset(fBaselineOffsets); 247 float pa = pb - pcm.ascent; 248 float pd = pb + pcm.descent; 249 // pb += pcm.ssOffset; 250 251 float cb = cm.effectiveBaselineOffset(fBaselineOffsets); 252 float ca = cb - cm.ascent; 253 float cd = cb + cm.descent; 254 // cb += cm.ssOffset; 255 256 float a = Math.max(pa, ca); 257 float d = Math.min(pd, cd); 258 259 // 2) 260 float pax = pcm.italicAngle * (pb - a); 261 float pdx = pcm.italicAngle * (pb - d); 262 263 float cax = cm.italicAngle * (cb - a); 264 float cdx = cm.italicAngle * (cb - d); 265 266 // 3) 267 float dax = pax - cax; 268 float ddx = pdx - cdx; 269 float dx = Math.max(dax, ddx); 270 271 x += dx; 272 y = cb; 273 } else { 274 // no italic adjustment for x, but still need to compute y 275 y = cm.effectiveBaselineOffset(fBaselineOffsets); // + cm.ssOffset; 276 } 277 278 locs[n] = x; 279 locs[n+1] = y; 280 281 x += tlc.getAdvance(); 282 pcm = cm; 283 284 needPath |= tlc.getBaselineTransform() != null; 285 } 286 287 // do we want italic padding at the right of the line? 288 if (pcm.italicAngle != 0) { 289 float pb = pcm.effectiveBaselineOffset(fBaselineOffsets); 290 float pa = pb - pcm.ascent; 291 float pd = pb + pcm.descent; 292 pb += pcm.ssOffset; 293 294 float d; 295 if (pcm.italicAngle > 0) { 296 d = pb + pcm.ascent; 297 } else { 298 d = pb - pcm.descent; 299 } 300 d *= pcm.italicAngle; 301 302 x += d; 303 } 304 locs[locs.length - 2] = x; 305 // locs[locs.length - 1] = 0; // final offset is always back on baseline 306 307 // ok, build fMetrics since we have the final advance 308 advance = x; 309 fMetrics = new TextLineMetrics(ascent, descent, leading, advance); 310 311 // build path if we need it 312 if (needPath) { 313 isSimple = false; 314 315 Point2D.Double pt = new Point2D.Double(); 316 double tx = 0, ty = 0; 317 SegmentPathBuilder builder = new SegmentPathBuilder(); 318 builder.moveTo(locs[0], 0); 319 for (int i = 0, n = 0; i < fComponents.length; ++i, n += 2) { 320 tlc = fComponents[getComponentLogicalIndex(i)]; 321 AffineTransform at = tlc.getBaselineTransform(); 322 if (at != null && 323 ((at.getType() & AffineTransform.TYPE_TRANSLATION) != 0)) { 324 double dx = at.getTranslateX(); 325 double dy = at.getTranslateY(); 326 builder.moveTo(tx += dx, ty += dy); 327 } 328 pt.x = locs[n+2] - locs[n]; 329 pt.y = 0; 330 if (at != null) { 331 at.deltaTransform(pt, pt); 332 } 333 builder.lineTo(tx += pt.x, ty += pt.y); 334 } 335 lp = builder.complete(); 336 337 if (lp == null) { // empty path 338 tlc = fComponents[getComponentLogicalIndex(0)]; 339 AffineTransform at = tlc.getBaselineTransform(); 340 if (at != null) { 341 lp = new EmptyPath(at); 342 } 343 } 344 } 345 } 346 347 public Rectangle getPixelBounds(FontRenderContext frc, float x, float y) { 348 Rectangle result = null; 349 350 // if we have a matching frc, set it to null so we don't have to test it 351 // for each component 352 if (frc != null && frc.equals(this.frc)) { 353 frc = null; 354 } 355 356 // only cache integral locations with the default frc, this is a bit strict 357 int ix = (int)Math.floor(x); 358 int iy = (int)Math.floor(y); 359 float rx = x - ix; 360 float ry = y - iy; 361 boolean canCache = frc == null && rx == 0 && ry == 0; 362 363 if (canCache && pixelBounds != null) { 364 result = new Rectangle(pixelBounds); 365 result.x += ix; 366 result.y += iy; 367 return result; 368 } 369 370 // couldn't use cache, or didn't have it, so compute 371 372 if (isSimple) { // all glyphvectors with no decorations, no layout path 373 for (int i = 0, n = 0; i < fComponents.length; i++, n += 2) { 374 TextLineComponent tlc = fComponents[getComponentLogicalIndex(i)]; 375 Rectangle pb = tlc.getPixelBounds(frc, locs[n] + rx, locs[n+1] + ry); 376 if (!pb.isEmpty()) { 377 if (result == null) { 378 result = pb; 379 } else { 380 result.add(pb); 381 } 382 } 383 } 384 if (result == null) { 385 result = new Rectangle(0, 0, 0, 0); 386 } 387 } else { // draw and test 388 final int MARGIN = 3; 389 Rectangle2D r2d = getVisualBounds(); 390 if (lp != null) { 391 r2d = lp.mapShape(r2d).getBounds(); 392 } 393 Rectangle bounds = r2d.getBounds(); 394 BufferedImage im = new BufferedImage(bounds.width + MARGIN * 2, 395 bounds.height + MARGIN * 2, 396 BufferedImage.TYPE_INT_ARGB); 397 398 Graphics2D g2d = im.createGraphics(); 399 g2d.setColor(Color.WHITE); 400 g2d.fillRect(0, 0, im.getWidth(), im.getHeight()); 401 402 g2d.setColor(Color.BLACK); 403 draw(g2d, rx + MARGIN - bounds.x, ry + MARGIN - bounds.y); 404 405 result = computePixelBounds(im); 406 result.x -= MARGIN - bounds.x; 407 result.y -= MARGIN - bounds.y; 408 } 409 410 if (canCache) { 411 pixelBounds = new Rectangle(result); 412 } 413 414 result.x += ix; 415 result.y += iy; 416 return result; 417 } 418 419 static Rectangle computePixelBounds(BufferedImage im) { 420 int w = im.getWidth(); 421 int h = im.getHeight(); 422 423 int l = -1, t = -1, r = w, b = h; 424 425 { 426 // get top 427 int[] buf = new int[w]; 428 loop: while (++t < h) { 429 im.getRGB(0, t, buf.length, 1, buf, 0, w); // w ignored 430 for (int i = 0; i < buf.length; i++) { 431 if (buf[i] != -1) { 432 break loop; 433 } 434 } 435 } 436 } 437 438 // get bottom 439 { 440 int[] buf = new int[w]; 441 loop: while (--b > t) { 442 im.getRGB(0, b, buf.length, 1, buf, 0, w); // w ignored 443 for (int i = 0; i < buf.length; ++i) { 444 if (buf[i] != -1) { 445 break loop; 446 } 447 } 448 } 449 ++b; 450 } 451 452 // get left 453 { 454 loop: while (++l < r) { 455 for (int i = t; i < b; ++i) { 456 int v = im.getRGB(l, i); 457 if (v != -1) { 458 break loop; 459 } 460 } 461 } 462 } 463 464 // get right 465 { 466 loop: while (--r > l) { 467 for (int i = t; i < b; ++i) { 468 int v = im.getRGB(r, i); 469 if (v != -1) { 470 break loop; 471 } 472 } 473 } 474 ++r; 475 } 476 477 return new Rectangle(l, t, r-l, b-t); 478 } 479 480 private abstract static class Function { 481 482 abstract float computeFunction(TextLine line, 483 int componentIndex, 484 int indexInArray); 485 } 486 487 private static Function fgPosAdvF = new Function() { 488 float computeFunction(TextLine line, 489 int componentIndex, 490 int indexInArray) { 491 492 TextLineComponent tlc = line.fComponents[componentIndex]; 493 int vi = line.getComponentVisualIndex(componentIndex); 494 return line.locs[vi * 2] + tlc.getCharX(indexInArray) + tlc.getCharAdvance(indexInArray); 495 } 496 }; 497 498 private static Function fgAdvanceF = new Function() { 499 500 float computeFunction(TextLine line, 501 int componentIndex, 502 int indexInArray) { 503 504 TextLineComponent tlc = line.fComponents[componentIndex]; 505 return tlc.getCharAdvance(indexInArray); 506 } 507 }; 508 509 private static Function fgXPositionF = new Function() { 510 511 float computeFunction(TextLine line, 512 int componentIndex, 513 int indexInArray) { 514 515 int vi = line.getComponentVisualIndex(componentIndex); 516 TextLineComponent tlc = line.fComponents[componentIndex]; 517 return line.locs[vi * 2] + tlc.getCharX(indexInArray); 518 } 519 }; 520 521 private static Function fgYPositionF = new Function() { 522 523 float computeFunction(TextLine line, 524 int componentIndex, 525 int indexInArray) { 526 527 TextLineComponent tlc = line.fComponents[componentIndex]; 528 float charPos = tlc.getCharY(indexInArray); 529 530 // charPos is relative to the component - adjust for 531 // baseline 532 533 return charPos + line.getComponentShift(componentIndex); 534 } 535 }; 536 537 public int characterCount() { 538 539 return fCharsLimit - fCharsStart; 540 } 541 542 public boolean isDirectionLTR() { 543 544 return fIsDirectionLTR; 545 } 546 547 public TextLineMetrics getMetrics() { 548 return fMetrics; 549 } 550 551 public int visualToLogical(int visualIndex) { 552 553 if (fCharLogicalOrder == null) { 554 return visualIndex; 555 } 556 557 if (fCharVisualOrder == null) { 558 fCharVisualOrder = BidiUtils.createInverseMap(fCharLogicalOrder); 559 } 560 561 return fCharVisualOrder[visualIndex]; 562 } 563 564 public int logicalToVisual(int logicalIndex) { 565 566 return (fCharLogicalOrder == null)? 567 logicalIndex : fCharLogicalOrder[logicalIndex]; 568 } 569 570 public byte getCharLevel(int logicalIndex) { 571 572 return fCharLevels==null? 0 : fCharLevels[logicalIndex]; 573 } 574 575 public boolean isCharLTR(int logicalIndex) { 576 577 return (getCharLevel(logicalIndex) & 0x1) == 0; 578 } 579 580 public int getCharType(int logicalIndex) { 581 582 return Character.getType(fChars[logicalIndex + fCharsStart]); 583 } 584 585 public boolean isCharSpace(int logicalIndex) { 586 587 return Character.isSpaceChar(fChars[logicalIndex + fCharsStart]); 588 } 589 590 public boolean isCharWhitespace(int logicalIndex) { 591 592 return Character.isWhitespace(fChars[logicalIndex + fCharsStart]); 593 } 594 595 public float getCharAngle(int logicalIndex) { 596 597 return getCoreMetricsAt(logicalIndex).italicAngle; 598 } 599 600 public CoreMetrics getCoreMetricsAt(int logicalIndex) { 601 602 if (logicalIndex < 0) { 603 throw new IllegalArgumentException("Negative logicalIndex."); 604 } 605 606 if (logicalIndex > fCharsLimit - fCharsStart) { 607 throw new IllegalArgumentException("logicalIndex too large."); 608 } 609 610 int currentTlc = 0; 611 int tlcStart = 0; 612 int tlcLimit = 0; 613 614 do { 615 tlcLimit += fComponents[currentTlc].getNumCharacters(); 616 if (tlcLimit > logicalIndex) { 617 break; 618 } 619 ++currentTlc; 620 tlcStart = tlcLimit; 621 } while(currentTlc < fComponents.length); 622 623 return fComponents[currentTlc].getCoreMetrics(); 624 } 625 626 public float getCharAscent(int logicalIndex) { 627 628 return getCoreMetricsAt(logicalIndex).ascent; 629 } 630 631 public float getCharDescent(int logicalIndex) { 632 633 return getCoreMetricsAt(logicalIndex).descent; 634 } 635 636 public float getCharShift(int logicalIndex) { 637 638 return getCoreMetricsAt(logicalIndex).ssOffset; 639 } 640 641 private float applyFunctionAtIndex(int logicalIndex, Function f) { 642 643 if (logicalIndex < 0) { 644 throw new IllegalArgumentException("Negative logicalIndex."); 645 } 646 647 int tlcStart = 0; 648 649 for(int i=0; i < fComponents.length; i++) { 650 651 int tlcLimit = tlcStart + fComponents[i].getNumCharacters(); 652 if (tlcLimit > logicalIndex) { 653 return f.computeFunction(this, i, logicalIndex - tlcStart); 654 } 655 else { 656 tlcStart = tlcLimit; 657 } 658 } 659 660 throw new IllegalArgumentException("logicalIndex too large."); 661 } 662 663 public float getCharAdvance(int logicalIndex) { 664 665 return applyFunctionAtIndex(logicalIndex, fgAdvanceF); 666 } 667 668 public float getCharXPosition(int logicalIndex) { 669 670 return applyFunctionAtIndex(logicalIndex, fgXPositionF); 671 } 672 673 public float getCharYPosition(int logicalIndex) { 674 675 return applyFunctionAtIndex(logicalIndex, fgYPositionF); 676 } 677 678 public float getCharLinePosition(int logicalIndex) { 679 680 return getCharXPosition(logicalIndex); 681 } 682 683 public float getCharLinePosition(int logicalIndex, boolean leading) { 684 Function f = isCharLTR(logicalIndex) == leading ? fgXPositionF : fgPosAdvF; 685 return applyFunctionAtIndex(logicalIndex, f); 686 } 687 688 public boolean caretAtOffsetIsValid(int offset) { 689 690 if (offset < 0) { 691 throw new IllegalArgumentException("Negative offset."); 692 } 693 694 int tlcStart = 0; 695 696 for(int i=0; i < fComponents.length; i++) { 697 698 int tlcLimit = tlcStart + fComponents[i].getNumCharacters(); 699 if (tlcLimit > offset) { 700 return fComponents[i].caretAtOffsetIsValid(offset-tlcStart); 701 } 702 else { 703 tlcStart = tlcLimit; 704 } 705 } 706 707 throw new IllegalArgumentException("logicalIndex too large."); 708 } 709 710 /** 711 * map a component visual index to the logical index. 712 */ 713 private int getComponentLogicalIndex(int vi) { 714 if (fComponentVisualOrder == null) { 715 return vi; 716 } 717 return fComponentVisualOrder[vi]; 718 } 719 720 /** 721 * map a component logical index to the visual index. 722 */ 723 private int getComponentVisualIndex(int li) { 724 if (fComponentVisualOrder == null) { 725 return li; 726 } 727 for (int i = 0; i < fComponentVisualOrder.length; ++i) { 728 if (fComponentVisualOrder[i] == li) { 729 return i; 730 } 731 } 732 throw new IndexOutOfBoundsException("bad component index: " + li); 733 } 734 735 public Rectangle2D getCharBounds(int logicalIndex) { 736 737 if (logicalIndex < 0) { 738 throw new IllegalArgumentException("Negative logicalIndex."); 739 } 740 741 int tlcStart = 0; 742 743 for (int i=0; i < fComponents.length; i++) { 744 745 int tlcLimit = tlcStart + fComponents[i].getNumCharacters(); 746 if (tlcLimit > logicalIndex) { 747 748 TextLineComponent tlc = fComponents[i]; 749 int indexInTlc = logicalIndex - tlcStart; 750 Rectangle2D chBounds = tlc.getCharVisualBounds(indexInTlc); 751 752 int vi = getComponentVisualIndex(i); 753 chBounds.setRect(chBounds.getX() + locs[vi * 2], 754 chBounds.getY() + locs[vi * 2 + 1], 755 chBounds.getWidth(), 756 chBounds.getHeight()); 757 return chBounds; 758 } 759 else { 760 tlcStart = tlcLimit; 761 } 762 } 763 764 throw new IllegalArgumentException("logicalIndex too large."); 765 } 766 767 private float getComponentShift(int index) { 768 CoreMetrics cm = fComponents[index].getCoreMetrics(); 769 return cm.effectiveBaselineOffset(fBaselineOffsets); 770 } 771 772 public void draw(Graphics2D g2, float x, float y) { 773 if (lp == null) { 774 for (int i = 0, n = 0; i < fComponents.length; i++, n += 2) { 775 TextLineComponent tlc = fComponents[getComponentLogicalIndex(i)]; 776 tlc.draw(g2, locs[n] + x, locs[n+1] + y); 777 } 778 } else { 779 AffineTransform oldTx = g2.getTransform(); 780 Point2D.Float pt = new Point2D.Float(); 781 for (int i = 0, n = 0; i < fComponents.length; i++, n += 2) { 782 TextLineComponent tlc = fComponents[getComponentLogicalIndex(i)]; 783 lp.pathToPoint(locs[n], locs[n+1], false, pt); 784 pt.x += x; 785 pt.y += y; 786 AffineTransform at = tlc.getBaselineTransform(); 787 788 if (at != null) { 789 g2.translate(pt.x - at.getTranslateX(), pt.y - at.getTranslateY()); 790 g2.transform(at); 791 tlc.draw(g2, 0, 0); 792 g2.setTransform(oldTx); 793 } else { 794 tlc.draw(g2, pt.x, pt.y); 795 } 796 } 797 } 798 } 799 800 /** 801 * Return the union of the visual bounds of all the components. 802 * This incorporates the path. It does not include logical 803 * bounds (used by carets). 804 */ 805 public Rectangle2D getVisualBounds() { 806 Rectangle2D result = null; 807 808 for (int i = 0, n = 0; i < fComponents.length; i++, n += 2) { 809 TextLineComponent tlc = fComponents[getComponentLogicalIndex(i)]; 810 Rectangle2D r = tlc.getVisualBounds(); 811 812 Point2D.Float pt = new Point2D.Float(locs[n], locs[n+1]); 813 if (lp == null) { 814 r.setRect(r.getMinX() + pt.x, r.getMinY() + pt.y, 815 r.getWidth(), r.getHeight()); 816 } else { 817 lp.pathToPoint(pt, false, pt); 818 819 AffineTransform at = tlc.getBaselineTransform(); 820 if (at != null) { 821 AffineTransform tx = AffineTransform.getTranslateInstance 822 (pt.x - at.getTranslateX(), pt.y - at.getTranslateY()); 823 tx.concatenate(at); 824 r = tx.createTransformedShape(r).getBounds2D(); 825 } else { 826 r.setRect(r.getMinX() + pt.x, r.getMinY() + pt.y, 827 r.getWidth(), r.getHeight()); 828 } 829 } 830 831 if (result == null) { 832 result = r; 833 } else { 834 result.add(r); 835 } 836 } 837 838 if (result == null) { 839 result = new Rectangle2D.Float(Float.MAX_VALUE, Float.MAX_VALUE, Float.MIN_VALUE, Float.MIN_VALUE); 840 } 841 842 return result; 843 } 844 845 public Rectangle2D getItalicBounds() { 846 847 float left = Float.MAX_VALUE, right = -Float.MAX_VALUE; 848 float top = Float.MAX_VALUE, bottom = -Float.MAX_VALUE; 849 850 for (int i=0, n = 0; i < fComponents.length; i++, n += 2) { 851 TextLineComponent tlc = fComponents[getComponentLogicalIndex(i)]; 852 853 Rectangle2D tlcBounds = tlc.getItalicBounds(); 854 float x = locs[n]; 855 float y = locs[n+1]; 856 857 left = Math.min(left, x + (float)tlcBounds.getX()); 858 right = Math.max(right, x + (float)tlcBounds.getMaxX()); 859 860 top = Math.min(top, y + (float)tlcBounds.getY()); 861 bottom = Math.max(bottom, y + (float)tlcBounds.getMaxY()); 862 } 863 864 return new Rectangle2D.Float(left, top, right-left, bottom-top); 865 } 866 867 public Shape getOutline(AffineTransform tx) { 868 869 GeneralPath dstShape = new GeneralPath(GeneralPath.WIND_NON_ZERO); 870 871 for (int i=0, n = 0; i < fComponents.length; i++, n += 2) { 872 TextLineComponent tlc = fComponents[getComponentLogicalIndex(i)]; 873 874 dstShape.append(tlc.getOutline(locs[n], locs[n+1]), false); 875 } 876 877 if (tx != null) { 878 dstShape.transform(tx); 879 } 880 return dstShape; 881 } 882 883 public int hashCode() { 884 return (fComponents.length << 16) ^ 885 (fComponents[0].hashCode() << 3) ^ (fCharsLimit-fCharsStart); 886 } 887 888 public String toString() { 889 StringBuilder buf = new StringBuilder(); 890 891 for (int i = 0; i < fComponents.length; i++) { 892 buf.append(fComponents[i]); 893 } 894 895 return buf.toString(); 896 } 897 898 /** 899 * Create a TextLine from the text. The Font must be able to 900 * display all of the text. 901 * attributes==null is equivalent to using an empty Map for 902 * attributes 903 */ 904 public static TextLine fastCreateTextLine(FontRenderContext frc, 905 char[] chars, 906 Font font, 907 CoreMetrics lm, 908 Map<? extends Attribute, ?> attributes) { 909 910 boolean isDirectionLTR = true; 911 byte[] levels = null; 912 int[] charsLtoV = null; 913 Bidi bidi = null; 914 int characterCount = chars.length; 915 916 boolean requiresBidi = false; 917 byte[] embs = null; 918 919 AttributeValues values = null; 920 if (attributes != null) { 921 values = AttributeValues.fromMap(attributes); 922 if (values.getRunDirection() >= 0) { 923 isDirectionLTR = values.getRunDirection() == 0; 924 requiresBidi = !isDirectionLTR; 925 } 926 if (values.getBidiEmbedding() != 0) { 927 requiresBidi = true; 928 byte level = (byte)values.getBidiEmbedding(); 929 embs = new byte[characterCount]; 930 for (int i = 0; i < embs.length; ++i) { 931 embs[i] = level; 932 } 933 } 934 } 935 936 // dlf: get baseRot from font for now??? 937 938 if (!requiresBidi) { 939 requiresBidi = Bidi.requiresBidi(chars, 0, chars.length); 940 } 941 942 if (requiresBidi) { 943 int bidiflags = values == null 944 ? Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT 945 : values.getRunDirection(); 946 947 bidi = new Bidi(chars, 0, embs, 0, chars.length, bidiflags); 948 if (!bidi.isLeftToRight()) { 949 levels = BidiUtils.getLevels(bidi); 950 int[] charsVtoL = BidiUtils.createVisualToLogicalMap(levels); 951 charsLtoV = BidiUtils.createInverseMap(charsVtoL); 952 isDirectionLTR = bidi.baseIsLeftToRight(); 953 } 954 } 955 956 Decoration decorator = Decoration.getDecoration(values); 957 958 int layoutFlags = 0; // no extra info yet, bidi determines run and line direction 959 TextLabelFactory factory = new TextLabelFactory(frc, chars, bidi, layoutFlags); 960 961 TextLineComponent[] components = new TextLineComponent[1]; 962 963 components = createComponentsOnRun(0, chars.length, 964 chars, 965 charsLtoV, levels, 966 factory, font, lm, 967 frc, 968 decorator, 969 components, 970 0); 971 972 int numComponents = components.length; 973 while (components[numComponents-1] == null) { 974 numComponents -= 1; 975 } 976 977 if (numComponents != components.length) { 978 TextLineComponent[] temp = new TextLineComponent[numComponents]; 979 System.arraycopy(components, 0, temp, 0, numComponents); 980 components = temp; 981 } 982 983 return new TextLine(frc, components, lm.baselineOffsets, 984 chars, 0, chars.length, charsLtoV, levels, isDirectionLTR); 985 } 986 987 private static TextLineComponent[] expandArray(TextLineComponent[] orig) { 988 989 TextLineComponent[] newComponents = new TextLineComponent[orig.length + 8]; 990 System.arraycopy(orig, 0, newComponents, 0, orig.length); 991 992 return newComponents; 993 } 994 995 /** 996 * Returns an array in logical order of the TextLineComponents on 997 * the text in the given range, with the given attributes. 998 */ 999 public static TextLineComponent[] createComponentsOnRun(int runStart, 1000 int runLimit, 1001 char[] chars, 1002 int[] charsLtoV, 1003 byte[] levels, 1004 TextLabelFactory factory, 1005 Font font, 1006 CoreMetrics cm, 1007 FontRenderContext frc, 1008 Decoration decorator, 1009 TextLineComponent[] components, 1010 int numComponents) { 1011 1012 int pos = runStart; 1013 do { 1014 int chunkLimit = firstVisualChunk(charsLtoV, levels, pos, runLimit); // <= displayLimit 1015 1016 do { 1017 int startPos = pos; 1018 int lmCount; 1019 1020 if (cm == null) { 1021 LineMetrics lineMetrics = font.getLineMetrics(chars, startPos, chunkLimit, frc); 1022 cm = CoreMetrics.get(lineMetrics); 1023 lmCount = lineMetrics.getNumChars(); 1024 } 1025 else { 1026 lmCount = (chunkLimit-startPos); 1027 } 1028 1029 TextLineComponent nextComponent = 1030 factory.createExtended(font, cm, decorator, startPos, startPos + lmCount); 1031 1032 ++numComponents; 1033 if (numComponents >= components.length) { 1034 components = expandArray(components); 1035 } 1036 1037 components[numComponents-1] = nextComponent; 1038 1039 pos += lmCount; 1040 } while (pos < chunkLimit); 1041 1042 } while (pos < runLimit); 1043 1044 return components; 1045 } 1046 1047 /** 1048 * Returns an array (in logical order) of the TextLineComponents representing 1049 * the text. The components are both logically and visually contiguous. 1050 */ 1051 public static TextLineComponent[] getComponents(StyledParagraph styledParagraph, 1052 char[] chars, 1053 int textStart, 1054 int textLimit, 1055 int[] charsLtoV, 1056 byte[] levels, 1057 TextLabelFactory factory) { 1058 1059 FontRenderContext frc = factory.getFontRenderContext(); 1060 1061 int numComponents = 0; 1062 TextLineComponent[] tempComponents = new TextLineComponent[1]; 1063 1064 int pos = textStart; 1065 do { 1066 int runLimit = Math.min(styledParagraph.getRunLimit(pos), textLimit); 1067 1068 Decoration decorator = styledParagraph.getDecorationAt(pos); 1069 1070 Object graphicOrFont = styledParagraph.getFontOrGraphicAt(pos); 1071 1072 if (graphicOrFont instanceof GraphicAttribute) { 1073 // AffineTransform baseRot = styledParagraph.getBaselineRotationAt(pos); 1074 // !!! For now, let's assign runs of text with both fonts and graphic attributes 1075 // a null rotation (e.g. the baseline rotation goes away when a graphic 1076 // is applied. 1077 AffineTransform baseRot = null; 1078 GraphicAttribute graphicAttribute = (GraphicAttribute) graphicOrFont; 1079 do { 1080 int chunkLimit = firstVisualChunk(charsLtoV, levels, 1081 pos, runLimit); 1082 1083 GraphicComponent nextGraphic = 1084 new GraphicComponent(graphicAttribute, decorator, charsLtoV, levels, pos, chunkLimit, baseRot); 1085 pos = chunkLimit; 1086 1087 ++numComponents; 1088 if (numComponents >= tempComponents.length) { 1089 tempComponents = expandArray(tempComponents); 1090 } 1091 1092 tempComponents[numComponents-1] = nextGraphic; 1093 1094 } while(pos < runLimit); 1095 } 1096 else { 1097 Font font = (Font) graphicOrFont; 1098 1099 tempComponents = createComponentsOnRun(pos, runLimit, 1100 chars, 1101 charsLtoV, levels, 1102 factory, font, null, 1103 frc, 1104 decorator, 1105 tempComponents, 1106 numComponents); 1107 pos = runLimit; 1108 numComponents = tempComponents.length; 1109 while (tempComponents[numComponents-1] == null) { 1110 numComponents -= 1; 1111 } 1112 } 1113 1114 } while (pos < textLimit); 1115 1116 TextLineComponent[] components; 1117 if (tempComponents.length == numComponents) { 1118 components = tempComponents; 1119 } 1120 else { 1121 components = new TextLineComponent[numComponents]; 1122 System.arraycopy(tempComponents, 0, components, 0, numComponents); 1123 } 1124 1125 return components; 1126 } 1127 1128 /** 1129 * Create a TextLine from the Font and character data over the 1130 * range. The range is relative to both the StyledParagraph and the 1131 * character array. 1132 */ 1133 public static TextLine createLineFromText(char[] chars, 1134 StyledParagraph styledParagraph, 1135 TextLabelFactory factory, 1136 boolean isDirectionLTR, 1137 float[] baselineOffsets) { 1138 1139 factory.setLineContext(0, chars.length); 1140 1141 Bidi lineBidi = factory.getLineBidi(); 1142 int[] charsLtoV = null; 1143 byte[] levels = null; 1144 1145 if (lineBidi != null) { 1146 levels = BidiUtils.getLevels(lineBidi); 1147 int[] charsVtoL = BidiUtils.createVisualToLogicalMap(levels); 1148 charsLtoV = BidiUtils.createInverseMap(charsVtoL); 1149 } 1150 1151 TextLineComponent[] components = 1152 getComponents(styledParagraph, chars, 0, chars.length, charsLtoV, levels, factory); 1153 1154 return new TextLine(factory.getFontRenderContext(), components, baselineOffsets, 1155 chars, 0, chars.length, charsLtoV, levels, isDirectionLTR); 1156 } 1157 1158 /** 1159 * Compute the components order from the given components array and 1160 * logical-to-visual character mapping. May return null if canonical. 1161 */ 1162 private static int[] computeComponentOrder(TextLineComponent[] components, 1163 int[] charsLtoV) { 1164 1165 /* 1166 * Create a visual ordering for the glyph sets. The important thing 1167 * here is that the values have the proper rank with respect to 1168 * each other, not the exact values. For example, the first glyph 1169 * set that appears visually should have the lowest value. The last 1170 * should have the highest value. The values are then normalized 1171 * to map 1-1 with positions in glyphs. 1172 * 1173 */ 1174 int[] componentOrder = null; 1175 if (charsLtoV != null && components.length > 1) { 1176 componentOrder = new int[components.length]; 1177 int gStart = 0; 1178 for (int i = 0; i < components.length; i++) { 1179 componentOrder[i] = charsLtoV[gStart]; 1180 gStart += components[i].getNumCharacters(); 1181 } 1182 1183 componentOrder = BidiUtils.createContiguousOrder(componentOrder); 1184 componentOrder = BidiUtils.createInverseMap(componentOrder); 1185 } 1186 return componentOrder; 1187 } 1188 1189 1190 /** 1191 * Create a TextLine from the text. chars is just the text in the iterator. 1192 */ 1193 public static TextLine standardCreateTextLine(FontRenderContext frc, 1194 AttributedCharacterIterator text, 1195 char[] chars, 1196 float[] baselineOffsets) { 1197 1198 StyledParagraph styledParagraph = new StyledParagraph(text, chars); 1199 Bidi bidi = new Bidi(text); 1200 if (bidi.isLeftToRight()) { 1201 bidi = null; 1202 } 1203 int layoutFlags = 0; // no extra info yet, bidi determines run and line direction 1204 TextLabelFactory factory = new TextLabelFactory(frc, chars, bidi, layoutFlags); 1205 1206 boolean isDirectionLTR = true; 1207 if (bidi != null) { 1208 isDirectionLTR = bidi.baseIsLeftToRight(); 1209 } 1210 return createLineFromText(chars, styledParagraph, factory, isDirectionLTR, baselineOffsets); 1211 } 1212 1213 1214 1215 /* 1216 * A utility to get a range of text that is both logically and visually 1217 * contiguous. 1218 * If the entire range is ok, return limit, otherwise return the first 1219 * directional change after start. We could do better than this, but 1220 * it doesn't seem worth it at the moment. 1221 private static int firstVisualChunk(int order[], byte direction[], 1222 int start, int limit) 1223 { 1224 if (order != null) { 1225 int min = order[start]; 1226 int max = order[start]; 1227 int count = limit - start; 1228 for (int i = start + 1; i < limit; i++) { 1229 min = Math.min(min, order[i]); 1230 max = Math.max(max, order[i]); 1231 if (max - min >= count) { 1232 if (direction != null) { 1233 byte baseLevel = direction[start]; 1234 for (int j = start + 1; j < i; j++) { 1235 if (direction[j] != baseLevel) { 1236 return j; 1237 } 1238 } 1239 } 1240 return i; 1241 } 1242 } 1243 } 1244 return limit; 1245 } 1246 */ 1247 1248 /** 1249 * When this returns, the ACI's current position will be at the start of the 1250 * first run which does NOT contain a GraphicAttribute. If no such run exists 1251 * the ACI's position will be at the end, and this method will return false. 1252 */ 1253 static boolean advanceToFirstFont(AttributedCharacterIterator aci) { 1254 1255 for (char ch = aci.first(); 1256 ch != CharacterIterator.DONE; 1257 ch = aci.setIndex(aci.getRunLimit())) 1258 { 1259 1260 if (aci.getAttribute(TextAttribute.CHAR_REPLACEMENT) == null) { 1261 return true; 1262 } 1263 } 1264 1265 return false; 1266 } 1267 1268 static float[] getNormalizedOffsets(float[] baselineOffsets, byte baseline) { 1269 1270 if (baselineOffsets[baseline] != 0) { 1271 float base = baselineOffsets[baseline]; 1272 float[] temp = new float[baselineOffsets.length]; 1273 for (int i = 0; i < temp.length; i++) 1274 temp[i] = baselineOffsets[i] - base; 1275 baselineOffsets = temp; 1276 } 1277 return baselineOffsets; 1278 } 1279 1280 static Font getFontAtCurrentPos(AttributedCharacterIterator aci) { 1281 1282 Object value = aci.getAttribute(TextAttribute.FONT); 1283 if (value != null) { 1284 return (Font) value; 1285 } 1286 if (aci.getAttribute(TextAttribute.FAMILY) != null) { 1287 return Font.getFont(aci.getAttributes()); 1288 } 1289 1290 int ch = CodePointIterator.create(aci).next(); 1291 if (ch != CodePointIterator.DONE) { 1292 FontResolver resolver = FontResolver.getInstance(); 1293 return resolver.getFont(resolver.getFontIndex(ch), aci.getAttributes()); 1294 } 1295 return null; 1296 } 1297 1298 /* 1299 * The new version requires that chunks be at the same level. 1300 */ 1301 private static int firstVisualChunk(int order[], byte direction[], 1302 int start, int limit) 1303 { 1304 if (order != null && direction != null) { 1305 byte dir = direction[start]; 1306 while (++start < limit && direction[start] == dir) {} 1307 return start; 1308 } 1309 return limit; 1310 } 1311 1312 /* 1313 * create a new line with characters between charStart and charLimit 1314 * justified using the provided width and ratio. 1315 */ 1316 public TextLine getJustifiedLine(float justificationWidth, float justifyRatio, int justStart, int justLimit) { 1317 1318 TextLineComponent[] newComponents = new TextLineComponent[fComponents.length]; 1319 System.arraycopy(fComponents, 0, newComponents, 0, fComponents.length); 1320 1321 float leftHang = 0; 1322 float adv = 0; 1323 float justifyDelta = 0; 1324 boolean rejustify = false; 1325 do { 1326 adv = getAdvanceBetween(newComponents, 0, characterCount()); 1327 1328 // all characters outside the justification range must be in the base direction 1329 // of the layout, otherwise justification makes no sense. 1330 1331 float justifyAdvance = getAdvanceBetween(newComponents, justStart, justLimit); 1332 1333 // get the actual justification delta 1334 justifyDelta = (justificationWidth - justifyAdvance) * justifyRatio; 1335 1336 // generate an array of GlyphJustificationInfo records to pass to 1337 // the justifier. Array is visually ordered. 1338 1339 // get positions that each component will be using 1340 int[] infoPositions = new int[newComponents.length]; 1341 int infoCount = 0; 1342 for (int visIndex = 0; visIndex < newComponents.length; visIndex++) { 1343 int logIndex = getComponentLogicalIndex(visIndex); 1344 infoPositions[logIndex] = infoCount; 1345 infoCount += newComponents[logIndex].getNumJustificationInfos(); 1346 } 1347 GlyphJustificationInfo[] infos = new GlyphJustificationInfo[infoCount]; 1348 1349 // get justification infos 1350 int compStart = 0; 1351 for (int i = 0; i < newComponents.length; i++) { 1352 TextLineComponent comp = newComponents[i]; 1353 int compLength = comp.getNumCharacters(); 1354 int compLimit = compStart + compLength; 1355 if (compLimit > justStart) { 1356 int rangeMin = Math.max(0, justStart - compStart); 1357 int rangeMax = Math.min(compLength, justLimit - compStart); 1358 comp.getJustificationInfos(infos, infoPositions[i], rangeMin, rangeMax); 1359 1360 if (compLimit >= justLimit) { 1361 break; 1362 } 1363 } 1364 } 1365 1366 // records are visually ordered, and contiguous, so start and end are 1367 // simply the places where we didn't fetch records 1368 int infoStart = 0; 1369 int infoLimit = infoCount; 1370 while (infoStart < infoLimit && infos[infoStart] == null) { 1371 ++infoStart; 1372 } 1373 1374 while (infoLimit > infoStart && infos[infoLimit - 1] == null) { 1375 --infoLimit; 1376 } 1377 1378 // invoke justifier on the records 1379 TextJustifier justifier = new TextJustifier(infos, infoStart, infoLimit); 1380 1381 float[] deltas = justifier.justify(justifyDelta); 1382 1383 boolean canRejustify = rejustify == false; 1384 boolean wantRejustify = false; 1385 boolean[] flags = new boolean[1]; 1386 1387 // apply justification deltas 1388 compStart = 0; 1389 for (int i = 0; i < newComponents.length; i++) { 1390 TextLineComponent comp = newComponents[i]; 1391 int compLength = comp.getNumCharacters(); 1392 int compLimit = compStart + compLength; 1393 if (compLimit > justStart) { 1394 int rangeMin = Math.max(0, justStart - compStart); 1395 int rangeMax = Math.min(compLength, justLimit - compStart); 1396 newComponents[i] = comp.applyJustificationDeltas(deltas, infoPositions[i] * 2, flags); 1397 1398 wantRejustify |= flags[0]; 1399 1400 if (compLimit >= justLimit) { 1401 break; 1402 } 1403 } 1404 } 1405 1406 rejustify = wantRejustify && !rejustify; // only make two passes 1407 } while (rejustify); 1408 1409 return new TextLine(frc, newComponents, fBaselineOffsets, fChars, fCharsStart, 1410 fCharsLimit, fCharLogicalOrder, fCharLevels, 1411 fIsDirectionLTR); 1412 } 1413 1414 // return the sum of the advances of text between the logical start and limit 1415 public static float getAdvanceBetween(TextLineComponent[] components, int start, int limit) { 1416 float advance = 0; 1417 1418 int tlcStart = 0; 1419 for(int i = 0; i < components.length; i++) { 1420 TextLineComponent comp = components[i]; 1421 1422 int tlcLength = comp.getNumCharacters(); 1423 int tlcLimit = tlcStart + tlcLength; 1424 if (tlcLimit > start) { 1425 int measureStart = Math.max(0, start - tlcStart); 1426 int measureLimit = Math.min(tlcLength, limit - tlcStart); 1427 advance += comp.getAdvanceBetween(measureStart, measureLimit); 1428 if (tlcLimit >= limit) { 1429 break; 1430 } 1431 } 1432 1433 tlcStart = tlcLimit; 1434 } 1435 1436 return advance; 1437 } 1438 1439 LayoutPathImpl getLayoutPath() { 1440 return lp; 1441 } 1442 }