StaticLayout.java revision 9f7a4442b89cc06cb8cae6992484e7ae795323ab
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 android.graphics.Bitmap; 20import android.graphics.Paint; 21import com.android.internal.util.ArrayUtils; 22import android.util.Log; 23import android.text.style.LeadingMarginSpan; 24import android.text.style.LineHeightSpan; 25import android.text.style.MetricAffectingSpan; 26import android.text.style.ReplacementSpan; 27 28/** 29 * StaticLayout is a Layout for text that will not be edited after it 30 * is laid out. Use {@link DynamicLayout} for text that may change. 31 * <p>This is used by widgets to control text layout. You should not need 32 * to use this class directly unless you are implementing your own widget 33 * or custom display object, or would be tempted to call 34 * {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int, float, float, android.graphics.Paint) 35 * Canvas.drawText()} directly.</p> 36 */ 37public class 38StaticLayout 39extends Layout 40{ 41 public StaticLayout(CharSequence source, TextPaint paint, 42 int width, 43 Alignment align, float spacingmult, float spacingadd, 44 boolean includepad) { 45 this(source, 0, source.length(), paint, width, align, 46 spacingmult, spacingadd, includepad); 47 } 48 49 public StaticLayout(CharSequence source, int bufstart, int bufend, 50 TextPaint paint, int outerwidth, 51 Alignment align, 52 float spacingmult, float spacingadd, 53 boolean includepad) { 54 this(source, bufstart, bufend, paint, outerwidth, align, 55 spacingmult, spacingadd, includepad, null, 0); 56 } 57 58 public StaticLayout(CharSequence source, int bufstart, int bufend, 59 TextPaint paint, int outerwidth, 60 Alignment align, 61 float spacingmult, float spacingadd, 62 boolean includepad, 63 TextUtils.TruncateAt ellipsize, int ellipsizedWidth) { 64 super((ellipsize == null) 65 ? source 66 : (source instanceof Spanned) 67 ? new SpannedEllipsizer(source) 68 : new Ellipsizer(source), 69 paint, outerwidth, align, spacingmult, spacingadd); 70 71 /* 72 * This is annoying, but we can't refer to the layout until 73 * superclass construction is finished, and the superclass 74 * constructor wants the reference to the display text. 75 * 76 * This will break if the superclass constructor ever actually 77 * cares about the content instead of just holding the reference. 78 */ 79 if (ellipsize != null) { 80 Ellipsizer e = (Ellipsizer) getText(); 81 82 e.mLayout = this; 83 e.mWidth = ellipsizedWidth; 84 e.mMethod = ellipsize; 85 mEllipsizedWidth = ellipsizedWidth; 86 87 mColumns = COLUMNS_ELLIPSIZE; 88 } else { 89 mColumns = COLUMNS_NORMAL; 90 mEllipsizedWidth = outerwidth; 91 } 92 93 mLines = new int[ArrayUtils.idealIntArraySize(2 * mColumns)]; 94 mLineDirections = new Directions[ 95 ArrayUtils.idealIntArraySize(2 * mColumns)]; 96 97 generate(source, bufstart, bufend, paint, outerwidth, align, 98 spacingmult, spacingadd, includepad, includepad, 99 ellipsize != null, ellipsizedWidth, ellipsize); 100 101 mChdirs = null; 102 mChs = null; 103 mWidths = null; 104 mFontMetricsInt = null; 105 } 106 107 /* package */ StaticLayout(boolean ellipsize) { 108 super(null, null, 0, null, 0, 0); 109 110 mColumns = COLUMNS_ELLIPSIZE; 111 mLines = new int[ArrayUtils.idealIntArraySize(2 * mColumns)]; 112 mLineDirections = new Directions[ 113 ArrayUtils.idealIntArraySize(2 * mColumns)]; 114 } 115 116 /* package */ void generate(CharSequence source, int bufstart, int bufend, 117 TextPaint paint, int outerwidth, 118 Alignment align, 119 float spacingmult, float spacingadd, 120 boolean includepad, boolean trackpad, 121 boolean breakOnlyAtSpaces, 122 float ellipsizedWidth, TextUtils.TruncateAt where) { 123 mLineCount = 0; 124 125 int v = 0; 126 boolean needMultiply = (spacingmult != 1 || spacingadd != 0); 127 128 Paint.FontMetricsInt fm = mFontMetricsInt; 129 int[] choosehtv = null; 130 131 int end = TextUtils.indexOf(source, '\n', bufstart, bufend); 132 int bufsiz = end >= 0 ? end - bufstart : bufend - bufstart; 133 boolean first = true; 134 135 if (mChdirs == null) { 136 mChdirs = new byte[ArrayUtils.idealByteArraySize(bufsiz + 1)]; 137 mChs = new char[ArrayUtils.idealCharArraySize(bufsiz + 1)]; 138 mWidths = new float[ArrayUtils.idealIntArraySize((bufsiz + 1) * 2)]; 139 } 140 141 byte[] chdirs = mChdirs; 142 char[] chs = mChs; 143 float[] widths = mWidths; 144 145 AlteredCharSequence alter = null; 146 Spanned spanned = null; 147 148 if (source instanceof Spanned) 149 spanned = (Spanned) source; 150 151 int DEFAULT_DIR = DIR_LEFT_TO_RIGHT; // XXX 152 153 for (int start = bufstart; start <= bufend; start = end) { 154 if (first) 155 first = false; 156 else 157 end = TextUtils.indexOf(source, '\n', start, bufend); 158 159 if (end < 0) 160 end = bufend; 161 else 162 end++; 163 164 int firstWidthLineCount = 1; 165 int firstwidth = outerwidth; 166 int restwidth = outerwidth; 167 168 LineHeightSpan[] chooseht = null; 169 170 if (spanned != null) { 171 LeadingMarginSpan[] sp; 172 173 sp = spanned.getSpans(start, end, LeadingMarginSpan.class); 174 for (int i = 0; i < sp.length; i++) { 175 LeadingMarginSpan lms = sp[i]; 176 firstwidth -= sp[i].getLeadingMargin(true); 177 restwidth -= sp[i].getLeadingMargin(false); 178 if (lms instanceof LeadingMarginSpan.LeadingMarginSpan2) { 179 firstWidthLineCount = ((LeadingMarginSpan.LeadingMarginSpan2)lms).getLeadingMarginLineCount(); 180 } 181 } 182 183 chooseht = spanned.getSpans(start, end, LineHeightSpan.class); 184 185 if (chooseht.length != 0) { 186 if (choosehtv == null || 187 choosehtv.length < chooseht.length) { 188 choosehtv = new int[ArrayUtils.idealIntArraySize( 189 chooseht.length)]; 190 } 191 192 for (int i = 0; i < chooseht.length; i++) { 193 int o = spanned.getSpanStart(chooseht[i]); 194 195 if (o < start) { 196 // starts in this layout, before the 197 // current paragraph 198 199 choosehtv[i] = getLineTop(getLineForOffset(o)); 200 } else { 201 // starts in this paragraph 202 203 choosehtv[i] = v; 204 } 205 } 206 } 207 } 208 209 if (end - start > chdirs.length) { 210 chdirs = new byte[ArrayUtils.idealByteArraySize(end - start)]; 211 mChdirs = chdirs; 212 } 213 if (end - start > chs.length) { 214 chs = new char[ArrayUtils.idealCharArraySize(end - start)]; 215 mChs = chs; 216 } 217 if ((end - start) * 2 > widths.length) { 218 widths = new float[ArrayUtils.idealIntArraySize((end - start) * 2)]; 219 mWidths = widths; 220 } 221 222 TextUtils.getChars(source, start, end, chs, 0); 223 final int n = end - start; 224 225 boolean easy = true; 226 boolean altered = false; 227 int dir = DEFAULT_DIR; // XXX pass value in 228 229 for (int i = 0; i < n; i++) { 230 if (chs[i] >= FIRST_RIGHT_TO_LEFT) { 231 easy = false; 232 break; 233 } 234 } 235 236 // Ensure that none of the underlying characters are treated 237 // as viable breakpoints, and that the entire run gets the 238 // same bidi direction. 239 240 if (source instanceof Spanned) { 241 Spanned sp = (Spanned) source; 242 ReplacementSpan[] spans = sp.getSpans(start, end, ReplacementSpan.class); 243 244 for (int y = 0; y < spans.length; y++) { 245 int a = sp.getSpanStart(spans[y]); 246 int b = sp.getSpanEnd(spans[y]); 247 248 for (int x = a; x < b; x++) { 249 chs[x - start] = '\uFFFC'; 250 } 251 } 252 } 253 254 if (!easy) { 255 // XXX put override flags, etc. into chdirs 256 // XXX supply dir rather than force 257 dir = AndroidBidi.bidi(DIR_REQUEST_DEFAULT_LTR, chs, chdirs, n, false); 258 259 // Do mirroring for right-to-left segments 260 261 for (int i = 0; i < n; i++) { 262 if (chdirs[i] == Character.DIRECTIONALITY_RIGHT_TO_LEFT) { 263 int j; 264 265 for (j = i; j < n; j++) { 266 if (chdirs[j] != 267 Character.DIRECTIONALITY_RIGHT_TO_LEFT) 268 break; 269 } 270 271 if (AndroidCharacter.mirror(chs, i, j - i)) 272 altered = true; 273 274 i = j - 1; 275 } 276 } 277 } 278 279 CharSequence sub; 280 281 if (altered) { 282 if (alter == null) 283 alter = AlteredCharSequence.make(source, chs, start, end); 284 else 285 alter.update(chs, start, end); 286 287 sub = alter; 288 } else { 289 sub = source; 290 } 291 292 int width = firstwidth; 293 294 float w = 0; 295 int here = start; 296 297 int ok = start; 298 float okwidth = w; 299 int okascent = 0, okdescent = 0, oktop = 0, okbottom = 0; 300 301 int fit = start; 302 float fitwidth = w; 303 int fitascent = 0, fitdescent = 0, fittop = 0, fitbottom = 0; 304 305 boolean tab = false; 306 307 int next; 308 for (int i = start; i < end; i = next) { 309 if (spanned == null) 310 next = end; 311 else 312 next = spanned.nextSpanTransition(i, end, 313 MetricAffectingSpan. 314 class); 315 316 if (spanned == null) { 317 paint.getTextWidths(sub, i, next, widths); 318 System.arraycopy(widths, 0, widths, 319 end - start + (i - start), next - i); 320 321 paint.getFontMetricsInt(fm); 322 } else { 323 mWorkPaint.baselineShift = 0; 324 325 Styled.getTextWidths(paint, mWorkPaint, 326 spanned, i, next, 327 widths, fm); 328 System.arraycopy(widths, 0, widths, 329 end - start + (i - start), next - i); 330 331 if (mWorkPaint.baselineShift < 0) { 332 fm.ascent += mWorkPaint.baselineShift; 333 fm.top += mWorkPaint.baselineShift; 334 } else { 335 fm.descent += mWorkPaint.baselineShift; 336 fm.bottom += mWorkPaint.baselineShift; 337 } 338 } 339 340 int fmtop = fm.top; 341 int fmbottom = fm.bottom; 342 int fmascent = fm.ascent; 343 int fmdescent = fm.descent; 344 345 if (false) { 346 StringBuilder sb = new StringBuilder(); 347 for (int j = i; j < next; j++) { 348 sb.append(widths[j - start + (end - start)]); 349 sb.append(' '); 350 } 351 352 Log.e("text", sb.toString()); 353 } 354 355 for (int j = i; j < next; j++) { 356 char c = chs[j - start]; 357 float before = w; 358 359 if (c == '\n') { 360 ; 361 } else if (c == '\t') { 362 w = Layout.nextTab(sub, start, end, w, null); 363 tab = true; 364 } else if (c >= 0xD800 && c <= 0xDFFF && j + 1 < next) { 365 int emoji = Character.codePointAt(chs, j - start); 366 367 if (emoji >= MIN_EMOJI && emoji <= MAX_EMOJI) { 368 Bitmap bm = EMOJI_FACTORY. 369 getBitmapFromAndroidPua(emoji); 370 371 if (bm != null) { 372 Paint whichPaint; 373 374 if (spanned == null) { 375 whichPaint = paint; 376 } else { 377 whichPaint = mWorkPaint; 378 } 379 380 float wid = (float) bm.getWidth() * 381 -whichPaint.ascent() / 382 bm.getHeight(); 383 384 w += wid; 385 tab = true; 386 j++; 387 } else { 388 w += widths[j - start + (end - start)]; 389 } 390 } else { 391 w += widths[j - start + (end - start)]; 392 } 393 } else { 394 w += widths[j - start + (end - start)]; 395 } 396 397 // Log.e("text", "was " + before + " now " + w + " after " + c + " within " + width); 398 399 if (w <= width) { 400 fitwidth = w; 401 fit = j + 1; 402 403 if (fmtop < fittop) 404 fittop = fmtop; 405 if (fmascent < fitascent) 406 fitascent = fmascent; 407 if (fmdescent > fitdescent) 408 fitdescent = fmdescent; 409 if (fmbottom > fitbottom) 410 fitbottom = fmbottom; 411 412 /* 413 * From the Unicode Line Breaking Algorithm: 414 * (at least approximately) 415 * 416 * .,:; are class IS: breakpoints 417 * except when adjacent to digits 418 * / is class SY: a breakpoint 419 * except when followed by a digit. 420 * - is class HY: a breakpoint 421 * except when followed by a digit. 422 * 423 * Ideographs are class ID: breakpoints when adjacent, 424 * except for NS (non-starters), which can be broken 425 * after but not before. 426 */ 427 428 if (c == ' ' || c == '\t' || 429 ((c == '.' || c == ',' || c == ':' || c == ';') && 430 (j - 1 < here || !Character.isDigit(chs[j - 1 - start])) && 431 (j + 1 >= next || !Character.isDigit(chs[j + 1 - start]))) || 432 ((c == '/' || c == '-') && 433 (j + 1 >= next || !Character.isDigit(chs[j + 1 - start]))) || 434 (c >= FIRST_CJK && isIdeographic(c, true) && 435 j + 1 < next && isIdeographic(chs[j + 1 - start], false))) { 436 okwidth = w; 437 ok = j + 1; 438 439 if (fittop < oktop) 440 oktop = fittop; 441 if (fitascent < okascent) 442 okascent = fitascent; 443 if (fitdescent > okdescent) 444 okdescent = fitdescent; 445 if (fitbottom > okbottom) 446 okbottom = fitbottom; 447 } 448 } else if (breakOnlyAtSpaces) { 449 if (ok != here) { 450 // Log.e("text", "output ok " + here + " to " +ok); 451 452 while (ok < next && chs[ok - start] == ' ') { 453 ok++; 454 } 455 456 v = out(source, 457 here, ok, 458 okascent, okdescent, oktop, okbottom, 459 v, 460 spacingmult, spacingadd, chooseht, 461 choosehtv, fm, tab, 462 needMultiply, start, chdirs, dir, easy, 463 ok == bufend, includepad, trackpad, 464 widths, start, end - start, 465 where, ellipsizedWidth, okwidth, 466 paint); 467 468 here = ok; 469 } else { 470 // Act like it fit even though it didn't. 471 472 fitwidth = w; 473 fit = j + 1; 474 475 if (fmtop < fittop) 476 fittop = fmtop; 477 if (fmascent < fitascent) 478 fitascent = fmascent; 479 if (fmdescent > fitdescent) 480 fitdescent = fmdescent; 481 if (fmbottom > fitbottom) 482 fitbottom = fmbottom; 483 } 484 } else { 485 if (ok != here) { 486 // Log.e("text", "output ok " + here + " to " +ok); 487 488 while (ok < next && chs[ok - start] == ' ') { 489 ok++; 490 } 491 492 v = out(source, 493 here, ok, 494 okascent, okdescent, oktop, okbottom, 495 v, 496 spacingmult, spacingadd, chooseht, 497 choosehtv, fm, tab, 498 needMultiply, start, chdirs, dir, easy, 499 ok == bufend, includepad, trackpad, 500 widths, start, end - start, 501 where, ellipsizedWidth, okwidth, 502 paint); 503 504 here = ok; 505 } else if (fit != here) { 506 // Log.e("text", "output fit " + here + " to " +fit); 507 v = out(source, 508 here, fit, 509 fitascent, fitdescent, 510 fittop, fitbottom, 511 v, 512 spacingmult, spacingadd, chooseht, 513 choosehtv, fm, tab, 514 needMultiply, start, chdirs, dir, easy, 515 fit == bufend, includepad, trackpad, 516 widths, start, end - start, 517 where, ellipsizedWidth, fitwidth, 518 paint); 519 520 here = fit; 521 } else { 522 // Log.e("text", "output one " + here + " to " +(here + 1)); 523 measureText(paint, mWorkPaint, 524 source, here, here + 1, fm, tab, 525 null); 526 527 v = out(source, 528 here, here+1, 529 fm.ascent, fm.descent, 530 fm.top, fm.bottom, 531 v, 532 spacingmult, spacingadd, chooseht, 533 choosehtv, fm, tab, 534 needMultiply, start, chdirs, dir, easy, 535 here + 1 == bufend, includepad, 536 trackpad, 537 widths, start, end - start, 538 where, ellipsizedWidth, 539 widths[here - start], paint); 540 541 here = here + 1; 542 } 543 544 if (here < i) { 545 j = next = here; // must remeasure 546 } else { 547 j = here - 1; // continue looping 548 } 549 550 ok = fit = here; 551 w = 0; 552 fitascent = fitdescent = fittop = fitbottom = 0; 553 okascent = okdescent = oktop = okbottom = 0; 554 555 if (--firstWidthLineCount <= 0) { 556 width = restwidth; 557 } 558 } 559 } 560 } 561 562 if (end != here) { 563 if ((fittop | fitbottom | fitdescent | fitascent) == 0) { 564 paint.getFontMetricsInt(fm); 565 566 fittop = fm.top; 567 fitbottom = fm.bottom; 568 fitascent = fm.ascent; 569 fitdescent = fm.descent; 570 } 571 572 // Log.e("text", "output rest " + here + " to " + end); 573 574 v = out(source, 575 here, end, fitascent, fitdescent, 576 fittop, fitbottom, 577 v, 578 spacingmult, spacingadd, chooseht, 579 choosehtv, fm, tab, 580 needMultiply, start, chdirs, dir, easy, 581 end == bufend, includepad, trackpad, 582 widths, start, end - start, 583 where, ellipsizedWidth, w, paint); 584 } 585 586 start = end; 587 588 if (end == bufend) 589 break; 590 } 591 592 if (bufend == bufstart || source.charAt(bufend - 1) == '\n') { 593 // Log.e("text", "output last " + bufend); 594 595 paint.getFontMetricsInt(fm); 596 597 v = out(source, 598 bufend, bufend, fm.ascent, fm.descent, 599 fm.top, fm.bottom, 600 v, 601 spacingmult, spacingadd, null, 602 null, fm, false, 603 needMultiply, bufend, chdirs, DEFAULT_DIR, true, 604 true, includepad, trackpad, 605 widths, bufstart, 0, 606 where, ellipsizedWidth, 0, paint); 607 } 608 } 609 610 private static final char FIRST_CJK = '\u2E80'; 611 /** 612 * Returns true if the specified character is one of those specified 613 * as being Ideographic (class ID) by the Unicode Line Breaking Algorithm 614 * (http://www.unicode.org/unicode/reports/tr14/), and is therefore OK 615 * to break between a pair of. 616 * 617 * @param includeNonStarters also return true for category NS 618 * (non-starters), which can be broken 619 * after but not before. 620 */ 621 private static final boolean isIdeographic(char c, boolean includeNonStarters) { 622 if (c >= '\u2E80' && c <= '\u2FFF') { 623 return true; // CJK, KANGXI RADICALS, DESCRIPTION SYMBOLS 624 } 625 if (c == '\u3000') { 626 return true; // IDEOGRAPHIC SPACE 627 } 628 if (c >= '\u3040' && c <= '\u309F') { 629 if (!includeNonStarters) { 630 switch (c) { 631 case '\u3041': // # HIRAGANA LETTER SMALL A 632 case '\u3043': // # HIRAGANA LETTER SMALL I 633 case '\u3045': // # HIRAGANA LETTER SMALL U 634 case '\u3047': // # HIRAGANA LETTER SMALL E 635 case '\u3049': // # HIRAGANA LETTER SMALL O 636 case '\u3063': // # HIRAGANA LETTER SMALL TU 637 case '\u3083': // # HIRAGANA LETTER SMALL YA 638 case '\u3085': // # HIRAGANA LETTER SMALL YU 639 case '\u3087': // # HIRAGANA LETTER SMALL YO 640 case '\u308E': // # HIRAGANA LETTER SMALL WA 641 case '\u3095': // # HIRAGANA LETTER SMALL KA 642 case '\u3096': // # HIRAGANA LETTER SMALL KE 643 case '\u309B': // # KATAKANA-HIRAGANA VOICED SOUND MARK 644 case '\u309C': // # KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK 645 case '\u309D': // # HIRAGANA ITERATION MARK 646 case '\u309E': // # HIRAGANA VOICED ITERATION MARK 647 return false; 648 } 649 } 650 return true; // Hiragana (except small characters) 651 } 652 if (c >= '\u30A0' && c <= '\u30FF') { 653 if (!includeNonStarters) { 654 switch (c) { 655 case '\u30A0': // # KATAKANA-HIRAGANA DOUBLE HYPHEN 656 case '\u30A1': // # KATAKANA LETTER SMALL A 657 case '\u30A3': // # KATAKANA LETTER SMALL I 658 case '\u30A5': // # KATAKANA LETTER SMALL U 659 case '\u30A7': // # KATAKANA LETTER SMALL E 660 case '\u30A9': // # KATAKANA LETTER SMALL O 661 case '\u30C3': // # KATAKANA LETTER SMALL TU 662 case '\u30E3': // # KATAKANA LETTER SMALL YA 663 case '\u30E5': // # KATAKANA LETTER SMALL YU 664 case '\u30E7': // # KATAKANA LETTER SMALL YO 665 case '\u30EE': // # KATAKANA LETTER SMALL WA 666 case '\u30F5': // # KATAKANA LETTER SMALL KA 667 case '\u30F6': // # KATAKANA LETTER SMALL KE 668 case '\u30FB': // # KATAKANA MIDDLE DOT 669 case '\u30FC': // # KATAKANA-HIRAGANA PROLONGED SOUND MARK 670 case '\u30FD': // # KATAKANA ITERATION MARK 671 case '\u30FE': // # KATAKANA VOICED ITERATION MARK 672 return false; 673 } 674 } 675 return true; // Katakana (except small characters) 676 } 677 if (c >= '\u3400' && c <= '\u4DB5') { 678 return true; // CJK UNIFIED IDEOGRAPHS EXTENSION A 679 } 680 if (c >= '\u4E00' && c <= '\u9FBB') { 681 return true; // CJK UNIFIED IDEOGRAPHS 682 } 683 if (c >= '\uF900' && c <= '\uFAD9') { 684 return true; // CJK COMPATIBILITY IDEOGRAPHS 685 } 686 if (c >= '\uA000' && c <= '\uA48F') { 687 return true; // YI SYLLABLES 688 } 689 if (c >= '\uA490' && c <= '\uA4CF') { 690 return true; // YI RADICALS 691 } 692 if (c >= '\uFE62' && c <= '\uFE66') { 693 return true; // SMALL PLUS SIGN to SMALL EQUALS SIGN 694 } 695 if (c >= '\uFF10' && c <= '\uFF19') { 696 return true; // WIDE DIGITS 697 } 698 699 return false; 700 } 701 702/* 703 private static void dump(byte[] data, int count, String label) { 704 if (false) { 705 System.out.print(label); 706 707 for (int i = 0; i < count; i++) 708 System.out.print(" " + data[i]); 709 710 System.out.println(); 711 } 712 } 713*/ 714 715 private static int getFit(TextPaint paint, 716 TextPaint workPaint, 717 CharSequence text, int start, int end, 718 float wid) { 719 int high = end + 1, low = start - 1, guess; 720 721 while (high - low > 1) { 722 guess = (high + low) / 2; 723 724 if (measureText(paint, workPaint, 725 text, start, guess, null, true, null) > wid) 726 high = guess; 727 else 728 low = guess; 729 } 730 731 if (low < start) 732 return start; 733 else 734 return low; 735 } 736 737 private int out(CharSequence text, int start, int end, 738 int above, int below, int top, int bottom, int v, 739 float spacingmult, float spacingadd, 740 LineHeightSpan[] chooseht, int[] choosehtv, 741 Paint.FontMetricsInt fm, boolean tab, 742 boolean needMultiply, int pstart, byte[] chdirs, 743 int dir, boolean easy, boolean last, 744 boolean includepad, boolean trackpad, 745 float[] widths, int widstart, int widoff, 746 TextUtils.TruncateAt ellipsize, float ellipsiswidth, 747 float textwidth, TextPaint paint) { 748 int j = mLineCount; 749 int off = j * mColumns; 750 int want = off + mColumns + TOP; 751 int[] lines = mLines; 752 753 // Log.e("text", "line " + start + " to " + end + (last ? "===" : "")); 754 755 if (want >= lines.length) { 756 int nlen = ArrayUtils.idealIntArraySize(want + 1); 757 int[] grow = new int[nlen]; 758 System.arraycopy(lines, 0, grow, 0, lines.length); 759 mLines = grow; 760 lines = grow; 761 762 Directions[] grow2 = new Directions[nlen]; 763 System.arraycopy(mLineDirections, 0, grow2, 0, 764 mLineDirections.length); 765 mLineDirections = grow2; 766 } 767 768 if (chooseht != null) { 769 fm.ascent = above; 770 fm.descent = below; 771 fm.top = top; 772 fm.bottom = bottom; 773 774 for (int i = 0; i < chooseht.length; i++) { 775 if (chooseht[i] instanceof LineHeightSpan.WithDensity) { 776 ((LineHeightSpan.WithDensity) chooseht[i]). 777 chooseHeight(text, start, end, choosehtv[i], v, fm, paint); 778 779 } else { 780 chooseht[i].chooseHeight(text, start, end, choosehtv[i], v, fm); 781 } 782 } 783 784 above = fm.ascent; 785 below = fm.descent; 786 top = fm.top; 787 bottom = fm.bottom; 788 } 789 790 if (j == 0) { 791 if (trackpad) { 792 mTopPadding = top - above; 793 } 794 795 if (includepad) { 796 above = top; 797 } 798 } 799 if (last) { 800 if (trackpad) { 801 mBottomPadding = bottom - below; 802 } 803 804 if (includepad) { 805 below = bottom; 806 } 807 } 808 809 int extra; 810 811 if (needMultiply) { 812 double ex = (below - above) * (spacingmult - 1) + spacingadd; 813 if (ex >= 0) { 814 extra = (int)(ex + 0.5); 815 } else { 816 extra = -(int)(-ex + 0.5); 817 } 818 } else { 819 extra = 0; 820 } 821 822 lines[off + START] = start; 823 lines[off + TOP] = v; 824 lines[off + DESCENT] = below + extra; 825 826 v += (below - above) + extra; 827 lines[off + mColumns + START] = end; 828 lines[off + mColumns + TOP] = v; 829 830 if (tab) 831 lines[off + TAB] |= TAB_MASK; 832 833 lines[off + DIR] |= dir << DIR_SHIFT; 834 Directions linedirs = DIRS_ALL_LEFT_TO_RIGHT; 835 // easy means all chars < the first RTL, so no emoji, no nothing 836 // XXX a run with no text or all spaces is easy but might be an empty 837 // RTL paragraph. Make sure easy is false if this is the case. 838 if (easy) { 839 mLineDirections[j] = linedirs; 840 } else { 841 int startOff = start - pstart; 842 int baseLevel = dir == DIR_LEFT_TO_RIGHT ? 0 : 1; 843 int curLevel = chdirs[startOff]; 844 int minLevel = curLevel; 845 int runCount = 1; 846 for (int i = start + 1; i < end; ++i) { 847 int level = chdirs[i - pstart]; 848 if (level != curLevel) { 849 curLevel = level; 850 ++runCount; 851 } 852 } 853 854 // add final run for trailing counter-directional whitespace 855 int visEnd = end; 856 if ((curLevel & 1) != (baseLevel & 1)) { 857 // look for visible end 858 while (--visEnd >= start) { 859 char ch = text.charAt(visEnd); 860 861 if (ch == '\n') { 862 --visEnd; 863 break; 864 } 865 866 if (ch != ' ' && ch != '\t') { 867 break; 868 } 869 } 870 ++visEnd; 871 if (visEnd != end) { 872 ++runCount; 873 } 874 } 875 876 if (runCount == 1 && minLevel == baseLevel) { 877 if ((minLevel & 1) != 0) { 878 linedirs = DIRS_ALL_RIGHT_TO_LEFT; 879 } 880 // we're done, only one run on this line 881 } else { 882 int[] ld = new int[runCount * 2]; 883 int maxLevel = minLevel; 884 int levelBits = minLevel << RUN_LEVEL_SHIFT; 885 { 886 // Start of first pair is always 0, we write 887 // length then start at each new run, and the 888 // last run length after we're done. 889 int n = 1; 890 int prev = start; 891 curLevel = minLevel; 892 for (int i = start; i < visEnd; ++i) { 893 int level = chdirs[i - pstart]; 894 if (level != curLevel) { 895 curLevel = level; 896 if (level > maxLevel) { 897 maxLevel = level; 898 } else if (level < minLevel) { 899 minLevel = level; 900 } 901 // XXX ignore run length limit of 2^RUN_LEVEL_SHIFT 902 ld[n++] = (i - prev) | levelBits; 903 ld[n++] = i - start; 904 levelBits = curLevel << RUN_LEVEL_SHIFT; 905 prev = i; 906 } 907 } 908 ld[n] = (visEnd - prev) | levelBits; 909 if (visEnd < end) { 910 ld[++n] = visEnd - start; 911 ld[++n] = (end - visEnd) | (baseLevel << RUN_LEVEL_SHIFT); 912 } 913 } 914 915 // See if we need to swap any runs. 916 // If the min level run direction doesn't match the base 917 // direction, we always need to swap (at this point 918 // we have more than one run). 919 // Otherwise, we don't need to swap the lowest level. 920 // Since there are no logically adjacent runs at the same 921 // level, if the max level is the same as the (new) min 922 // level, we have a series of alternating levels that 923 // is already in order, so there's no more to do. 924 // 925 boolean swap; 926 if ((minLevel & 1) == baseLevel) { 927 minLevel += 1; 928 swap = maxLevel > minLevel; 929 } else { 930 swap = runCount > 1; 931 } 932 if (swap) { 933 for (int level = maxLevel - 1; level >= minLevel; --level) { 934 for (int i = 0; i < ld.length; i += 2) { 935 if (chdirs[startOff + ld[i]] >= level) { 936 int e = i + 2; 937 while (e < ld.length && chdirs[startOff + ld[e]] >= level) { 938 e += 2; 939 } 940 for (int low = i, hi = e - 2; low < hi; low += 2, hi -= 2) { 941 int x = ld[low]; ld[low] = ld[hi]; ld[hi] = x; 942 x = ld[low+1]; ld[low+1] = ld[hi+1]; ld[hi+1] = x; 943 } 944 i = e + 2; 945 } 946 } 947 } 948 } 949 linedirs = new Directions(ld); 950 } 951 952 mLineDirections[j] = linedirs; 953 954 // If ellipsize is in marquee mode, do not apply ellipsis on the first line 955 if (ellipsize != null && (ellipsize != TextUtils.TruncateAt.MARQUEE || j != 0)) { 956 calculateEllipsis(start, end, widths, widstart, widoff, 957 ellipsiswidth, ellipsize, j, 958 textwidth, paint); 959 } 960 } 961 962 mLineCount++; 963 return v; 964 } 965 966 private void calculateEllipsis(int linestart, int lineend, 967 float[] widths, int widstart, int widoff, 968 float avail, TextUtils.TruncateAt where, 969 int line, float textwidth, TextPaint paint) { 970 int len = lineend - linestart; 971 972 if (textwidth <= avail) { 973 // Everything fits! 974 mLines[mColumns * line + ELLIPSIS_START] = 0; 975 mLines[mColumns * line + ELLIPSIS_COUNT] = 0; 976 return; 977 } 978 979 float ellipsiswid = paint.measureText("\u2026"); 980 int ellipsisStart, ellipsisCount; 981 982 if (where == TextUtils.TruncateAt.START) { 983 float sum = 0; 984 int i; 985 986 for (i = len; i >= 0; i--) { 987 float w = widths[i - 1 + linestart - widstart + widoff]; 988 989 if (w + sum + ellipsiswid > avail) { 990 break; 991 } 992 993 sum += w; 994 } 995 996 ellipsisStart = 0; 997 ellipsisCount = i; 998 } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE) { 999 float sum = 0; 1000 int i; 1001 1002 for (i = 0; i < len; i++) { 1003 float w = widths[i + linestart - widstart + widoff]; 1004 1005 if (w + sum + ellipsiswid > avail) { 1006 break; 1007 } 1008 1009 sum += w; 1010 } 1011 1012 ellipsisStart = i; 1013 ellipsisCount = len - i; 1014 } else /* where = TextUtils.TruncateAt.MIDDLE */ { 1015 float lsum = 0, rsum = 0; 1016 int left = 0, right = len; 1017 1018 float ravail = (avail - ellipsiswid) / 2; 1019 for (right = len; right >= 0; right--) { 1020 float w = widths[right - 1 + linestart - widstart + widoff]; 1021 1022 if (w + rsum > ravail) { 1023 break; 1024 } 1025 1026 rsum += w; 1027 } 1028 1029 float lavail = avail - ellipsiswid - rsum; 1030 for (left = 0; left < right; left++) { 1031 float w = widths[left + linestart - widstart + widoff]; 1032 1033 if (w + lsum > lavail) { 1034 break; 1035 } 1036 1037 lsum += w; 1038 } 1039 1040 ellipsisStart = left; 1041 ellipsisCount = right - left; 1042 } 1043 1044 mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart; 1045 mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount; 1046 } 1047 1048 // Override the baseclass so we can directly access our members, 1049 // rather than relying on member functions. 1050 // The logic mirrors that of Layout.getLineForVertical 1051 // FIXME: It may be faster to do a linear search for layouts without many lines. 1052 public int getLineForVertical(int vertical) { 1053 int high = mLineCount; 1054 int low = -1; 1055 int guess; 1056 int[] lines = mLines; 1057 while (high - low > 1) { 1058 guess = (high + low) >> 1; 1059 if (lines[mColumns * guess + TOP] > vertical){ 1060 high = guess; 1061 } else { 1062 low = guess; 1063 } 1064 } 1065 if (low < 0) { 1066 return 0; 1067 } else { 1068 return low; 1069 } 1070 } 1071 1072 public int getLineCount() { 1073 return mLineCount; 1074 } 1075 1076 public int getLineTop(int line) { 1077 return mLines[mColumns * line + TOP]; 1078 } 1079 1080 public int getLineDescent(int line) { 1081 return mLines[mColumns * line + DESCENT]; 1082 } 1083 1084 public int getLineStart(int line) { 1085 return mLines[mColumns * line + START] & START_MASK; 1086 } 1087 1088 public int getParagraphDirection(int line) { 1089 return mLines[mColumns * line + DIR] >> DIR_SHIFT; 1090 } 1091 1092 public boolean getLineContainsTab(int line) { 1093 return (mLines[mColumns * line + TAB] & TAB_MASK) != 0; 1094 } 1095 1096 public final Directions getLineDirections(int line) { 1097 return mLineDirections[line]; 1098 } 1099 1100 public int getTopPadding() { 1101 return mTopPadding; 1102 } 1103 1104 public int getBottomPadding() { 1105 return mBottomPadding; 1106 } 1107 1108 @Override 1109 public int getEllipsisCount(int line) { 1110 if (mColumns < COLUMNS_ELLIPSIZE) { 1111 return 0; 1112 } 1113 1114 return mLines[mColumns * line + ELLIPSIS_COUNT]; 1115 } 1116 1117 @Override 1118 public int getEllipsisStart(int line) { 1119 if (mColumns < COLUMNS_ELLIPSIZE) { 1120 return 0; 1121 } 1122 1123 return mLines[mColumns * line + ELLIPSIS_START]; 1124 } 1125 1126 @Override 1127 public int getEllipsizedWidth() { 1128 return mEllipsizedWidth; 1129 } 1130 1131 private int mLineCount; 1132 private int mTopPadding, mBottomPadding; 1133 private int mColumns; 1134 private int mEllipsizedWidth; 1135 1136 private static final int COLUMNS_NORMAL = 3; 1137 private static final int COLUMNS_ELLIPSIZE = 5; 1138 private static final int START = 0; 1139 private static final int DIR = START; 1140 private static final int TAB = START; 1141 private static final int TOP = 1; 1142 private static final int DESCENT = 2; 1143 private static final int ELLIPSIS_START = 3; 1144 private static final int ELLIPSIS_COUNT = 4; 1145 1146 private int[] mLines; 1147 private Directions[] mLineDirections; 1148 1149 private static final int START_MASK = 0x1FFFFFFF; 1150 private static final int DIR_MASK = 0xC0000000; 1151 private static final int DIR_SHIFT = 30; 1152 private static final int TAB_MASK = 0x20000000; 1153 1154 private static final char FIRST_RIGHT_TO_LEFT = '\u0590'; 1155 1156 /* 1157 * These are reused across calls to generate() 1158 */ 1159 private byte[] mChdirs; 1160 private char[] mChs; 1161 private float[] mWidths; 1162 private Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt(); 1163} 1164