StaticLayout.java revision c982f60e982c1d2df9f115ed9a5c3ef3643d0892
1/* 2 * Copyright (C) 2006 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.text; 18 19import com.android.internal.util.ArrayUtils; 20 21import android.graphics.Bitmap; 22import android.graphics.Paint; 23import android.text.style.LeadingMarginSpan; 24import android.text.style.LineHeightSpan; 25import android.text.style.MetricAffectingSpan; 26import android.text.style.TabStopSpan; 27import android.text.style.LeadingMarginSpan.LeadingMarginSpan2; 28 29/** 30 * StaticLayout is a Layout for text that will not be edited after it 31 * is laid out. Use {@link DynamicLayout} for text that may change. 32 * <p>This is used by widgets to control text layout. You should not need 33 * to use this class directly unless you are implementing your own widget 34 * or custom display object, or would be tempted to call 35 * {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int, 36 * float, float, android.graphics.Paint) 37 * Canvas.drawText()} directly.</p> 38 */ 39public class 40StaticLayout 41extends Layout 42{ 43 public StaticLayout(CharSequence source, TextPaint paint, 44 int width, 45 Alignment align, float spacingmult, float spacingadd, 46 boolean includepad) { 47 this(source, 0, source.length(), paint, width, align, 48 spacingmult, spacingadd, includepad); 49 } 50 51 public StaticLayout(CharSequence source, int bufstart, int bufend, 52 TextPaint paint, int outerwidth, 53 Alignment align, 54 float spacingmult, float spacingadd, 55 boolean includepad) { 56 this(source, bufstart, bufend, paint, outerwidth, align, 57 spacingmult, spacingadd, includepad, null, 0); 58 } 59 60 public StaticLayout(CharSequence source, int bufstart, int bufend, 61 TextPaint paint, int outerwidth, 62 Alignment align, 63 float spacingmult, float spacingadd, 64 boolean includepad, 65 TextUtils.TruncateAt ellipsize, int ellipsizedWidth) { 66 super((ellipsize == null) 67 ? source 68 : (source instanceof Spanned) 69 ? new SpannedEllipsizer(source) 70 : new Ellipsizer(source), 71 paint, outerwidth, align, spacingmult, spacingadd); 72 73 /* 74 * This is annoying, but we can't refer to the layout until 75 * superclass construction is finished, and the superclass 76 * constructor wants the reference to the display text. 77 * 78 * This will break if the superclass constructor ever actually 79 * cares about the content instead of just holding the reference. 80 */ 81 if (ellipsize != null) { 82 Ellipsizer e = (Ellipsizer) getText(); 83 84 e.mLayout = this; 85 e.mWidth = ellipsizedWidth; 86 e.mMethod = ellipsize; 87 mEllipsizedWidth = ellipsizedWidth; 88 89 mColumns = COLUMNS_ELLIPSIZE; 90 } else { 91 mColumns = COLUMNS_NORMAL; 92 mEllipsizedWidth = outerwidth; 93 } 94 95 mLines = new int[ArrayUtils.idealIntArraySize(2 * mColumns)]; 96 mLineDirections = new Directions[ 97 ArrayUtils.idealIntArraySize(2 * mColumns)]; 98 99 mMeasured = MeasuredText.obtain(); 100 101 generate(source, bufstart, bufend, paint, outerwidth, align, 102 spacingmult, spacingadd, includepad, includepad, 103 ellipsize != null, ellipsizedWidth, ellipsize); 104 105 mMeasured = MeasuredText.recycle(mMeasured); 106 mFontMetricsInt = null; 107 } 108 109 /* package */ StaticLayout(boolean ellipsize) { 110 super(null, null, 0, null, 0, 0); 111 112 mColumns = COLUMNS_ELLIPSIZE; 113 mLines = new int[ArrayUtils.idealIntArraySize(2 * mColumns)]; 114 mLineDirections = new Directions[ 115 ArrayUtils.idealIntArraySize(2 * mColumns)]; 116 mMeasured = MeasuredText.obtain(); 117 } 118 119 /* package */ void generate(CharSequence source, int bufstart, int bufend, 120 TextPaint paint, int outerwidth, 121 Alignment align, 122 float spacingmult, float spacingadd, 123 boolean includepad, boolean trackpad, 124 boolean breakOnlyAtSpaces, 125 float ellipsizedWidth, TextUtils.TruncateAt where) { 126 mLineCount = 0; 127 128 int v = 0; 129 boolean needMultiply = (spacingmult != 1 || spacingadd != 0); 130 131 Paint.FontMetricsInt fm = mFontMetricsInt; 132 int[] choosehtv = null; 133 134 MeasuredText measured = mMeasured; 135 136 Spanned spanned = null; 137 if (source instanceof Spanned) 138 spanned = (Spanned) source; 139 140 int DEFAULT_DIR = DIR_LEFT_TO_RIGHT; // XXX 141 142 int paraEnd; 143 for (int paraStart = bufstart; paraStart <= bufend; paraStart = paraEnd) { 144 paraEnd = TextUtils.indexOf(source, '\n', paraStart, bufend); 145 if (paraEnd < 0) 146 paraEnd = bufend; 147 else 148 paraEnd++; 149 int paraLen = paraEnd - paraStart; 150 151 int firstWidthLineLimit = mLineCount + 1; 152 int firstwidth = outerwidth; 153 int restwidth = outerwidth; 154 155 LineHeightSpan[] chooseht = null; 156 157 if (spanned != null) { 158 LeadingMarginSpan[] sp = spanned.getSpans(paraStart, paraEnd, 159 LeadingMarginSpan.class); 160 for (int i = 0; i < sp.length; i++) { 161 LeadingMarginSpan lms = sp[i]; 162 firstwidth -= sp[i].getLeadingMargin(true); 163 restwidth -= sp[i].getLeadingMargin(false); 164 165 // LeadingMarginSpan2 is odd. The count affects all 166 // leading margin spans, not just this particular one, 167 // and start from the top of the span, not the top of the 168 // paragraph. 169 if (lms instanceof LeadingMarginSpan2) { 170 LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms; 171 int lmsFirstLine = getLineForOffset(spanned.getSpanStart(lms2)); 172 firstWidthLineLimit = lmsFirstLine + 173 lms2.getLeadingMarginLineCount(); 174 } 175 } 176 177 chooseht = spanned.getSpans(paraStart, paraEnd, LineHeightSpan.class); 178 179 if (chooseht.length != 0) { 180 if (choosehtv == null || 181 choosehtv.length < chooseht.length) { 182 choosehtv = new int[ArrayUtils.idealIntArraySize( 183 chooseht.length)]; 184 } 185 186 for (int i = 0; i < chooseht.length; i++) { 187 int o = spanned.getSpanStart(chooseht[i]); 188 189 if (o < paraStart) { 190 // starts in this layout, before the 191 // current paragraph 192 193 choosehtv[i] = getLineTop(getLineForOffset(o)); 194 } else { 195 // starts in this paragraph 196 197 choosehtv[i] = v; 198 } 199 } 200 } 201 } 202 203 measured.setPara(source, paraStart, paraEnd, DIR_REQUEST_DEFAULT_LTR); 204 char[] chs = measured.mChars; 205 float[] widths = measured.mWidths; 206 byte[] chdirs = measured.mLevels; 207 int dir = measured.mDir; 208 boolean easy = measured.mEasy; 209 210 CharSequence sub = source; 211 212 int width = firstwidth; 213 214 float w = 0; 215 int here = paraStart; 216 217 int ok = paraStart; 218 float okwidth = w; 219 int okascent = 0, okdescent = 0, oktop = 0, okbottom = 0; 220 221 int fit = paraStart; 222 float fitwidth = w; 223 int fitascent = 0, fitdescent = 0, fittop = 0, fitbottom = 0; 224 225 boolean hasTabOrEmoji = false; 226 boolean hasTab = false; 227 TabStops tabStops = null; 228 229 for (int spanStart = paraStart, spanEnd = spanStart, nextSpanStart; 230 spanStart < paraEnd; spanStart = nextSpanStart) { 231 232 if (spanStart == spanEnd) { 233 if (spanned == null) 234 spanEnd = paraEnd; 235 else 236 spanEnd = spanned.nextSpanTransition(spanStart, paraEnd, 237 MetricAffectingSpan.class); 238 239 int spanLen = spanEnd - spanStart; 240 if (spanned == null) { 241 measured.addStyleRun(paint, spanLen, fm); 242 } else { 243 MetricAffectingSpan[] spans = 244 spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class); 245 measured.addStyleRun(paint, spans, spanLen, fm); 246 } 247 } 248 249 nextSpanStart = spanEnd; 250 int startInPara = spanStart - paraStart; 251 int endInPara = spanEnd - paraStart; 252 253 int fmtop = fm.top; 254 int fmbottom = fm.bottom; 255 int fmascent = fm.ascent; 256 int fmdescent = fm.descent; 257 258 for (int j = spanStart; j < spanEnd; j++) { 259 char c = chs[j - paraStart]; 260 float before = w; 261 262 if (c == '\n') { 263 ; 264 } else if (c == '\t') { 265 if (hasTab == false) { 266 hasTab = true; 267 hasTabOrEmoji = true; 268 // First tab this para, check for tabstops 269 TabStopSpan[] spans = spanned.getSpans(paraStart, 270 paraEnd, TabStopSpan.class); 271 if (spans.length > 0) { 272 tabStops = new TabStops(TAB_INCREMENT, spans); 273 } 274 } 275 if (tabStops != null) { 276 w = tabStops.nextTab(w); 277 } else { 278 w = TabStops.nextDefaultStop(w, TAB_INCREMENT); 279 } 280 } else if (c >= 0xD800 && c <= 0xDFFF && j + 1 < spanEnd) { 281 int emoji = Character.codePointAt(chs, j - paraStart); 282 283 if (emoji >= MIN_EMOJI && emoji <= MAX_EMOJI) { 284 Bitmap bm = EMOJI_FACTORY. 285 getBitmapFromAndroidPua(emoji); 286 287 if (bm != null) { 288 Paint whichPaint; 289 290 if (spanned == null) { 291 whichPaint = paint; 292 } else { 293 whichPaint = mWorkPaint; 294 } 295 296 float wid = bm.getWidth() * 297 -whichPaint.ascent() / 298 bm.getHeight(); 299 300 w += wid; 301 hasTabOrEmoji = true; 302 j++; 303 } else { 304 w += widths[j - paraStart]; 305 } 306 } else { 307 w += widths[j - paraStart]; 308 } 309 } else { 310 w += widths[j - paraStart]; 311 } 312 313 // Log.e("text", "was " + before + " now " + w + " after " + c + " within " + width); 314 315 if (w <= width) { 316 fitwidth = w; 317 fit = j + 1; 318 319 if (fmtop < fittop) 320 fittop = fmtop; 321 if (fmascent < fitascent) 322 fitascent = fmascent; 323 if (fmdescent > fitdescent) 324 fitdescent = fmdescent; 325 if (fmbottom > fitbottom) 326 fitbottom = fmbottom; 327 328 /* 329 * From the Unicode Line Breaking Algorithm: 330 * (at least approximately) 331 * 332 * .,:; are class IS: breakpoints 333 * except when adjacent to digits 334 * / is class SY: a breakpoint 335 * except when followed by a digit. 336 * - is class HY: a breakpoint 337 * except when followed by a digit. 338 * 339 * Ideographs are class ID: breakpoints when adjacent, 340 * except for NS (non-starters), which can be broken 341 * after but not before. 342 */ 343 344 if (c == ' ' || c == '\t' || 345 ((c == '.' || c == ',' || c == ':' || c == ';') && 346 (j - 1 < here || !Character.isDigit(chs[j - 1 - paraStart])) && 347 (j + 1 >= spanEnd || !Character.isDigit(chs[j + 1 - paraStart]))) || 348 ((c == '/' || c == '-') && 349 (j + 1 >= spanEnd || !Character.isDigit(chs[j + 1 - paraStart]))) || 350 (c >= FIRST_CJK && isIdeographic(c, true) && 351 j + 1 < spanEnd && isIdeographic(chs[j + 1 - paraStart], false))) { 352 okwidth = w; 353 ok = j + 1; 354 355 if (fittop < oktop) 356 oktop = fittop; 357 if (fitascent < okascent) 358 okascent = fitascent; 359 if (fitdescent > okdescent) 360 okdescent = fitdescent; 361 if (fitbottom > okbottom) 362 okbottom = fitbottom; 363 } 364 } else if (breakOnlyAtSpaces) { 365 if (ok != here) { 366 // Log.e("text", "output ok " + here + " to " +ok); 367 368 while (ok < spanEnd && chs[ok - paraStart] == ' ') { 369 ok++; 370 } 371 372 v = out(source, 373 here, ok, 374 okascent, okdescent, oktop, okbottom, 375 v, 376 spacingmult, spacingadd, chooseht, 377 choosehtv, fm, hasTabOrEmoji, 378 needMultiply, paraStart, chdirs, dir, easy, 379 ok == bufend, includepad, trackpad, 380 chs, widths, here - paraStart, 381 where, ellipsizedWidth, okwidth, 382 paint); 383 384 here = ok; 385 } else { 386 // Act like it fit even though it didn't. 387 388 fitwidth = w; 389 fit = j + 1; 390 391 if (fmtop < fittop) 392 fittop = fmtop; 393 if (fmascent < fitascent) 394 fitascent = fmascent; 395 if (fmdescent > fitdescent) 396 fitdescent = fmdescent; 397 if (fmbottom > fitbottom) 398 fitbottom = fmbottom; 399 } 400 } else { 401 if (ok != here) { 402 // Log.e("text", "output ok " + here + " to " +ok); 403 404 while (ok < spanEnd && chs[ok - paraStart] == ' ') { 405 ok++; 406 } 407 408 v = out(source, 409 here, ok, 410 okascent, okdescent, oktop, okbottom, 411 v, 412 spacingmult, spacingadd, chooseht, 413 choosehtv, fm, hasTabOrEmoji, 414 needMultiply, paraStart, chdirs, dir, easy, 415 ok == bufend, includepad, trackpad, 416 chs, widths, here - paraStart, 417 where, ellipsizedWidth, okwidth, 418 paint); 419 420 here = ok; 421 } else if (fit != here) { 422 // Log.e("text", "output fit " + here + " to " +fit); 423 v = out(source, 424 here, fit, 425 fitascent, fitdescent, 426 fittop, fitbottom, 427 v, 428 spacingmult, spacingadd, chooseht, 429 choosehtv, fm, hasTabOrEmoji, 430 needMultiply, paraStart, chdirs, dir, easy, 431 fit == bufend, includepad, trackpad, 432 chs, widths, here - paraStart, 433 where, ellipsizedWidth, fitwidth, 434 paint); 435 436 here = fit; 437 } else { 438 // Log.e("text", "output one " + here + " to " +(here + 1)); 439 // XXX not sure why the existing fm wasn't ok. 440 // measureText(paint, mWorkPaint, 441 // source, here, here + 1, fm, tab, 442 // null); 443 444 v = out(source, 445 here, here+1, 446 fm.ascent, fm.descent, 447 fm.top, fm.bottom, 448 v, 449 spacingmult, spacingadd, chooseht, 450 choosehtv, fm, hasTabOrEmoji, 451 needMultiply, paraStart, chdirs, dir, easy, 452 here + 1 == bufend, includepad, 453 trackpad, 454 chs, widths, here - paraStart, 455 where, ellipsizedWidth, 456 widths[here - paraStart], paint); 457 458 here = here + 1; 459 } 460 461 if (here < spanStart) { 462 // didn't output all the text for this span 463 // we've measured the raw widths, though, so 464 // just reset the start point 465 j = nextSpanStart = here; 466 } else { 467 j = here - 1; // continue looping 468 } 469 470 ok = fit = here; 471 w = 0; 472 fitascent = fitdescent = fittop = fitbottom = 0; 473 okascent = okdescent = oktop = okbottom = 0; 474 475 if (--firstWidthLineLimit <= 0) { 476 width = restwidth; 477 } 478 } 479 } 480 } 481 482 if (paraEnd != here) { 483 if ((fittop | fitbottom | fitdescent | fitascent) == 0) { 484 paint.getFontMetricsInt(fm); 485 486 fittop = fm.top; 487 fitbottom = fm.bottom; 488 fitascent = fm.ascent; 489 fitdescent = fm.descent; 490 } 491 492 // Log.e("text", "output rest " + here + " to " + end); 493 494 v = out(source, 495 here, paraEnd, fitascent, fitdescent, 496 fittop, fitbottom, 497 v, 498 spacingmult, spacingadd, chooseht, 499 choosehtv, fm, hasTabOrEmoji, 500 needMultiply, paraStart, chdirs, dir, easy, 501 paraEnd == bufend, includepad, trackpad, 502 chs, widths, here - paraStart, 503 where, ellipsizedWidth, w, paint); 504 } 505 506 paraStart = paraEnd; 507 508 if (paraEnd == bufend) 509 break; 510 } 511 512 if (bufend == bufstart || source.charAt(bufend - 1) == '\n') { 513 // Log.e("text", "output last " + bufend); 514 515 paint.getFontMetricsInt(fm); 516 517 v = out(source, 518 bufend, bufend, fm.ascent, fm.descent, 519 fm.top, fm.bottom, 520 v, 521 spacingmult, spacingadd, null, 522 null, fm, false, 523 needMultiply, bufend, null, DEFAULT_DIR, true, 524 true, includepad, trackpad, 525 null, null, bufstart, 526 where, ellipsizedWidth, 0, paint); 527 } 528 } 529 530 private static final char FIRST_CJK = '\u2E80'; 531 /** 532 * Returns true if the specified character is one of those specified 533 * as being Ideographic (class ID) by the Unicode Line Breaking Algorithm 534 * (http://www.unicode.org/unicode/reports/tr14/), and is therefore OK 535 * to break between a pair of. 536 * 537 * @param includeNonStarters also return true for category NS 538 * (non-starters), which can be broken 539 * after but not before. 540 */ 541 private static final boolean isIdeographic(char c, boolean includeNonStarters) { 542 if (c >= '\u2E80' && c <= '\u2FFF') { 543 return true; // CJK, KANGXI RADICALS, DESCRIPTION SYMBOLS 544 } 545 if (c == '\u3000') { 546 return true; // IDEOGRAPHIC SPACE 547 } 548 if (c >= '\u3040' && c <= '\u309F') { 549 if (!includeNonStarters) { 550 switch (c) { 551 case '\u3041': // # HIRAGANA LETTER SMALL A 552 case '\u3043': // # HIRAGANA LETTER SMALL I 553 case '\u3045': // # HIRAGANA LETTER SMALL U 554 case '\u3047': // # HIRAGANA LETTER SMALL E 555 case '\u3049': // # HIRAGANA LETTER SMALL O 556 case '\u3063': // # HIRAGANA LETTER SMALL TU 557 case '\u3083': // # HIRAGANA LETTER SMALL YA 558 case '\u3085': // # HIRAGANA LETTER SMALL YU 559 case '\u3087': // # HIRAGANA LETTER SMALL YO 560 case '\u308E': // # HIRAGANA LETTER SMALL WA 561 case '\u3095': // # HIRAGANA LETTER SMALL KA 562 case '\u3096': // # HIRAGANA LETTER SMALL KE 563 case '\u309B': // # KATAKANA-HIRAGANA VOICED SOUND MARK 564 case '\u309C': // # KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK 565 case '\u309D': // # HIRAGANA ITERATION MARK 566 case '\u309E': // # HIRAGANA VOICED ITERATION MARK 567 return false; 568 } 569 } 570 return true; // Hiragana (except small characters) 571 } 572 if (c >= '\u30A0' && c <= '\u30FF') { 573 if (!includeNonStarters) { 574 switch (c) { 575 case '\u30A0': // # KATAKANA-HIRAGANA DOUBLE HYPHEN 576 case '\u30A1': // # KATAKANA LETTER SMALL A 577 case '\u30A3': // # KATAKANA LETTER SMALL I 578 case '\u30A5': // # KATAKANA LETTER SMALL U 579 case '\u30A7': // # KATAKANA LETTER SMALL E 580 case '\u30A9': // # KATAKANA LETTER SMALL O 581 case '\u30C3': // # KATAKANA LETTER SMALL TU 582 case '\u30E3': // # KATAKANA LETTER SMALL YA 583 case '\u30E5': // # KATAKANA LETTER SMALL YU 584 case '\u30E7': // # KATAKANA LETTER SMALL YO 585 case '\u30EE': // # KATAKANA LETTER SMALL WA 586 case '\u30F5': // # KATAKANA LETTER SMALL KA 587 case '\u30F6': // # KATAKANA LETTER SMALL KE 588 case '\u30FB': // # KATAKANA MIDDLE DOT 589 case '\u30FC': // # KATAKANA-HIRAGANA PROLONGED SOUND MARK 590 case '\u30FD': // # KATAKANA ITERATION MARK 591 case '\u30FE': // # KATAKANA VOICED ITERATION MARK 592 return false; 593 } 594 } 595 return true; // Katakana (except small characters) 596 } 597 if (c >= '\u3400' && c <= '\u4DB5') { 598 return true; // CJK UNIFIED IDEOGRAPHS EXTENSION A 599 } 600 if (c >= '\u4E00' && c <= '\u9FBB') { 601 return true; // CJK UNIFIED IDEOGRAPHS 602 } 603 if (c >= '\uF900' && c <= '\uFAD9') { 604 return true; // CJK COMPATIBILITY IDEOGRAPHS 605 } 606 if (c >= '\uA000' && c <= '\uA48F') { 607 return true; // YI SYLLABLES 608 } 609 if (c >= '\uA490' && c <= '\uA4CF') { 610 return true; // YI RADICALS 611 } 612 if (c >= '\uFE62' && c <= '\uFE66') { 613 return true; // SMALL PLUS SIGN to SMALL EQUALS SIGN 614 } 615 if (c >= '\uFF10' && c <= '\uFF19') { 616 return true; // WIDE DIGITS 617 } 618 619 return false; 620 } 621 622/* 623 private static void dump(byte[] data, int count, String label) { 624 if (false) { 625 System.out.print(label); 626 627 for (int i = 0; i < count; i++) 628 System.out.print(" " + data[i]); 629 630 System.out.println(); 631 } 632 } 633*/ 634 635 private int out(CharSequence text, int start, int end, 636 int above, int below, int top, int bottom, int v, 637 float spacingmult, float spacingadd, 638 LineHeightSpan[] chooseht, int[] choosehtv, 639 Paint.FontMetricsInt fm, boolean hasTabOrEmoji, 640 boolean needMultiply, int pstart, byte[] chdirs, 641 int dir, boolean easy, boolean last, 642 boolean includepad, boolean trackpad, 643 char[] chs, float[] widths, int widstart, 644 TextUtils.TruncateAt ellipsize, float ellipsiswidth, 645 float textwidth, TextPaint paint) { 646 int j = mLineCount; 647 int off = j * mColumns; 648 int want = off + mColumns + TOP; 649 int[] lines = mLines; 650 651 if (want >= lines.length) { 652 int nlen = ArrayUtils.idealIntArraySize(want + 1); 653 int[] grow = new int[nlen]; 654 System.arraycopy(lines, 0, grow, 0, lines.length); 655 mLines = grow; 656 lines = grow; 657 658 Directions[] grow2 = new Directions[nlen]; 659 System.arraycopy(mLineDirections, 0, grow2, 0, 660 mLineDirections.length); 661 mLineDirections = grow2; 662 } 663 664 if (chooseht != null) { 665 fm.ascent = above; 666 fm.descent = below; 667 fm.top = top; 668 fm.bottom = bottom; 669 670 for (int i = 0; i < chooseht.length; i++) { 671 if (chooseht[i] instanceof LineHeightSpan.WithDensity) { 672 ((LineHeightSpan.WithDensity) chooseht[i]). 673 chooseHeight(text, start, end, choosehtv[i], v, fm, paint); 674 675 } else { 676 chooseht[i].chooseHeight(text, start, end, choosehtv[i], v, fm); 677 } 678 } 679 680 above = fm.ascent; 681 below = fm.descent; 682 top = fm.top; 683 bottom = fm.bottom; 684 } 685 686 if (j == 0) { 687 if (trackpad) { 688 mTopPadding = top - above; 689 } 690 691 if (includepad) { 692 above = top; 693 } 694 } 695 if (last) { 696 if (trackpad) { 697 mBottomPadding = bottom - below; 698 } 699 700 if (includepad) { 701 below = bottom; 702 } 703 } 704 705 int extra; 706 707 if (needMultiply) { 708 double ex = (below - above) * (spacingmult - 1) + spacingadd; 709 if (ex >= 0) { 710 extra = (int)(ex + 0.5); 711 } else { 712 extra = -(int)(-ex + 0.5); 713 } 714 } else { 715 extra = 0; 716 } 717 718 lines[off + START] = start; 719 lines[off + TOP] = v; 720 lines[off + DESCENT] = below + extra; 721 722 v += (below - above) + extra; 723 lines[off + mColumns + START] = end; 724 lines[off + mColumns + TOP] = v; 725 726 if (hasTabOrEmoji) 727 lines[off + TAB] |= TAB_MASK; 728 729 lines[off + DIR] |= dir << DIR_SHIFT; 730 Directions linedirs = DIRS_ALL_LEFT_TO_RIGHT; 731 // easy means all chars < the first RTL, so no emoji, no nothing 732 // XXX a run with no text or all spaces is easy but might be an empty 733 // RTL paragraph. Make sure easy is false if this is the case. 734 if (easy) { 735 mLineDirections[j] = linedirs; 736 } else { 737 mLineDirections[j] = AndroidBidi.directions(dir, chdirs, widstart, chs, 738 widstart, end - start); 739 740 // If ellipsize is in marquee mode, do not apply ellipsis on the first line 741 if (ellipsize != null && (ellipsize != TextUtils.TruncateAt.MARQUEE || j != 0)) { 742 calculateEllipsis(start, end, widths, widstart, 743 ellipsiswidth, ellipsize, j, 744 textwidth, paint); 745 } 746 } 747 748 mLineCount++; 749 return v; 750 } 751 752 private void calculateEllipsis(int linestart, int lineend, 753 float[] widths, int widstart, 754 float avail, TextUtils.TruncateAt where, 755 int line, float textwidth, TextPaint paint) { 756 int len = lineend - linestart; 757 758 if (textwidth <= avail) { 759 // Everything fits! 760 mLines[mColumns * line + ELLIPSIS_START] = 0; 761 mLines[mColumns * line + ELLIPSIS_COUNT] = 0; 762 return; 763 } 764 765 float ellipsiswid = paint.measureText("\u2026"); 766 int ellipsisStart, ellipsisCount; 767 768 if (where == TextUtils.TruncateAt.START) { 769 float sum = 0; 770 int i; 771 772 for (i = len; i >= 0; i--) { 773 float w = widths[i - 1 + linestart - widstart]; 774 775 if (w + sum + ellipsiswid > avail) { 776 break; 777 } 778 779 sum += w; 780 } 781 782 ellipsisStart = 0; 783 ellipsisCount = i; 784 } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE) { 785 float sum = 0; 786 int i; 787 788 for (i = 0; i < len; i++) { 789 float w = widths[i + linestart - widstart]; 790 791 if (w + sum + ellipsiswid > avail) { 792 break; 793 } 794 795 sum += w; 796 } 797 798 ellipsisStart = i; 799 ellipsisCount = len - i; 800 } else /* where = TextUtils.TruncateAt.MIDDLE */ { 801 float lsum = 0, rsum = 0; 802 int left = 0, right = len; 803 804 float ravail = (avail - ellipsiswid) / 2; 805 for (right = len; right >= 0; right--) { 806 float w = widths[right - 1 + linestart - widstart]; 807 808 if (w + rsum > ravail) { 809 break; 810 } 811 812 rsum += w; 813 } 814 815 float lavail = avail - ellipsiswid - rsum; 816 for (left = 0; left < right; left++) { 817 float w = widths[left + linestart - widstart]; 818 819 if (w + lsum > lavail) { 820 break; 821 } 822 823 lsum += w; 824 } 825 826 ellipsisStart = left; 827 ellipsisCount = right - left; 828 } 829 830 mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart; 831 mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount; 832 } 833 834 // Override the base class so we can directly access our members, 835 // rather than relying on member functions. 836 // The logic mirrors that of Layout.getLineForVertical 837 // FIXME: It may be faster to do a linear search for layouts without many lines. 838 public int getLineForVertical(int vertical) { 839 int high = mLineCount; 840 int low = -1; 841 int guess; 842 int[] lines = mLines; 843 while (high - low > 1) { 844 guess = (high + low) >> 1; 845 if (lines[mColumns * guess + TOP] > vertical){ 846 high = guess; 847 } else { 848 low = guess; 849 } 850 } 851 if (low < 0) { 852 return 0; 853 } else { 854 return low; 855 } 856 } 857 858 public int getLineCount() { 859 return mLineCount; 860 } 861 862 public int getLineTop(int line) { 863 return mLines[mColumns * line + TOP]; 864 } 865 866 public int getLineDescent(int line) { 867 return mLines[mColumns * line + DESCENT]; 868 } 869 870 public int getLineStart(int line) { 871 return mLines[mColumns * line + START] & START_MASK; 872 } 873 874 public int getParagraphDirection(int line) { 875 return mLines[mColumns * line + DIR] >> DIR_SHIFT; 876 } 877 878 public boolean getLineContainsTab(int line) { 879 return (mLines[mColumns * line + TAB] & TAB_MASK) != 0; 880 } 881 882 public final Directions getLineDirections(int line) { 883 return mLineDirections[line]; 884 } 885 886 public int getTopPadding() { 887 return mTopPadding; 888 } 889 890 public int getBottomPadding() { 891 return mBottomPadding; 892 } 893 894 @Override 895 public int getEllipsisCount(int line) { 896 if (mColumns < COLUMNS_ELLIPSIZE) { 897 return 0; 898 } 899 900 return mLines[mColumns * line + ELLIPSIS_COUNT]; 901 } 902 903 @Override 904 public int getEllipsisStart(int line) { 905 if (mColumns < COLUMNS_ELLIPSIZE) { 906 return 0; 907 } 908 909 return mLines[mColumns * line + ELLIPSIS_START]; 910 } 911 912 @Override 913 public int getEllipsizedWidth() { 914 return mEllipsizedWidth; 915 } 916 917 private int mLineCount; 918 private int mTopPadding, mBottomPadding; 919 private int mColumns; 920 private int mEllipsizedWidth; 921 922 private static final int COLUMNS_NORMAL = 3; 923 private static final int COLUMNS_ELLIPSIZE = 5; 924 private static final int START = 0; 925 private static final int DIR = START; 926 private static final int TAB = START; 927 private static final int TOP = 1; 928 private static final int DESCENT = 2; 929 private static final int ELLIPSIS_START = 3; 930 private static final int ELLIPSIS_COUNT = 4; 931 932 private int[] mLines; 933 private Directions[] mLineDirections; 934 935 private static final int START_MASK = 0x1FFFFFFF; 936 private static final int DIR_MASK = 0xC0000000; 937 private static final int DIR_SHIFT = 30; 938 private static final int TAB_MASK = 0x20000000; 939 940 private static final int TAB_INCREMENT = 20; // same as Layout, but that's private 941 942 /* 943 * This is reused across calls to generate() 944 */ 945 private MeasuredText mMeasured; 946 private Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt(); 947} 948