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