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