1 /*
   2  * Copyright (c) 1998, 2018, 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 }