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 String toString() { 884 StringBuilder buf = new StringBuilder(); 885 886 for (int i = 0; i < fComponents.length; i++) { 887 buf.append(fComponents[i]); 888 } 889 890 return buf.toString(); 891 } 892 893 /** 894 * Create a TextLine from the text. The Font must be able to 895 * display all of the text. 896 * attributes==null is equivalent to using an empty Map for 897 * attributes 898 */ 899 public static TextLine fastCreateTextLine(FontRenderContext frc, 900 char[] chars, 901 Font font, 902 CoreMetrics lm, 903 Map<? extends Attribute, ?> attributes) { 904 905 boolean isDirectionLTR = true; 906 byte[] levels = null; 907 int[] charsLtoV = null; 908 Bidi bidi = null; 909 int characterCount = chars.length; 910 911 boolean requiresBidi = false; 912 byte[] embs = null; 913 914 AttributeValues values = null; 915 if (attributes != null) { 916 values = AttributeValues.fromMap(attributes); 917 if (values.getRunDirection() >= 0) { 918 isDirectionLTR = values.getRunDirection() == 0; 919 requiresBidi = !isDirectionLTR; 920 } 921 if (values.getBidiEmbedding() != 0) { 922 requiresBidi = true; 923 byte level = (byte)values.getBidiEmbedding(); 924 embs = new byte[characterCount]; 925 for (int i = 0; i < embs.length; ++i) { 926 embs[i] = level; 927 } 928 } 929 } 930 931 // dlf: get baseRot from font for now??? 932 933 if (!requiresBidi) { 934 requiresBidi = Bidi.requiresBidi(chars, 0, chars.length); 935 } 936 937 if (requiresBidi) { 938 int bidiflags = values == null 939 ? Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT 940 : values.getRunDirection(); 941 942 bidi = new Bidi(chars, 0, embs, 0, chars.length, bidiflags); 943 if (!bidi.isLeftToRight()) { 944 levels = BidiUtils.getLevels(bidi); 945 int[] charsVtoL = BidiUtils.createVisualToLogicalMap(levels); 946 charsLtoV = BidiUtils.createInverseMap(charsVtoL); 947 isDirectionLTR = bidi.baseIsLeftToRight(); 948 } 949 } 950 951 Decoration decorator = Decoration.getDecoration(values); 952 953 int layoutFlags = 0; // no extra info yet, bidi determines run and line direction 954 TextLabelFactory factory = new TextLabelFactory(frc, chars, bidi, layoutFlags); 955 956 TextLineComponent[] components = new TextLineComponent[1]; 957 958 components = createComponentsOnRun(0, chars.length, 959 chars, 960 charsLtoV, levels, 961 factory, font, lm, 962 frc, 963 decorator, 964 components, 965 0); 966 967 int numComponents = components.length; 968 while (components[numComponents-1] == null) { 969 numComponents -= 1; 970 } 971 972 if (numComponents != components.length) { 973 TextLineComponent[] temp = new TextLineComponent[numComponents]; 974 System.arraycopy(components, 0, temp, 0, numComponents); 975 components = temp; 976 } 977 978 return new TextLine(frc, components, lm.baselineOffsets, 979 chars, 0, chars.length, charsLtoV, levels, isDirectionLTR); 980 } 981 982 private static TextLineComponent[] expandArray(TextLineComponent[] orig) { 983 984 TextLineComponent[] newComponents = new TextLineComponent[orig.length + 8]; 985 System.arraycopy(orig, 0, newComponents, 0, orig.length); 986 987 return newComponents; 988 } 989 990 /** 991 * Returns an array in logical order of the TextLineComponents on 992 * the text in the given range, with the given attributes. 993 */ 994 public static TextLineComponent[] createComponentsOnRun(int runStart, 995 int runLimit, 996 char[] chars, 997 int[] charsLtoV, 998 byte[] levels, 999 TextLabelFactory factory, 1000 Font font, 1001 CoreMetrics cm, 1002 FontRenderContext frc, 1003 Decoration decorator, 1004 TextLineComponent[] components, 1005 int numComponents) { 1006 1007 int pos = runStart; 1008 do { 1009 int chunkLimit = firstVisualChunk(charsLtoV, levels, pos, runLimit); // <= displayLimit 1010 1011 do { 1012 int startPos = pos; 1013 int lmCount; 1014 1015 if (cm == null) { 1016 LineMetrics lineMetrics = font.getLineMetrics(chars, startPos, chunkLimit, frc); 1017 cm = CoreMetrics.get(lineMetrics); 1018 lmCount = lineMetrics.getNumChars(); 1019 } 1020 else { 1021 lmCount = (chunkLimit-startPos); 1022 } 1023 1024 TextLineComponent nextComponent = 1025 factory.createExtended(font, cm, decorator, startPos, startPos + lmCount); 1026 1027 ++numComponents; 1028 if (numComponents >= components.length) { 1029 components = expandArray(components); 1030 } 1031 1032 components[numComponents-1] = nextComponent; 1033 1034 pos += lmCount; 1035 } while (pos < chunkLimit); 1036 1037 } while (pos < runLimit); 1038 1039 return components; 1040 } 1041 1042 /** 1043 * Returns an array (in logical order) of the TextLineComponents representing 1044 * the text. The components are both logically and visually contiguous. 1045 */ 1046 public static TextLineComponent[] getComponents(StyledParagraph styledParagraph, 1047 char[] chars, 1048 int textStart, 1049 int textLimit, 1050 int[] charsLtoV, 1051 byte[] levels, 1052 TextLabelFactory factory) { 1053 1054 FontRenderContext frc = factory.getFontRenderContext(); 1055 1056 int numComponents = 0; 1057 TextLineComponent[] tempComponents = new TextLineComponent[1]; 1058 1059 int pos = textStart; 1060 do { 1061 int runLimit = Math.min(styledParagraph.getRunLimit(pos), textLimit); 1062 1063 Decoration decorator = styledParagraph.getDecorationAt(pos); 1064 1065 Object graphicOrFont = styledParagraph.getFontOrGraphicAt(pos); 1066 1067 if (graphicOrFont instanceof GraphicAttribute) { 1068 // AffineTransform baseRot = styledParagraph.getBaselineRotationAt(pos); 1069 // !!! For now, let's assign runs of text with both fonts and graphic attributes 1070 // a null rotation (e.g. the baseline rotation goes away when a graphic 1071 // is applied. 1072 AffineTransform baseRot = null; 1073 GraphicAttribute graphicAttribute = (GraphicAttribute) graphicOrFont; 1074 do { 1075 int chunkLimit = firstVisualChunk(charsLtoV, levels, 1076 pos, runLimit); 1077 1078 GraphicComponent nextGraphic = 1079 new GraphicComponent(graphicAttribute, decorator, charsLtoV, levels, pos, chunkLimit, baseRot); 1080 pos = chunkLimit; 1081 1082 ++numComponents; 1083 if (numComponents >= tempComponents.length) { 1084 tempComponents = expandArray(tempComponents); 1085 } 1086 1087 tempComponents[numComponents-1] = nextGraphic; 1088 1089 } while(pos < runLimit); 1090 } 1091 else { 1092 Font font = (Font) graphicOrFont; 1093 1094 tempComponents = createComponentsOnRun(pos, runLimit, 1095 chars, 1096 charsLtoV, levels, 1097 factory, font, null, 1098 frc, 1099 decorator, 1100 tempComponents, 1101 numComponents); 1102 pos = runLimit; 1103 numComponents = tempComponents.length; 1104 while (tempComponents[numComponents-1] == null) { 1105 numComponents -= 1; 1106 } 1107 } 1108 1109 } while (pos < textLimit); 1110 1111 TextLineComponent[] components; 1112 if (tempComponents.length == numComponents) { 1113 components = tempComponents; 1114 } 1115 else { 1116 components = new TextLineComponent[numComponents]; 1117 System.arraycopy(tempComponents, 0, components, 0, numComponents); 1118 } 1119 1120 return components; 1121 } 1122 1123 /** 1124 * Create a TextLine from the Font and character data over the 1125 * range. The range is relative to both the StyledParagraph and the 1126 * character array. 1127 */ 1128 public static TextLine createLineFromText(char[] chars, 1129 StyledParagraph styledParagraph, 1130 TextLabelFactory factory, 1131 boolean isDirectionLTR, 1132 float[] baselineOffsets) { 1133 1134 factory.setLineContext(0, chars.length); 1135 1136 Bidi lineBidi = factory.getLineBidi(); 1137 int[] charsLtoV = null; 1138 byte[] levels = null; 1139 1140 if (lineBidi != null) { 1141 levels = BidiUtils.getLevels(lineBidi); 1142 int[] charsVtoL = BidiUtils.createVisualToLogicalMap(levels); 1143 charsLtoV = BidiUtils.createInverseMap(charsVtoL); 1144 } 1145 1146 TextLineComponent[] components = 1147 getComponents(styledParagraph, chars, 0, chars.length, charsLtoV, levels, factory); 1148 1149 return new TextLine(factory.getFontRenderContext(), components, baselineOffsets, 1150 chars, 0, chars.length, charsLtoV, levels, isDirectionLTR); 1151 } 1152 1153 /** 1154 * Compute the components order from the given components array and 1155 * logical-to-visual character mapping. May return null if canonical. 1156 */ 1157 private static int[] computeComponentOrder(TextLineComponent[] components, 1158 int[] charsLtoV) { 1159 1160 /* 1161 * Create a visual ordering for the glyph sets. The important thing 1162 * here is that the values have the proper rank with respect to 1163 * each other, not the exact values. For example, the first glyph 1164 * set that appears visually should have the lowest value. The last 1165 * should have the highest value. The values are then normalized 1166 * to map 1-1 with positions in glyphs. 1167 * 1168 */ 1169 int[] componentOrder = null; 1170 if (charsLtoV != null && components.length > 1) { 1171 componentOrder = new int[components.length]; 1172 int gStart = 0; 1173 for (int i = 0; i < components.length; i++) { 1174 componentOrder[i] = charsLtoV[gStart]; 1175 gStart += components[i].getNumCharacters(); 1176 } 1177 1178 componentOrder = BidiUtils.createContiguousOrder(componentOrder); 1179 componentOrder = BidiUtils.createInverseMap(componentOrder); 1180 } 1181 return componentOrder; 1182 } 1183 1184 1185 /** 1186 * Create a TextLine from the text. chars is just the text in the iterator. 1187 */ 1188 public static TextLine standardCreateTextLine(FontRenderContext frc, 1189 AttributedCharacterIterator text, 1190 char[] chars, 1191 float[] baselineOffsets) { 1192 1193 StyledParagraph styledParagraph = new StyledParagraph(text, chars); 1194 Bidi bidi = new Bidi(text); 1195 if (bidi.isLeftToRight()) { 1196 bidi = null; 1197 } 1198 int layoutFlags = 0; // no extra info yet, bidi determines run and line direction 1199 TextLabelFactory factory = new TextLabelFactory(frc, chars, bidi, layoutFlags); 1200 1201 boolean isDirectionLTR = true; 1202 if (bidi != null) { 1203 isDirectionLTR = bidi.baseIsLeftToRight(); 1204 } 1205 return createLineFromText(chars, styledParagraph, factory, isDirectionLTR, baselineOffsets); 1206 } 1207 1208 1209 1210 /* 1211 * A utility to get a range of text that is both logically and visually 1212 * contiguous. 1213 * If the entire range is ok, return limit, otherwise return the first 1214 * directional change after start. We could do better than this, but 1215 * it doesn't seem worth it at the moment. 1216 private static int firstVisualChunk(int order[], byte direction[], 1217 int start, int limit) 1218 { 1219 if (order != null) { 1220 int min = order[start]; 1221 int max = order[start]; 1222 int count = limit - start; 1223 for (int i = start + 1; i < limit; i++) { 1224 min = Math.min(min, order[i]); 1225 max = Math.max(max, order[i]); 1226 if (max - min >= count) { 1227 if (direction != null) { 1228 byte baseLevel = direction[start]; 1229 for (int j = start + 1; j < i; j++) { 1230 if (direction[j] != baseLevel) { 1231 return j; 1232 } 1233 } 1234 } 1235 return i; 1236 } 1237 } 1238 } 1239 return limit; 1240 } 1241 */ 1242 1243 /** 1244 * When this returns, the ACI's current position will be at the start of the 1245 * first run which does NOT contain a GraphicAttribute. If no such run exists 1246 * the ACI's position will be at the end, and this method will return false. 1247 */ 1248 static boolean advanceToFirstFont(AttributedCharacterIterator aci) { 1249 1250 for (char ch = aci.first(); 1251 ch != CharacterIterator.DONE; 1252 ch = aci.setIndex(aci.getRunLimit())) 1253 { 1254 1255 if (aci.getAttribute(TextAttribute.CHAR_REPLACEMENT) == null) { 1256 return true; 1257 } 1258 } 1259 1260 return false; 1261 } 1262 1263 static float[] getNormalizedOffsets(float[] baselineOffsets, byte baseline) { 1264 1265 if (baselineOffsets[baseline] != 0) { 1266 float base = baselineOffsets[baseline]; 1267 float[] temp = new float[baselineOffsets.length]; 1268 for (int i = 0; i < temp.length; i++) 1269 temp[i] = baselineOffsets[i] - base; 1270 baselineOffsets = temp; 1271 } 1272 return baselineOffsets; 1273 } 1274 1275 static Font getFontAtCurrentPos(AttributedCharacterIterator aci) { 1276 1277 Object value = aci.getAttribute(TextAttribute.FONT); 1278 if (value != null) { 1279 return (Font) value; 1280 } 1281 if (aci.getAttribute(TextAttribute.FAMILY) != null) { 1282 return Font.getFont(aci.getAttributes()); 1283 } 1284 1285 int ch = CodePointIterator.create(aci).next(); 1286 if (ch != CodePointIterator.DONE) { 1287 FontResolver resolver = FontResolver.getInstance(); 1288 return resolver.getFont(resolver.getFontIndex(ch), aci.getAttributes()); 1289 } 1290 return null; 1291 } 1292 1293 /* 1294 * The new version requires that chunks be at the same level. 1295 */ 1296 private static int firstVisualChunk(int order[], byte direction[], 1297 int start, int limit) 1298 { 1299 if (order != null && direction != null) { 1300 byte dir = direction[start]; 1301 while (++start < limit && direction[start] == dir) {} 1302 return start; 1303 } 1304 return limit; 1305 } 1306 1307 /* 1308 * create a new line with characters between charStart and charLimit 1309 * justified using the provided width and ratio. 1310 */ 1311 public TextLine getJustifiedLine(float justificationWidth, float justifyRatio, int justStart, int justLimit) { 1312 1313 TextLineComponent[] newComponents = new TextLineComponent[fComponents.length]; 1314 System.arraycopy(fComponents, 0, newComponents, 0, fComponents.length); 1315 1316 float leftHang = 0; 1317 float adv = 0; 1318 float justifyDelta = 0; 1319 boolean rejustify = false; 1320 do { 1321 adv = getAdvanceBetween(newComponents, 0, characterCount()); 1322 1323 // all characters outside the justification range must be in the base direction 1324 // of the layout, otherwise justification makes no sense. 1325 1326 float justifyAdvance = getAdvanceBetween(newComponents, justStart, justLimit); 1327 1328 // get the actual justification delta 1329 justifyDelta = (justificationWidth - justifyAdvance) * justifyRatio; 1330 1331 // generate an array of GlyphJustificationInfo records to pass to 1332 // the justifier. Array is visually ordered. 1333 1334 // get positions that each component will be using 1335 int[] infoPositions = new int[newComponents.length]; 1336 int infoCount = 0; 1337 for (int visIndex = 0; visIndex < newComponents.length; visIndex++) { 1338 int logIndex = getComponentLogicalIndex(visIndex); 1339 infoPositions[logIndex] = infoCount; 1340 infoCount += newComponents[logIndex].getNumJustificationInfos(); 1341 } 1342 GlyphJustificationInfo[] infos = new GlyphJustificationInfo[infoCount]; 1343 1344 // get justification infos 1345 int compStart = 0; 1346 for (int i = 0; i < newComponents.length; i++) { 1347 TextLineComponent comp = newComponents[i]; 1348 int compLength = comp.getNumCharacters(); 1349 int compLimit = compStart + compLength; 1350 if (compLimit > justStart) { 1351 int rangeMin = Math.max(0, justStart - compStart); 1352 int rangeMax = Math.min(compLength, justLimit - compStart); 1353 comp.getJustificationInfos(infos, infoPositions[i], rangeMin, rangeMax); 1354 1355 if (compLimit >= justLimit) { 1356 break; 1357 } 1358 } 1359 } 1360 1361 // records are visually ordered, and contiguous, so start and end are 1362 // simply the places where we didn't fetch records 1363 int infoStart = 0; 1364 int infoLimit = infoCount; 1365 while (infoStart < infoLimit && infos[infoStart] == null) { 1366 ++infoStart; 1367 } 1368 1369 while (infoLimit > infoStart && infos[infoLimit - 1] == null) { 1370 --infoLimit; 1371 } 1372 1373 // invoke justifier on the records 1374 TextJustifier justifier = new TextJustifier(infos, infoStart, infoLimit); 1375 1376 float[] deltas = justifier.justify(justifyDelta); 1377 1378 boolean canRejustify = rejustify == false; 1379 boolean wantRejustify = false; 1380 boolean[] flags = new boolean[1]; 1381 1382 // apply justification deltas 1383 compStart = 0; 1384 for (int i = 0; i < newComponents.length; i++) { 1385 TextLineComponent comp = newComponents[i]; 1386 int compLength = comp.getNumCharacters(); 1387 int compLimit = compStart + compLength; 1388 if (compLimit > justStart) { 1389 int rangeMin = Math.max(0, justStart - compStart); 1390 int rangeMax = Math.min(compLength, justLimit - compStart); 1391 newComponents[i] = comp.applyJustificationDeltas(deltas, infoPositions[i] * 2, flags); 1392 1393 wantRejustify |= flags[0]; 1394 1395 if (compLimit >= justLimit) { 1396 break; 1397 } 1398 } 1399 } 1400 1401 rejustify = wantRejustify && !rejustify; // only make two passes 1402 } while (rejustify); 1403 1404 return new TextLine(frc, newComponents, fBaselineOffsets, fChars, fCharsStart, 1405 fCharsLimit, fCharLogicalOrder, fCharLevels, 1406 fIsDirectionLTR); 1407 } 1408 1409 // return the sum of the advances of text between the logical start and limit 1410 public static float getAdvanceBetween(TextLineComponent[] components, int start, int limit) { 1411 float advance = 0; 1412 1413 int tlcStart = 0; 1414 for(int i = 0; i < components.length; i++) { 1415 TextLineComponent comp = components[i]; 1416 1417 int tlcLength = comp.getNumCharacters(); 1418 int tlcLimit = tlcStart + tlcLength; 1419 if (tlcLimit > start) { 1420 int measureStart = Math.max(0, start - tlcStart); 1421 int measureLimit = Math.min(tlcLength, limit - tlcStart); 1422 advance += comp.getAdvanceBetween(measureStart, measureLimit); 1423 if (tlcLimit >= limit) { 1424 break; 1425 } 1426 } 1427 1428 tlcStart = tlcLimit; 1429 } 1430 1431 return advance; 1432 } 1433 1434 LayoutPathImpl getLayoutPath() { 1435 return lp; 1436 } 1437 }