0001 /*
0002 * Copyright 1998-2006 Sun Microsystems, Inc. All Rights Reserved.
0003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0004 *
0005 * This code is free software; you can redistribute it and/or modify it
0006 * under the terms of the GNU General Public License version 2 only, as
0007 * published by the Free Software Foundation. Sun designates this
0008 * particular file as subject to the "Classpath" exception as provided
0009 * by Sun in the LICENSE file that accompanied this code.
0010 *
0011 * This code is distributed in the hope that it will be useful, but WITHOUT
0012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0014 * version 2 for more details (a copy is included in the LICENSE file that
0015 * accompanied this code).
0016 *
0017 * You should have received a copy of the GNU General Public License version
0018 * 2 along with this work; if not, write to the Free Software Foundation,
0019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0020 *
0021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
0022 * CA 95054 USA or visit www.sun.com if you need additional information or
0023 * have any questions.
0024 */
0025
0026 /*
0027 * (C) Copyright IBM Corp. 1998-2003, All Rights Reserved
0028 *
0029 */
0030
0031 package java.awt.font;
0032
0033 import java.awt.Color;
0034 import java.awt.Font;
0035 import java.awt.Graphics2D;
0036 import java.awt.Rectangle;
0037 import java.awt.Shape;
0038 import java.awt.geom.AffineTransform;
0039 import java.awt.geom.GeneralPath;
0040 import java.awt.geom.Point2D;
0041 import java.awt.geom.Rectangle2D;
0042 import java.awt.im.InputMethodHighlight;
0043 import java.awt.image.BufferedImage;
0044 import java.text.Annotation;
0045 import java.text.AttributedCharacterIterator;
0046 import java.text.Bidi;
0047 import java.text.CharacterIterator;
0048 import java.util.Hashtable;
0049 import java.util.Map;
0050 import sun.font.AttributeValues;
0051 import sun.font.BidiUtils;
0052 import sun.font.CoreMetrics;
0053 import sun.font.Decoration;
0054 import sun.font.FontLineMetrics;
0055 import sun.font.FontResolver;
0056 import sun.font.GraphicComponent;
0057 import sun.font.LayoutPathImpl;
0058 import sun.font.LayoutPathImpl.EmptyPath;
0059 import sun.font.LayoutPathImpl.SegmentPathBuilder;
0060 import sun.font.TextLabelFactory;
0061 import sun.font.TextLineComponent;
0062 import sun.text.CodePointIterator;
0063
0064 import java.awt.geom.Line2D;
0065
0066 final class TextLine {
0067
0068 static final class TextLineMetrics {
0069 public final float ascent;
0070 public final float descent;
0071 public final float leading;
0072 public final float advance;
0073
0074 public TextLineMetrics(float ascent, float descent,
0075 float leading, float advance) {
0076 this .ascent = ascent;
0077 this .descent = descent;
0078 this .leading = leading;
0079 this .advance = advance;
0080 }
0081 }
0082
0083 private TextLineComponent[] fComponents;
0084 private float[] fBaselineOffsets;
0085 private int[] fComponentVisualOrder; // if null, ltr
0086 private float[] locs; // x,y pairs for components in visual order
0087 private char[] fChars;
0088 private int fCharsStart;
0089 private int fCharsLimit;
0090 private int[] fCharVisualOrder; // if null, ltr
0091 private int[] fCharLogicalOrder; // if null, ltr
0092 private byte[] fCharLevels; // if null, 0
0093 private boolean fIsDirectionLTR;
0094 private LayoutPathImpl lp;
0095 private boolean isSimple;
0096 private Rectangle pixelBounds;
0097 private FontRenderContext frc;
0098
0099 private TextLineMetrics fMetrics = null; // built on demand in getMetrics
0100
0101 public TextLine(FontRenderContext frc,
0102 TextLineComponent[] components, float[] baselineOffsets,
0103 char[] chars, int charsStart, int charsLimit,
0104 int[] charLogicalOrder, byte[] charLevels,
0105 boolean isDirectionLTR) {
0106
0107 int[] componentVisualOrder = computeComponentOrder(components,
0108 charLogicalOrder);
0109
0110 this .frc = frc;
0111 fComponents = components;
0112 fBaselineOffsets = baselineOffsets;
0113 fComponentVisualOrder = componentVisualOrder;
0114 fChars = chars;
0115 fCharsStart = charsStart;
0116 fCharsLimit = charsLimit;
0117 fCharLogicalOrder = charLogicalOrder;
0118 fCharLevels = charLevels;
0119 fIsDirectionLTR = isDirectionLTR;
0120 checkCtorArgs();
0121
0122 init();
0123 }
0124
0125 private void checkCtorArgs() {
0126
0127 int checkCharCount = 0;
0128 for (int i = 0; i < fComponents.length; i++) {
0129 checkCharCount += fComponents[i].getNumCharacters();
0130 }
0131
0132 if (checkCharCount != this .characterCount()) {
0133 throw new IllegalArgumentException("Invalid TextLine! "
0134 + "char count is different from "
0135 + "sum of char counts of components.");
0136 }
0137 }
0138
0139 private void init() {
0140
0141 // first, we need to check for graphic components on the TOP or BOTTOM baselines. So
0142 // we perform the work that used to be in getMetrics here.
0143
0144 float ascent = 0;
0145 float descent = 0;
0146 float leading = 0;
0147 float advance = 0;
0148
0149 // ascent + descent must not be less than this value
0150 float maxGraphicHeight = 0;
0151 float maxGraphicHeightWithLeading = 0;
0152
0153 // walk through EGA's
0154 TextLineComponent tlc;
0155 boolean fitTopAndBottomGraphics = false;
0156
0157 isSimple = true;
0158
0159 for (int i = 0; i < fComponents.length; i++) {
0160 tlc = fComponents[i];
0161
0162 isSimple &= tlc.isSimple();
0163
0164 CoreMetrics cm = tlc.getCoreMetrics();
0165
0166 byte baseline = (byte) cm.baselineIndex;
0167
0168 if (baseline >= 0) {
0169 float baselineOffset = fBaselineOffsets[baseline];
0170
0171 ascent = Math.max(ascent, -baselineOffset + cm.ascent);
0172
0173 float gd = baselineOffset + cm.descent;
0174 descent = Math.max(descent, gd);
0175
0176 leading = Math.max(leading, gd + cm.leading);
0177 } else {
0178 fitTopAndBottomGraphics = true;
0179 float graphicHeight = cm.ascent + cm.descent;
0180 float graphicHeightWithLeading = graphicHeight
0181 + cm.leading;
0182 maxGraphicHeight = Math.max(maxGraphicHeight,
0183 graphicHeight);
0184 maxGraphicHeightWithLeading = Math.max(
0185 maxGraphicHeightWithLeading,
0186 graphicHeightWithLeading);
0187 }
0188 }
0189
0190 if (fitTopAndBottomGraphics) {
0191 if (maxGraphicHeight > ascent + descent) {
0192 descent = maxGraphicHeight - ascent;
0193 }
0194 if (maxGraphicHeightWithLeading > ascent + leading) {
0195 leading = maxGraphicHeightWithLeading - ascent;
0196 }
0197 }
0198
0199 leading -= descent;
0200
0201 // we now know enough to compute the locs, but we need the final loc
0202 // for the advance before we can create the metrics object
0203
0204 if (fitTopAndBottomGraphics) {
0205 // we have top or bottom baselines, so expand the baselines array
0206 // full offsets are needed by CoreMetrics.effectiveBaselineOffset
0207 fBaselineOffsets = new float[] { fBaselineOffsets[0],
0208 fBaselineOffsets[1], fBaselineOffsets[2], descent,
0209 -ascent };
0210 }
0211
0212 float x = 0;
0213 float y = 0;
0214 CoreMetrics pcm = null;
0215
0216 boolean needPath = false;
0217 locs = new float[fComponents.length * 2 + 2];
0218
0219 for (int i = 0, n = 0; i < fComponents.length; ++i, n += 2) {
0220 int vi = fComponentVisualOrder == null ? i
0221 : fComponentVisualOrder[i];
0222
0223 tlc = fComponents[vi];
0224 CoreMetrics cm = tlc.getCoreMetrics();
0225
0226 if ((pcm != null)
0227 && (pcm.italicAngle != 0 || cm.italicAngle != 0) && // adjust because of italics
0228 (pcm.italicAngle != cm.italicAngle
0229 || pcm.baselineIndex != cm.baselineIndex || pcm.ssOffset != cm.ssOffset)) {
0230
0231 // 1) compute the area of overlap - min effective ascent and min effective descent
0232 // 2) compute the x positions along italic angle of ascent and descent for left and right
0233 // 3) compute maximum left - right, adjust right position by this value
0234 // this is a crude form of kerning between textcomponents
0235
0236 // note glyphvectors preposition glyphs based on offset,
0237 // so tl doesn't need to adjust glyphvector position
0238 // 1)
0239 float pb = pcm
0240 .effectiveBaselineOffset(fBaselineOffsets);
0241 float pa = pb - pcm.ascent;
0242 float pd = pb + pcm.descent;
0243 // pb += pcm.ssOffset;
0244
0245 float cb = cm.effectiveBaselineOffset(fBaselineOffsets);
0246 float ca = cb - cm.ascent;
0247 float cd = cb + cm.descent;
0248 // cb += cm.ssOffset;
0249
0250 float a = Math.max(pa, ca);
0251 float d = Math.min(pd, cd);
0252
0253 // 2)
0254 float pax = pcm.italicAngle * (pb - a);
0255 float pdx = pcm.italicAngle * (pb - d);
0256
0257 float cax = cm.italicAngle * (cb - a);
0258 float cdx = cm.italicAngle * (cb - d);
0259
0260 // 3)
0261 float dax = pax - cax;
0262 float ddx = pdx - cdx;
0263 float dx = Math.max(dax, ddx);
0264
0265 x += dx;
0266 y = cb;
0267 } else {
0268 // no italic adjustment for x, but still need to compute y
0269 y = cm.effectiveBaselineOffset(fBaselineOffsets); // + cm.ssOffset;
0270 }
0271
0272 locs[n] = x;
0273 locs[n + 1] = y;
0274
0275 x += tlc.getAdvance();
0276 pcm = cm;
0277
0278 needPath |= tlc.getBaselineTransform() != null;
0279 }
0280
0281 // do we want italic padding at the right of the line?
0282 if (pcm.italicAngle != 0) {
0283 float pb = pcm.effectiveBaselineOffset(fBaselineOffsets);
0284 float pa = pb - pcm.ascent;
0285 float pd = pb + pcm.descent;
0286 pb += pcm.ssOffset;
0287
0288 float d;
0289 if (pcm.italicAngle > 0) {
0290 d = pb + pcm.ascent;
0291 } else {
0292 d = pb - pcm.descent;
0293 }
0294 d *= pcm.italicAngle;
0295
0296 x += d;
0297 }
0298 locs[locs.length - 2] = x;
0299 // locs[locs.length - 1] = 0; // final offset is always back on baseline
0300
0301 // ok, build fMetrics since we have the final advance
0302 advance = x;
0303 fMetrics = new TextLineMetrics(ascent, descent, leading,
0304 advance);
0305
0306 // build path if we need it
0307 if (needPath) {
0308 isSimple = false;
0309
0310 Point2D.Double pt = new Point2D.Double();
0311 double tx = 0, ty = 0;
0312 SegmentPathBuilder builder = new SegmentPathBuilder();
0313 builder.moveTo(locs[0], 0);
0314 for (int i = 0, n = 0; i < fComponents.length; ++i, n += 2) {
0315 int vi = fComponentVisualOrder == null ? i
0316 : fComponentVisualOrder[i];
0317 tlc = fComponents[vi];
0318 AffineTransform at = tlc.getBaselineTransform();
0319 if (at != null
0320 && ((at.getType() & at.TYPE_TRANSLATION) != 0)) {
0321 double dx = at.getTranslateX();
0322 double dy = at.getTranslateY();
0323 builder.moveTo(tx += dx, ty += dy);
0324 }
0325 pt.x = locs[n + 2] - locs[n];
0326 pt.y = 0;
0327 if (at != null) {
0328 at.deltaTransform(pt, pt);
0329 }
0330 builder.lineTo(tx += pt.x, ty += pt.y);
0331 }
0332 lp = builder.complete();
0333
0334 if (lp == null) { // empty path
0335 int vi = fComponentVisualOrder == null ? 0
0336 : fComponentVisualOrder[0];
0337 tlc = fComponents[vi];
0338 AffineTransform at = tlc.getBaselineTransform();
0339 if (at != null) {
0340 lp = new EmptyPath(at);
0341 }
0342 }
0343 }
0344 }
0345
0346 public Rectangle getPixelBounds(FontRenderContext frc, float x,
0347 float y) {
0348 Rectangle result = null;
0349
0350 // if we have a matching frc, set it to null so we don't have to test it
0351 // for each component
0352 if (frc != null && frc.equals(this .frc)) {
0353 frc = null;
0354 }
0355
0356 // only cache integral locations with the default frc, this is a bit strict
0357 int ix = (int) Math.floor(x);
0358 int iy = (int) Math.floor(y);
0359 float rx = x - ix;
0360 float ry = y - iy;
0361 boolean canCache = frc == null && rx == 0 && ry == 0;
0362
0363 if (canCache && pixelBounds != null) {
0364 result = new Rectangle(pixelBounds);
0365 result.x += ix;
0366 result.y += iy;
0367 return result;
0368 }
0369
0370 // couldn't use cache, or didn't have it, so compute
0371
0372 if (isSimple) { // all glyphvectors with no decorations, no layout path
0373 for (int i = 0, n = 0; i < fComponents.length; i++, n += 2) {
0374 int vi = fComponentVisualOrder == null ? i
0375 : fComponentVisualOrder[i];
0376 TextLineComponent tlc = fComponents[vi];
0377 Rectangle pb = tlc.getPixelBounds(frc, locs[n] + rx,
0378 locs[n + 1] + ry);
0379 if (!pb.isEmpty()) {
0380 if (result == null) {
0381 result = pb;
0382 } else {
0383 result.add(pb);
0384 }
0385 }
0386 }
0387 if (result == null) {
0388 result = new Rectangle(0, 0, 0, 0);
0389 }
0390 } else { // draw and test
0391 final int MARGIN = 3;
0392 Rectangle2D r2d = getVisualBounds();
0393 if (lp != null) {
0394 r2d = lp.mapShape(r2d).getBounds();
0395 }
0396 Rectangle bounds = r2d.getBounds();
0397 BufferedImage im = new BufferedImage(bounds.width + MARGIN
0398 * 2, bounds.height + MARGIN * 2,
0399 BufferedImage.TYPE_INT_ARGB);
0400
0401 Graphics2D g2d = im.createGraphics();
0402 g2d.setColor(Color.WHITE);
0403 g2d.fillRect(0, 0, im.getWidth(), im.getHeight());
0404
0405 g2d.setColor(Color.BLACK);
0406 draw(g2d, rx + MARGIN - bounds.x, ry + MARGIN - bounds.y);
0407
0408 result = computePixelBounds(im);
0409 result.x -= MARGIN - bounds.x;
0410 result.y -= MARGIN - bounds.y;
0411 }
0412
0413 if (canCache) {
0414 pixelBounds = new Rectangle(result);
0415 }
0416
0417 result.x += ix;
0418 result.y += iy;
0419 return result;
0420 }
0421
0422 static Rectangle computePixelBounds(BufferedImage im) {
0423 int w = im.getWidth();
0424 int h = im.getHeight();
0425
0426 int l = -1, t = -1, r = w, b = h;
0427
0428 {
0429 // get top
0430 int[] buf = new int[w];
0431 loop: while (++t < h) {
0432 im.getRGB(0, t, buf.length, 1, buf, 0, w); // w ignored
0433 for (int i = 0; i < buf.length; i++) {
0434 if (buf[i] != -1) {
0435 break loop;
0436 }
0437 }
0438 }
0439 }
0440
0441 // get bottom
0442 {
0443 int[] buf = new int[w];
0444 loop: while (--b > t) {
0445 im.getRGB(0, b, buf.length, 1, buf, 0, w); // w ignored
0446 for (int i = 0; i < buf.length; ++i) {
0447 if (buf[i] != -1) {
0448 break loop;
0449 }
0450 }
0451 }
0452 ++b;
0453 }
0454
0455 // get left
0456 {
0457 loop: while (++l < r) {
0458 for (int i = t; i < b; ++i) {
0459 int v = im.getRGB(l, i);
0460 if (v != -1) {
0461 break loop;
0462 }
0463 }
0464 }
0465 }
0466
0467 // get right
0468 {
0469 loop: while (--r > l) {
0470 for (int i = t; i < b; ++i) {
0471 int v = im.getRGB(r, i);
0472 if (v != -1) {
0473 break loop;
0474 }
0475 }
0476 }
0477 ++r;
0478 }
0479
0480 return new Rectangle(l, t, r - l, b - t);
0481 }
0482
0483 private abstract static class Function {
0484
0485 abstract float computeFunction(TextLine line,
0486 int componentIndex, int indexInArray);
0487 }
0488
0489 private static Function fgPosAdvF = new Function() {
0490 float computeFunction(TextLine line, int componentIndex,
0491 int indexInArray) {
0492
0493 TextLineComponent tlc = line.fComponents[componentIndex];
0494 int vi = line.fComponentVisualOrder == null ? componentIndex
0495 : line.fComponentVisualOrder[componentIndex];
0496 return line.locs[vi * 2] + tlc.getCharX(indexInArray)
0497 + tlc.getCharAdvance(indexInArray);
0498 }
0499 };
0500
0501 private static Function fgAdvanceF = new Function() {
0502
0503 float computeFunction(TextLine line, int componentIndex,
0504 int indexInArray) {
0505
0506 TextLineComponent tlc = line.fComponents[componentIndex];
0507 return tlc.getCharAdvance(indexInArray);
0508 }
0509 };
0510
0511 private static Function fgXPositionF = new Function() {
0512
0513 float computeFunction(TextLine line, int componentIndex,
0514 int indexInArray) {
0515
0516 int vi = line.fComponentVisualOrder == null ? componentIndex
0517 : line.fComponentVisualOrder[componentIndex];
0518 TextLineComponent tlc = line.fComponents[componentIndex];
0519 return line.locs[vi * 2] + tlc.getCharX(indexInArray);
0520 }
0521 };
0522
0523 private static Function fgYPositionF = new Function() {
0524
0525 float computeFunction(TextLine line, int componentIndex,
0526 int indexInArray) {
0527
0528 TextLineComponent tlc = line.fComponents[componentIndex];
0529 float charPos = tlc.getCharY(indexInArray);
0530
0531 // charPos is relative to the component - adjust for
0532 // baseline
0533
0534 return charPos + line.getComponentShift(componentIndex);
0535 }
0536 };
0537
0538 public int characterCount() {
0539
0540 return fCharsLimit - fCharsStart;
0541 }
0542
0543 public boolean isDirectionLTR() {
0544
0545 return fIsDirectionLTR;
0546 }
0547
0548 public TextLineMetrics getMetrics() {
0549 return fMetrics;
0550 }
0551
0552 public int visualToLogical(int visualIndex) {
0553
0554 if (fCharLogicalOrder == null) {
0555 return visualIndex;
0556 }
0557
0558 if (fCharVisualOrder == null) {
0559 fCharVisualOrder = BidiUtils
0560 .createInverseMap(fCharLogicalOrder);
0561 }
0562
0563 return fCharVisualOrder[visualIndex];
0564 }
0565
0566 public int logicalToVisual(int logicalIndex) {
0567
0568 return (fCharLogicalOrder == null) ? logicalIndex
0569 : fCharLogicalOrder[logicalIndex];
0570 }
0571
0572 public byte getCharLevel(int logicalIndex) {
0573
0574 return fCharLevels == null ? 0 : fCharLevels[logicalIndex];
0575 }
0576
0577 public boolean isCharLTR(int logicalIndex) {
0578
0579 return (getCharLevel(logicalIndex) & 0x1) == 0;
0580 }
0581
0582 public int getCharType(int logicalIndex) {
0583
0584 return Character.getType(fChars[logicalIndex + fCharsStart]);
0585 }
0586
0587 public boolean isCharSpace(int logicalIndex) {
0588
0589 return Character
0590 .isSpaceChar(fChars[logicalIndex + fCharsStart]);
0591 }
0592
0593 public boolean isCharWhitespace(int logicalIndex) {
0594
0595 return Character
0596 .isWhitespace(fChars[logicalIndex + fCharsStart]);
0597 }
0598
0599 public float getCharAngle(int logicalIndex) {
0600
0601 return getCoreMetricsAt(logicalIndex).italicAngle;
0602 }
0603
0604 public CoreMetrics getCoreMetricsAt(int logicalIndex) {
0605
0606 if (logicalIndex < 0) {
0607 throw new IllegalArgumentException("Negative logicalIndex.");
0608 }
0609
0610 if (logicalIndex > fCharsLimit - fCharsStart) {
0611 throw new IllegalArgumentException(
0612 "logicalIndex too large.");
0613 }
0614
0615 int currentTlc = 0;
0616 int tlcStart = 0;
0617 int tlcLimit = 0;
0618
0619 do {
0620 tlcLimit += fComponents[currentTlc].getNumCharacters();
0621 if (tlcLimit > logicalIndex) {
0622 break;
0623 }
0624 ++currentTlc;
0625 tlcStart = tlcLimit;
0626 } while (currentTlc < fComponents.length);
0627
0628 return fComponents[currentTlc].getCoreMetrics();
0629 }
0630
0631 public float getCharAscent(int logicalIndex) {
0632
0633 return getCoreMetricsAt(logicalIndex).ascent;
0634 }
0635
0636 public float getCharDescent(int logicalIndex) {
0637
0638 return getCoreMetricsAt(logicalIndex).descent;
0639 }
0640
0641 public float getCharShift(int logicalIndex) {
0642
0643 return getCoreMetricsAt(logicalIndex).ssOffset;
0644 }
0645
0646 private float applyFunctionAtIndex(int logicalIndex, Function f) {
0647
0648 if (logicalIndex < 0) {
0649 throw new IllegalArgumentException("Negative logicalIndex.");
0650 }
0651
0652 int tlcStart = 0;
0653
0654 for (int i = 0; i < fComponents.length; i++) {
0655
0656 int tlcLimit = tlcStart + fComponents[i].getNumCharacters();
0657 if (tlcLimit > logicalIndex) {
0658 return f.computeFunction(this , i, logicalIndex
0659 - tlcStart);
0660 } else {
0661 tlcStart = tlcLimit;
0662 }
0663 }
0664
0665 throw new IllegalArgumentException("logicalIndex too large.");
0666 }
0667
0668 public float getCharAdvance(int logicalIndex) {
0669
0670 return applyFunctionAtIndex(logicalIndex, fgAdvanceF);
0671 }
0672
0673 public float getCharXPosition(int logicalIndex) {
0674
0675 return applyFunctionAtIndex(logicalIndex, fgXPositionF);
0676 }
0677
0678 public float getCharYPosition(int logicalIndex) {
0679
0680 return applyFunctionAtIndex(logicalIndex, fgYPositionF);
0681 }
0682
0683 public float getCharLinePosition(int logicalIndex) {
0684
0685 return getCharXPosition(logicalIndex);
0686 }
0687
0688 public float getCharLinePosition(int logicalIndex, boolean leading) {
0689 Function f = isCharLTR(logicalIndex) == leading ? fgXPositionF
0690 : fgPosAdvF;
0691 return applyFunctionAtIndex(logicalIndex, f);
0692 }
0693
0694 public boolean caretAtOffsetIsValid(int offset) {
0695
0696 if (offset < 0) {
0697 throw new IllegalArgumentException("Negative offset.");
0698 }
0699
0700 int tlcStart = 0;
0701
0702 for (int i = 0; i < fComponents.length; i++) {
0703
0704 int tlcLimit = tlcStart + fComponents[i].getNumCharacters();
0705 if (tlcLimit > offset) {
0706 return fComponents[i].caretAtOffsetIsValid(offset
0707 - tlcStart);
0708 } else {
0709 tlcStart = tlcLimit;
0710 }
0711 }
0712
0713 throw new IllegalArgumentException("logicalIndex too large.");
0714 }
0715
0716 public Rectangle2D getCharBounds(int logicalIndex) {
0717
0718 if (logicalIndex < 0) {
0719 throw new IllegalArgumentException("Negative logicalIndex.");
0720 }
0721
0722 int tlcStart = 0;
0723
0724 for (int i = 0; i < fComponents.length; i++) {
0725
0726 int tlcLimit = tlcStart + fComponents[i].getNumCharacters();
0727 if (tlcLimit > logicalIndex) {
0728
0729 TextLineComponent tlc = fComponents[i];
0730 int indexInTlc = logicalIndex - tlcStart;
0731 Rectangle2D chBounds = tlc
0732 .getCharVisualBounds(indexInTlc);
0733
0734 int vi = fComponentVisualOrder == null ? i
0735 : fComponentVisualOrder[i];
0736 chBounds.setRect(chBounds.getX() + locs[vi * 2],
0737 chBounds.getY() + locs[vi * 2 + 1], chBounds
0738 .getWidth(), chBounds.getHeight());
0739 return chBounds;
0740 } else {
0741 tlcStart = tlcLimit;
0742 }
0743 }
0744
0745 throw new IllegalArgumentException("logicalIndex too large.");
0746 }
0747
0748 private float getComponentShift(int index) {
0749 CoreMetrics cm = fComponents[index].getCoreMetrics();
0750 return cm.effectiveBaselineOffset(fBaselineOffsets);
0751 }
0752
0753 public void draw(Graphics2D g2, float x, float y) {
0754 if (lp == null) {
0755 for (int i = 0, n = 0; i < fComponents.length; i++, n += 2) {
0756 int vi = fComponentVisualOrder == null ? i
0757 : fComponentVisualOrder[i];
0758 TextLineComponent tlc = fComponents[vi];
0759 tlc.draw(g2, locs[n] + x, locs[n + 1] + y);
0760 }
0761 } else {
0762 AffineTransform oldTx = g2.getTransform();
0763 Point2D.Float pt = new Point2D.Float();
0764 for (int i = 0, n = 0; i < fComponents.length; i++, n += 2) {
0765 int vi = fComponentVisualOrder == null ? i
0766 : fComponentVisualOrder[i];
0767 TextLineComponent tlc = fComponents[vi];
0768 lp.pathToPoint(locs[n], locs[n + 1], false, pt);
0769 pt.x += x;
0770 pt.y += y;
0771 AffineTransform at = tlc.getBaselineTransform();
0772
0773 if (at != null) {
0774 g2.translate(pt.x - at.getTranslateX(), pt.y
0775 - at.getTranslateY());
0776 g2.transform(at);
0777 tlc.draw(g2, 0, 0);
0778 g2.setTransform(oldTx);
0779 } else {
0780 tlc.draw(g2, pt.x, pt.y);
0781 }
0782 }
0783 }
0784 }
0785
0786 /**
0787 * Return the union of the visual bounds of all the components.
0788 * This incorporates the path. It does not include logical
0789 * bounds (used by carets).
0790 */
0791 public Rectangle2D getVisualBounds() {
0792 Rectangle2D result = null;
0793
0794 for (int i = 0, n = 0; i < fComponents.length; i++, n += 2) {
0795 int vi = fComponentVisualOrder == null ? i
0796 : fComponentVisualOrder[i];
0797 TextLineComponent tlc = fComponents[vi];
0798 Rectangle2D r = tlc.getVisualBounds();
0799
0800 Point2D.Float pt = new Point2D.Float(locs[n], locs[n + 1]);
0801 if (lp == null) {
0802 r.setRect(r.getMinX() + pt.x, r.getMinY() + pt.y, r
0803 .getWidth(), r.getHeight());
0804 } else {
0805 lp.pathToPoint(pt, false, pt);
0806
0807 AffineTransform at = tlc.getBaselineTransform();
0808 if (at != null) {
0809 AffineTransform tx = AffineTransform
0810 .getTranslateInstance(pt.x
0811 - at.getTranslateX(), pt.y
0812 - at.getTranslateY());
0813 tx.concatenate(at);
0814 r = tx.createTransformedShape(r).getBounds2D();
0815 } else {
0816 r.setRect(r.getMinX() + pt.x, r.getMinY() + pt.y, r
0817 .getWidth(), r.getHeight());
0818 }
0819 }
0820
0821 if (result == null) {
0822 result = r;
0823 } else {
0824 result.add(r);
0825 }
0826 }
0827
0828 if (result == null) {
0829 result = new Rectangle2D.Float(Float.MAX_VALUE,
0830 Float.MAX_VALUE, Float.MIN_VALUE, Float.MIN_VALUE);
0831 }
0832
0833 return result;
0834 }
0835
0836 public Rectangle2D getItalicBounds() {
0837
0838 float left = Float.MAX_VALUE, right = -Float.MAX_VALUE;
0839 float top = Float.MAX_VALUE, bottom = -Float.MAX_VALUE;
0840
0841 for (int i = 0, n = 0; i < fComponents.length; i++, n += 2) {
0842 int vi = fComponentVisualOrder == null ? i
0843 : fComponentVisualOrder[i];
0844 TextLineComponent tlc = fComponents[vi];
0845
0846 Rectangle2D tlcBounds = tlc.getItalicBounds();
0847 float x = locs[n];
0848 float y = locs[n + 1];
0849
0850 left = Math.min(left, x + (float) tlcBounds.getX());
0851 right = Math.max(right, x + (float) tlcBounds.getMaxX());
0852
0853 top = Math.min(top, y + (float) tlcBounds.getY());
0854 bottom = Math.max(bottom, y + (float) tlcBounds.getMaxY());
0855 }
0856
0857 return new Rectangle2D.Float(left, top, right - left, bottom
0858 - top);
0859 }
0860
0861 public Shape getOutline(AffineTransform tx) {
0862
0863 GeneralPath dstShape = new GeneralPath(
0864 GeneralPath.WIND_NON_ZERO);
0865
0866 for (int i = 0, n = 0; i < fComponents.length; i++, n += 2) {
0867 int vi = fComponentVisualOrder == null ? i
0868 : fComponentVisualOrder[i];
0869 TextLineComponent tlc = fComponents[vi];
0870
0871 dstShape
0872 .append(tlc.getOutline(locs[n], locs[n + 1]), false);
0873 }
0874
0875 if (tx != null) {
0876 dstShape.transform(tx);
0877 }
0878 return dstShape;
0879 }
0880
0881 public int hashCode() {
0882 return (fComponents.length << 16)
0883 ^ (fComponents[0].hashCode() << 3)
0884 ^ (fCharsLimit - fCharsStart);
0885 }
0886
0887 public String toString() {
0888 StringBuilder buf = new StringBuilder();
0889
0890 for (int i = 0; i < fComponents.length; i++) {
0891 buf.append(fComponents[i]);
0892 }
0893
0894 return buf.toString();
0895 }
0896
0897 /**
0898 * Create a TextLine from the text. The Font must be able to
0899 * display all of the text.
0900 * attributes==null is equivalent to using an empty Map for
0901 * attributes
0902 */
0903 public static TextLine fastCreateTextLine(FontRenderContext frc,
0904 char[] chars, Font font, CoreMetrics lm, Map attributes) {
0905
0906 boolean isDirectionLTR = true;
0907 byte[] levels = null;
0908 int[] charsLtoV = null;
0909 Bidi bidi = null;
0910 int characterCount = chars.length;
0911
0912 boolean requiresBidi = false;
0913 byte[] embs = null;
0914
0915 AttributeValues values = null;
0916 if (attributes != null) {
0917 values = AttributeValues.fromMap(attributes);
0918 if (values.getRunDirection() >= 0) {
0919 isDirectionLTR = values.getRunDirection() == 0;
0920 requiresBidi = !isDirectionLTR;
0921 }
0922 if (values.getBidiEmbedding() != 0) {
0923 requiresBidi = true;
0924 byte level = (byte) values.getBidiEmbedding();
0925 embs = new byte[characterCount];
0926 for (int i = 0; i < embs.length; ++i) {
0927 embs[i] = level;
0928 }
0929 }
0930 }
0931
0932 // dlf: get baseRot from font for now???
0933
0934 if (!requiresBidi) {
0935 requiresBidi = Bidi.requiresBidi(chars, 0, chars.length);
0936 }
0937
0938 if (requiresBidi) {
0939 int bidiflags = values == null ? Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT
0940 : values.getRunDirection();
0941
0942 bidi = new Bidi(chars, 0, embs, 0, chars.length, bidiflags);
0943 if (!bidi.isLeftToRight()) {
0944 levels = BidiUtils.getLevels(bidi);
0945 int[] charsVtoL = BidiUtils
0946 .createVisualToLogicalMap(levels);
0947 charsLtoV = BidiUtils.createInverseMap(charsVtoL);
0948 isDirectionLTR = bidi.baseIsLeftToRight();
0949 }
0950 }
0951
0952 Decoration decorator = Decoration.getDecoration(values);
0953
0954 int layoutFlags = 0; // no extra info yet, bidi determines run and line direction
0955 TextLabelFactory factory = new TextLabelFactory(frc, chars,
0956 bidi, layoutFlags);
0957
0958 TextLineComponent[] components = new TextLineComponent[1];
0959
0960 components = createComponentsOnRun(0, chars.length, chars,
0961 charsLtoV, levels, factory, font, lm, frc, decorator,
0962 components, 0);
0963
0964 int numComponents = components.length;
0965 while (components[numComponents - 1] == null) {
0966 numComponents -= 1;
0967 }
0968
0969 if (numComponents != components.length) {
0970 TextLineComponent[] temp = new TextLineComponent[numComponents];
0971 System.arraycopy(components, 0, temp, 0, numComponents);
0972 components = temp;
0973 }
0974
0975 return new TextLine(frc, components, lm.baselineOffsets, chars,
0976 0, chars.length, charsLtoV, levels, isDirectionLTR);
0977 }
0978
0979 private static TextLineComponent[] expandArray(
0980 TextLineComponent[] orig) {
0981
0982 TextLineComponent[] newComponents = new TextLineComponent[orig.length + 8];
0983 System.arraycopy(orig, 0, newComponents, 0, orig.length);
0984
0985 return newComponents;
0986 }
0987
0988 /**
0989 * Returns an array in logical order of the TextLineComponents on
0990 * the text in the given range, with the given attributes.
0991 */
0992 public static TextLineComponent[] createComponentsOnRun(
0993 int runStart, int runLimit, char[] chars, int[] charsLtoV,
0994 byte[] levels, TextLabelFactory factory, Font font,
0995 CoreMetrics cm, FontRenderContext frc,
0996 Decoration decorator, TextLineComponent[] components,
0997 int numComponents) {
0998
0999 int pos = runStart;
1000 do {
1001 int chunkLimit = firstVisualChunk(charsLtoV, levels, pos,
1002 runLimit); // <= displayLimit
1003
1004 do {
1005 int startPos = pos;
1006 int lmCount;
1007
1008 if (cm == null) {
1009 LineMetrics lineMetrics = font.getLineMetrics(
1010 chars, startPos, chunkLimit, frc);
1011 cm = CoreMetrics.get(lineMetrics);
1012 lmCount = lineMetrics.getNumChars();
1013 } else {
1014 lmCount = (chunkLimit - startPos);
1015 }
1016
1017 TextLineComponent nextComponent = factory
1018 .createExtended(font, cm, decorator, startPos,
1019 startPos + lmCount);
1020
1021 ++numComponents;
1022 if (numComponents >= components.length) {
1023 components = expandArray(components);
1024 }
1025
1026 components[numComponents - 1] = nextComponent;
1027
1028 pos += lmCount;
1029 } while (pos < chunkLimit);
1030
1031 } while (pos < runLimit);
1032
1033 return components;
1034 }
1035
1036 /**
1037 * Returns an array (in logical order) of the TextLineComponents representing
1038 * the text. The components are both logically and visually contiguous.
1039 */
1040 public static TextLineComponent[] getComponents(
1041 StyledParagraph styledParagraph, char[] chars,
1042 int textStart, int textLimit, int[] charsLtoV,
1043 byte[] levels, TextLabelFactory factory) {
1044
1045 FontRenderContext frc = factory.getFontRenderContext();
1046
1047 int numComponents = 0;
1048 TextLineComponent[] tempComponents = new TextLineComponent[1];
1049
1050 int pos = textStart;
1051 do {
1052 int runLimit = Math.min(styledParagraph.getRunLimit(pos),
1053 textLimit);
1054
1055 Decoration decorator = styledParagraph.getDecorationAt(pos);
1056
1057 Object graphicOrFont = styledParagraph
1058 .getFontOrGraphicAt(pos);
1059
1060 if (graphicOrFont instanceof GraphicAttribute) {
1061 // AffineTransform baseRot = styledParagraph.getBaselineRotationAt(pos);
1062 // !!! For now, let's assign runs of text with both fonts and graphic attributes
1063 // a null rotation (e.g. the baseline rotation goes away when a graphic
1064 // is applied.
1065 AffineTransform baseRot = null;
1066 GraphicAttribute graphicAttribute = (GraphicAttribute) graphicOrFont;
1067 do {
1068 int chunkLimit = firstVisualChunk(charsLtoV,
1069 levels, pos, runLimit);
1070
1071 GraphicComponent nextGraphic = new GraphicComponent(
1072 graphicAttribute, decorator, charsLtoV,
1073 levels, pos, chunkLimit, baseRot);
1074 pos = chunkLimit;
1075
1076 ++numComponents;
1077 if (numComponents >= tempComponents.length) {
1078 tempComponents = expandArray(tempComponents);
1079 }
1080
1081 tempComponents[numComponents - 1] = nextGraphic;
1082
1083 } while (pos < runLimit);
1084 } else {
1085 Font font = (Font) graphicOrFont;
1086
1087 tempComponents = createComponentsOnRun(pos, runLimit,
1088 chars, charsLtoV, levels, factory, font, null,
1089 frc, decorator, tempComponents, numComponents);
1090 pos = runLimit;
1091 numComponents = tempComponents.length;
1092 while (tempComponents[numComponents - 1] == null) {
1093 numComponents -= 1;
1094 }
1095 }
1096
1097 } while (pos < textLimit);
1098
1099 TextLineComponent[] components;
1100 if (tempComponents.length == numComponents) {
1101 components = tempComponents;
1102 } else {
1103 components = new TextLineComponent[numComponents];
1104 System.arraycopy(tempComponents, 0, components, 0,
1105 numComponents);
1106 }
1107
1108 return components;
1109 }
1110
1111 /**
1112 * Create a TextLine from the Font and character data over the
1113 * range. The range is relative to both the StyledParagraph and the
1114 * character array.
1115 */
1116 public static TextLine createLineFromText(char[] chars,
1117 StyledParagraph styledParagraph, TextLabelFactory factory,
1118 boolean isDirectionLTR, float[] baselineOffsets) {
1119
1120 factory.setLineContext(0, chars.length);
1121
1122 Bidi lineBidi = factory.getLineBidi();
1123 int[] charsLtoV = null;
1124 byte[] levels = null;
1125
1126 if (lineBidi != null) {
1127 levels = BidiUtils.getLevels(lineBidi);
1128 int[] charsVtoL = BidiUtils
1129 .createVisualToLogicalMap(levels);
1130 charsLtoV = BidiUtils.createInverseMap(charsVtoL);
1131 }
1132
1133 TextLineComponent[] components = getComponents(styledParagraph,
1134 chars, 0, chars.length, charsLtoV, levels, factory);
1135
1136 return new TextLine(factory.getFontRenderContext(), components,
1137 baselineOffsets, chars, 0, chars.length, charsLtoV,
1138 levels, isDirectionLTR);
1139 }
1140
1141 /**
1142 * Compute the components order from the given components array and
1143 * logical-to-visual character mapping. May return null if canonical.
1144 */
1145 private static int[] computeComponentOrder(
1146 TextLineComponent[] components, int[] charsLtoV) {
1147
1148 /*
1149 * Create a visual ordering for the glyph sets. The important thing
1150 * here is that the values have the proper rank with respect to
1151 * each other, not the exact values. For example, the first glyph
1152 * set that appears visually should have the lowest value. The last
1153 * should have the highest value. The values are then normalized
1154 * to map 1-1 with positions in glyphs.
1155 *
1156 */
1157 int[] componentOrder = null;
1158 if (charsLtoV != null && components.length > 1) {
1159 componentOrder = new int[components.length];
1160 int gStart = 0;
1161 for (int i = 0; i < components.length; i++) {
1162 componentOrder[i] = charsLtoV[gStart];
1163 gStart += components[i].getNumCharacters();
1164 }
1165
1166 componentOrder = BidiUtils
1167 .createContiguousOrder(componentOrder);
1168 componentOrder = BidiUtils.createInverseMap(componentOrder);
1169 }
1170 return componentOrder;
1171 }
1172
1173 /**
1174 * Create a TextLine from the text. chars is just the text in the iterator.
1175 */
1176 public static TextLine standardCreateTextLine(
1177 FontRenderContext frc, AttributedCharacterIterator text,
1178 char[] chars, float[] baselineOffsets) {
1179
1180 StyledParagraph styledParagraph = new StyledParagraph(text,
1181 chars);
1182 Bidi bidi = new Bidi(text);
1183 if (bidi.isLeftToRight()) {
1184 bidi = null;
1185 }
1186 int layoutFlags = 0; // no extra info yet, bidi determines run and line direction
1187 TextLabelFactory factory = new TextLabelFactory(frc, chars,
1188 bidi, layoutFlags);
1189
1190 boolean isDirectionLTR = true;
1191 if (bidi != null) {
1192 isDirectionLTR = bidi.baseIsLeftToRight();
1193 }
1194 return createLineFromText(chars, styledParagraph, factory,
1195 isDirectionLTR, baselineOffsets);
1196 }
1197
1198 /*
1199 * A utility to get a range of text that is both logically and visually
1200 * contiguous.
1201 * If the entire range is ok, return limit, otherwise return the first
1202 * directional change after start. We could do better than this, but
1203 * it doesn't seem worth it at the moment.
1204 private static int firstVisualChunk(int order[], byte direction[],
1205 int start, int limit)
1206 {
1207 if (order != null) {
1208 int min = order[start];
1209 int max = order[start];
1210 int count = limit - start;
1211 for (int i = start + 1; i < limit; i++) {
1212 min = Math.min(min, order[i]);
1213 max = Math.max(max, order[i]);
1214 if (max - min >= count) {
1215 if (direction != null) {
1216 byte baseLevel = direction[start];
1217 for (int j = start + 1; j < i; j++) {
1218 if (direction[j] != baseLevel) {
1219 return j;
1220 }
1221 }
1222 }
1223 return i;
1224 }
1225 }
1226 }
1227 return limit;
1228 }
1229 */
1230
1231 /**
1232 * When this returns, the ACI's current position will be at the start of the
1233 * first run which does NOT contain a GraphicAttribute. If no such run exists
1234 * the ACI's position will be at the end, and this method will return false.
1235 */
1236 static boolean advanceToFirstFont(AttributedCharacterIterator aci) {
1237
1238 for (char ch = aci.first(); ch != aci.DONE; ch = aci
1239 .setIndex(aci.getRunLimit())) {
1240
1241 if (aci.getAttribute(TextAttribute.CHAR_REPLACEMENT) == null) {
1242 return true;
1243 }
1244 }
1245
1246 return false;
1247 }
1248
1249 static float[] getNormalizedOffsets(float[] baselineOffsets,
1250 byte baseline) {
1251
1252 if (baselineOffsets[baseline] != 0) {
1253 float base = baselineOffsets[baseline];
1254 float[] temp = new float[baselineOffsets.length];
1255 for (int i = 0; i < temp.length; i++)
1256 temp[i] = baselineOffsets[i] - base;
1257 baselineOffsets = temp;
1258 }
1259 return baselineOffsets;
1260 }
1261
1262 static Font getFontAtCurrentPos(AttributedCharacterIterator aci) {
1263
1264 Object value = aci.getAttribute(TextAttribute.FONT);
1265 if (value != null) {
1266 return (Font) value;
1267 }
1268 if (aci.getAttribute(TextAttribute.FAMILY) != null) {
1269 return Font.getFont(aci.getAttributes());
1270 }
1271
1272 int ch = CodePointIterator.create(aci).next();
1273 if (ch != CodePointIterator.DONE) {
1274 FontResolver resolver = FontResolver.getInstance();
1275 return resolver.getFont(resolver.getFontIndex(ch), aci
1276 .getAttributes());
1277 }
1278 return null;
1279 }
1280
1281 /*
1282 * The new version requires that chunks be at the same level.
1283 */
1284 private static int firstVisualChunk(int order[], byte direction[],
1285 int start, int limit) {
1286 if (order != null && direction != null) {
1287 byte dir = direction[start];
1288 while (++start < limit && direction[start] == dir) {
1289 }
1290 return start;
1291 }
1292 return limit;
1293 }
1294
1295 /*
1296 * create a new line with characters between charStart and charLimit
1297 * justified using the provided width and ratio.
1298 */
1299 public TextLine getJustifiedLine(float justificationWidth,
1300 float justifyRatio, int justStart, int justLimit) {
1301
1302 TextLineComponent[] newComponents = new TextLineComponent[fComponents.length];
1303 System.arraycopy(fComponents, 0, newComponents, 0,
1304 fComponents.length);
1305
1306 float leftHang = 0;
1307 float adv = 0;
1308 float justifyDelta = 0;
1309 boolean rejustify = false;
1310 do {
1311 adv = getAdvanceBetween(newComponents, 0, characterCount());
1312
1313 // all characters outside the justification range must be in the base direction
1314 // of the layout, otherwise justification makes no sense.
1315
1316 float justifyAdvance = getAdvanceBetween(newComponents,
1317 justStart, justLimit);
1318
1319 // get the actual justification delta
1320 justifyDelta = (justificationWidth - justifyAdvance)
1321 * justifyRatio;
1322
1323 // generate an array of GlyphJustificationInfo records to pass to
1324 // the justifier. Array is visually ordered.
1325
1326 // get positions that each component will be using
1327 int[] infoPositions = new int[newComponents.length];
1328 int infoCount = 0;
1329 for (int visIndex = 0; visIndex < newComponents.length; visIndex++) {
1330 int logIndex = fComponentVisualOrder == null ? visIndex
1331 : fComponentVisualOrder[visIndex];
1332 infoPositions[logIndex] = infoCount;
1333 infoCount += newComponents[logIndex]
1334 .getNumJustificationInfos();
1335 }
1336 GlyphJustificationInfo[] infos = new GlyphJustificationInfo[infoCount];
1337
1338 // get justification infos
1339 int compStart = 0;
1340 for (int i = 0; i < newComponents.length; i++) {
1341 TextLineComponent comp = newComponents[i];
1342 int compLength = comp.getNumCharacters();
1343 int compLimit = compStart + compLength;
1344 if (compLimit > justStart) {
1345 int rangeMin = Math.max(0, justStart - compStart);
1346 int rangeMax = Math.min(compLength, justLimit
1347 - compStart);
1348 comp.getJustificationInfos(infos, infoPositions[i],
1349 rangeMin, rangeMax);
1350
1351 if (compLimit >= justLimit) {
1352 break;
1353 }
1354 }
1355 }
1356
1357 // records are visually ordered, and contiguous, so start and end are
1358 // simply the places where we didn't fetch records
1359 int infoStart = 0;
1360 int infoLimit = infoCount;
1361 while (infoStart < infoLimit && infos[infoStart] == null) {
1362 ++infoStart;
1363 }
1364
1365 while (infoLimit > infoStart
1366 && infos[infoLimit - 1] == null) {
1367 --infoLimit;
1368 }
1369
1370 // invoke justifier on the records
1371 TextJustifier justifier = new TextJustifier(infos,
1372 infoStart, infoLimit);
1373
1374 float[] deltas = justifier.justify(justifyDelta);
1375
1376 boolean canRejustify = rejustify == false;
1377 boolean wantRejustify = false;
1378 boolean[] flags = new boolean[1];
1379
1380 // apply justification deltas
1381 compStart = 0;
1382 for (int i = 0; i < newComponents.length; i++) {
1383 TextLineComponent comp = newComponents[i];
1384 int compLength = comp.getNumCharacters();
1385 int compLimit = compStart + compLength;
1386 if (compLimit > justStart) {
1387 int rangeMin = Math.max(0, justStart - compStart);
1388 int rangeMax = Math.min(compLength, justLimit
1389 - compStart);
1390 newComponents[i] = comp.applyJustificationDeltas(
1391 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,
1405 fChars, fCharsStart, fCharsLimit, fCharLogicalOrder,
1406 fCharLevels, fIsDirectionLTR);
1407 }
1408
1409 // return the sum of the advances of text between the logical start and limit
1410 public static float getAdvanceBetween(
1411 TextLineComponent[] components, int start, int limit) {
1412 float advance = 0;
1413
1414 int tlcStart = 0;
1415 for (int i = 0; i < components.length; i++) {
1416 TextLineComponent comp = components[i];
1417
1418 int tlcLength = comp.getNumCharacters();
1419 int tlcLimit = tlcStart + tlcLength;
1420 if (tlcLimit > start) {
1421 int measureStart = Math.max(0, start - tlcStart);
1422 int measureLimit = Math
1423 .min(tlcLength, limit - tlcStart);
1424 advance += comp.getAdvanceBetween(measureStart,
1425 measureLimit);
1426 if (tlcLimit >= limit) {
1427 break;
1428 }
1429 }
1430
1431 tlcStart = tlcLimit;
1432 }
1433
1434 return advance;
1435 }
1436
1437 LayoutPathImpl getLayoutPath() {
1438 return lp;
1439 }
1440 }
|