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