StaticLayout.java revision 423f0e4205e3c49c6a87b389fa6025772aa7010c
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 */ 623 624 if (c == ' ' || c == '\t' || 625 ((c == '.' || c == ',' || c == ':' || c == ';') && 626 (j - 1 < here || !Character.isDigit(chs[j - 1 - start])) && 627 (j + 1 >= next || !Character.isDigit(chs[j + 1 - start]))) || 628 ((c == '/' || c == '-') && 629 (j + 1 >= next || !Character.isDigit(chs[j + 1 - start]))) || 630 (c >= FIRST_CJK && isIdeographic(c) && 631 j + 1 < next && isIdeographic(chs[j + 1 - start]))) { 632 okwidth = w; 633 ok = j + 1; 634 635 if (fittop < oktop) 636 oktop = fittop; 637 if (fitascent < okascent) 638 okascent = fitascent; 639 if (fitdescent > okdescent) 640 okdescent = fitdescent; 641 if (fitbottom > okbottom) 642 okbottom = fitbottom; 643 } 644 } else if (breakOnlyAtSpaces) { 645 if (ok != here) { 646 // Log.e("text", "output ok " + here + " to " +ok); 647 648 while (ok < next && chs[ok - start] == ' ') { 649 ok++; 650 } 651 652 v = out(source, 653 here, ok, 654 okascent, okdescent, oktop, okbottom, 655 v, 656 spacingmult, spacingadd, chooseht, 657 choosehtv, fm, tab, 658 needMultiply, start, chdirs, dir, easy, 659 ok == bufend, includepad, trackpad, 660 widths, start, end - start, 661 where, ellipsizedWidth, okwidth, 662 paint); 663 664 here = ok; 665 } else { 666 // Act like it fit even though it didn't. 667 668 fitwidth = w; 669 fit = j + 1; 670 671 if (fmtop < fittop) 672 fittop = fmtop; 673 if (fmascent < fitascent) 674 fitascent = fmascent; 675 if (fmdescent > fitdescent) 676 fitdescent = fmdescent; 677 if (fmbottom > fitbottom) 678 fitbottom = fmbottom; 679 } 680 } else { 681 if (ok != here) { 682 // Log.e("text", "output ok " + here + " to " +ok); 683 684 while (ok < next && chs[ok - start] == ' ') { 685 ok++; 686 } 687 688 v = out(source, 689 here, ok, 690 okascent, okdescent, oktop, okbottom, 691 v, 692 spacingmult, spacingadd, chooseht, 693 choosehtv, fm, tab, 694 needMultiply, start, chdirs, dir, easy, 695 ok == bufend, includepad, trackpad, 696 widths, start, end - start, 697 where, ellipsizedWidth, okwidth, 698 paint); 699 700 here = ok; 701 } else if (fit != here) { 702 // Log.e("text", "output fit " + here + " to " +fit); 703 v = out(source, 704 here, fit, 705 fitascent, fitdescent, 706 fittop, fitbottom, 707 v, 708 spacingmult, spacingadd, chooseht, 709 choosehtv, fm, tab, 710 needMultiply, start, chdirs, dir, easy, 711 fit == bufend, includepad, trackpad, 712 widths, start, end - start, 713 where, ellipsizedWidth, fitwidth, 714 paint); 715 716 here = fit; 717 } else { 718 // Log.e("text", "output one " + here + " to " +(here + 1)); 719 measureText(paint, mWorkPaint, 720 source, here, here + 1, fm, tab, 721 null); 722 723 v = out(source, 724 here, here+1, 725 fm.ascent, fm.descent, 726 fm.top, fm.bottom, 727 v, 728 spacingmult, spacingadd, chooseht, 729 choosehtv, fm, tab, 730 needMultiply, start, chdirs, dir, easy, 731 here + 1 == bufend, includepad, 732 trackpad, 733 widths, start, end - start, 734 where, ellipsizedWidth, 735 widths[here - start], paint); 736 737 here = here + 1; 738 } 739 740 if (here < i) { 741 j = next = here; // must remeasure 742 } else { 743 j = here - 1; // continue looping 744 } 745 746 ok = fit = here; 747 w = 0; 748 fitascent = fitdescent = fittop = fitbottom = 0; 749 okascent = okdescent = oktop = okbottom = 0; 750 751 width = restwidth; 752 } 753 } 754 } 755 756 if (end != here) { 757 if ((fittop | fitbottom | fitdescent | fitascent) == 0) { 758 paint.getFontMetricsInt(fm); 759 760 fittop = fm.top; 761 fitbottom = fm.bottom; 762 fitascent = fm.ascent; 763 fitdescent = fm.descent; 764 } 765 766 // Log.e("text", "output rest " + here + " to " + end); 767 768 v = out(source, 769 here, end, fitascent, fitdescent, 770 fittop, fitbottom, 771 v, 772 spacingmult, spacingadd, chooseht, 773 choosehtv, fm, tab, 774 needMultiply, start, chdirs, dir, easy, 775 end == bufend, includepad, trackpad, 776 widths, start, end - start, 777 where, ellipsizedWidth, w, paint); 778 } 779 780 start = end; 781 782 if (end == bufend) 783 break; 784 } 785 786 if (bufend == bufstart || source.charAt(bufend - 1) == '\n') { 787 // Log.e("text", "output last " + bufend); 788 789 paint.getFontMetricsInt(fm); 790 791 v = out(source, 792 bufend, bufend, fm.ascent, fm.descent, 793 fm.top, fm.bottom, 794 v, 795 spacingmult, spacingadd, null, 796 null, fm, false, 797 needMultiply, bufend, chdirs, DEFAULT_DIR, true, 798 true, includepad, trackpad, 799 widths, bufstart, 0, 800 where, ellipsizedWidth, 0, paint); 801 } 802 } 803 804 private static final char FIRST_CJK = '\u2E80'; 805 /** 806 * Returns true if the specified character is one of those specified 807 * as being Ideographic (class ID) by the Unicode Line Breaking Algorithm 808 * (http://www.unicode.org/unicode/reports/tr14/), and is therefore OK 809 * to break between a pair of. 810 */ 811 private static final boolean isIdeographic(char c) { 812 if (c >= '\u2E80' && c <= '\u2FFF') { 813 return true; // CJK, KANGXI RADICALS, DESCRIPTION SYMBOLS 814 } 815 if (c == '\u3000') { 816 return true; // IDEOGRAPHIC SPACE 817 } 818 if (c >= '\u3040' && c <= '\u309F') { 819 return true; // Hiragana (except small characters) 820 } 821 if (c >= '\u30A0' && c <= '\u30FF') { 822 return true; // Katakana (except small characters) 823 } 824 if (c >= '\u3400' && c <= '\u4DB5') { 825 return true; // CJK UNIFIED IDEOGRAPHS EXTENSION A 826 } 827 if (c >= '\u4E00' && c <= '\u9FBB') { 828 return true; // CJK UNIFIED IDEOGRAPHS 829 } 830 if (c >= '\uF900' && c <= '\uFAD9') { 831 return true; // CJK COMPATIBILITY IDEOGRAPHS 832 } 833 if (c >= '\uA000' && c <= '\uA48F') { 834 return true; // YI SYLLABLES 835 } 836 if (c >= '\uA490' && c <= '\uA4CF') { 837 return true; // YI RADICALS 838 } 839 if (c >= '\uFE62' && c <= '\uFE66') { 840 return true; // SMALL PLUS SIGN to SMALL EQUALS SIGN 841 } 842 if (c >= '\uFF10' && c <= '\uFF19') { 843 return true; // WIDE DIGITS 844 } 845 846 return false; 847 } 848 849/* 850 private static void dump(byte[] data, int count, String label) { 851 if (false) { 852 System.out.print(label); 853 854 for (int i = 0; i < count; i++) 855 System.out.print(" " + data[i]); 856 857 System.out.println(); 858 } 859 } 860*/ 861 862 private static int getFit(TextPaint paint, 863 TextPaint workPaint, 864 CharSequence text, int start, int end, 865 float wid) { 866 int high = end + 1, low = start - 1, guess; 867 868 while (high - low > 1) { 869 guess = (high + low) / 2; 870 871 if (measureText(paint, workPaint, 872 text, start, guess, null, true, null) > wid) 873 high = guess; 874 else 875 low = guess; 876 } 877 878 if (low < start) 879 return start; 880 else 881 return low; 882 } 883 884 private int out(CharSequence text, int start, int end, 885 int above, int below, int top, int bottom, int v, 886 float spacingmult, float spacingadd, 887 LineHeightSpan[] chooseht, int[] choosehtv, 888 Paint.FontMetricsInt fm, boolean tab, 889 boolean needMultiply, int pstart, byte[] chdirs, 890 int dir, boolean easy, boolean last, 891 boolean includepad, boolean trackpad, 892 float[] widths, int widstart, int widoff, 893 TextUtils.TruncateAt ellipsize, float ellipsiswidth, 894 float textwidth, TextPaint paint) { 895 int j = mLineCount; 896 int off = j * mColumns; 897 int want = off + mColumns + TOP; 898 int[] lines = mLines; 899 900 // Log.e("text", "line " + start + " to " + end + (last ? "===" : "")); 901 902 if (want >= lines.length) { 903 int nlen = ArrayUtils.idealIntArraySize(want + 1); 904 int[] grow = new int[nlen]; 905 System.arraycopy(lines, 0, grow, 0, lines.length); 906 mLines = grow; 907 lines = grow; 908 909 Directions[] grow2 = new Directions[nlen]; 910 System.arraycopy(mLineDirections, 0, grow2, 0, 911 mLineDirections.length); 912 mLineDirections = grow2; 913 } 914 915 if (chooseht != null) { 916 fm.ascent = above; 917 fm.descent = below; 918 fm.top = top; 919 fm.bottom = bottom; 920 921 for (int i = 0; i < chooseht.length; i++) { 922 chooseht[i].chooseHeight(text, start, end, choosehtv[i], v, fm); 923 } 924 925 above = fm.ascent; 926 below = fm.descent; 927 top = fm.top; 928 bottom = fm.bottom; 929 } 930 931 if (j == 0) { 932 if (trackpad) { 933 mTopPadding = top - above; 934 } 935 936 if (includepad) { 937 above = top; 938 } 939 } 940 if (last) { 941 if (trackpad) { 942 mBottomPadding = bottom - below; 943 } 944 945 if (includepad) { 946 below = bottom; 947 } 948 } 949 950 int extra; 951 952 if (needMultiply) { 953 extra = (int) ((below - above) * (spacingmult - 1) 954 + spacingadd + 0.5); 955 } else { 956 extra = 0; 957 } 958 959 lines[off + START] = start; 960 lines[off + TOP] = v; 961 lines[off + DESCENT] = below + extra; 962 963 v += (below - above) + extra; 964 lines[off + mColumns + START] = end; 965 lines[off + mColumns + TOP] = v; 966 967 if (tab) 968 lines[off + TAB] |= TAB_MASK; 969 970 { 971 lines[off + DIR] |= dir << DIR_SHIFT; 972 973 int cur = Character.DIRECTIONALITY_LEFT_TO_RIGHT; 974 int count = 0; 975 976 if (!easy) { 977 for (int k = start; k < end; k++) { 978 if (chdirs[k - pstart] != cur) { 979 count++; 980 cur = chdirs[k - pstart]; 981 } 982 } 983 } 984 985 Directions linedirs; 986 987 if (count == 0) { 988 linedirs = DIRS_ALL_LEFT_TO_RIGHT; 989 } else { 990 short[] ld = new short[count + 1]; 991 992 cur = Character.DIRECTIONALITY_LEFT_TO_RIGHT; 993 count = 0; 994 int here = start; 995 996 for (int k = start; k < end; k++) { 997 if (chdirs[k - pstart] != cur) { 998 // XXX check to make sure we don't 999 // overflow short 1000 ld[count++] = (short) (k - here); 1001 cur = chdirs[k - pstart]; 1002 here = k; 1003 } 1004 } 1005 1006 ld[count] = (short) (end - here); 1007 1008 if (count == 1 && ld[0] == 0) { 1009 linedirs = DIRS_ALL_RIGHT_TO_LEFT; 1010 } else { 1011 linedirs = new Directions(ld); 1012 } 1013 } 1014 1015 mLineDirections[j] = linedirs; 1016 1017 // If ellipsize is in marquee mode, do not apply ellipsis on the first line 1018 if (ellipsize != null && (ellipsize != TextUtils.TruncateAt.MARQUEE || j != 0)) { 1019 calculateEllipsis(start, end, widths, widstart, widoff, 1020 ellipsiswidth, ellipsize, j, 1021 textwidth, paint); 1022 } 1023 } 1024 1025 mLineCount++; 1026 return v; 1027 } 1028 1029 private void calculateEllipsis(int linestart, int lineend, 1030 float[] widths, int widstart, int widoff, 1031 float avail, TextUtils.TruncateAt where, 1032 int line, float textwidth, TextPaint paint) { 1033 int len = lineend - linestart; 1034 1035 if (textwidth <= avail) { 1036 // Everything fits! 1037 mLines[mColumns * line + ELLIPSIS_START] = 0; 1038 mLines[mColumns * line + ELLIPSIS_COUNT] = 0; 1039 return; 1040 } 1041 1042 float ellipsiswid = paint.measureText("\u2026"); 1043 int ellipsisStart, ellipsisCount; 1044 1045 if (where == TextUtils.TruncateAt.START) { 1046 float sum = 0; 1047 int i; 1048 1049 for (i = len; i >= 0; i--) { 1050 float w = widths[i - 1 + linestart - widstart + widoff]; 1051 1052 if (w + sum + ellipsiswid > avail) { 1053 break; 1054 } 1055 1056 sum += w; 1057 } 1058 1059 ellipsisStart = 0; 1060 ellipsisCount = i; 1061 } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE) { 1062 float sum = 0; 1063 int i; 1064 1065 for (i = 0; i < len; i++) { 1066 float w = widths[i + linestart - widstart + widoff]; 1067 1068 if (w + sum + ellipsiswid > avail) { 1069 break; 1070 } 1071 1072 sum += w; 1073 } 1074 1075 ellipsisStart = i; 1076 ellipsisCount = len - i; 1077 } else /* where = TextUtils.TruncateAt.MIDDLE */ { 1078 float lsum = 0, rsum = 0; 1079 int left = 0, right = len; 1080 1081 float ravail = (avail - ellipsiswid) / 2; 1082 for (right = len; right >= 0; right--) { 1083 float w = widths[right - 1 + linestart - widstart + widoff]; 1084 1085 if (w + rsum > ravail) { 1086 break; 1087 } 1088 1089 rsum += w; 1090 } 1091 1092 float lavail = avail - ellipsiswid - rsum; 1093 for (left = 0; left < right; left++) { 1094 float w = widths[left + linestart - widstart + widoff]; 1095 1096 if (w + lsum > lavail) { 1097 break; 1098 } 1099 1100 lsum += w; 1101 } 1102 1103 ellipsisStart = left; 1104 ellipsisCount = right - left; 1105 } 1106 1107 mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart; 1108 mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount; 1109 } 1110 1111 // Override the baseclass so we can directly access our members, 1112 // rather than relying on member functions. 1113 // The logic mirrors that of Layout.getLineForVertical 1114 // FIXME: It may be faster to do a linear search for layouts without many lines. 1115 public int getLineForVertical(int vertical) { 1116 int high = mLineCount; 1117 int low = -1; 1118 int guess; 1119 int[] lines = mLines; 1120 while (high - low > 1) { 1121 guess = (high + low) >> 1; 1122 if (lines[mColumns * guess + TOP] > vertical){ 1123 high = guess; 1124 } else { 1125 low = guess; 1126 } 1127 } 1128 if (low < 0) { 1129 return 0; 1130 } else { 1131 return low; 1132 } 1133 } 1134 1135 public int getLineCount() { 1136 return mLineCount; 1137 } 1138 1139 public int getLineTop(int line) { 1140 return mLines[mColumns * line + TOP]; 1141 } 1142 1143 public int getLineDescent(int line) { 1144 return mLines[mColumns * line + DESCENT]; 1145 } 1146 1147 public int getLineStart(int line) { 1148 return mLines[mColumns * line + START] & START_MASK; 1149 } 1150 1151 public int getParagraphDirection(int line) { 1152 return mLines[mColumns * line + DIR] >> DIR_SHIFT; 1153 } 1154 1155 public boolean getLineContainsTab(int line) { 1156 return (mLines[mColumns * line + TAB] & TAB_MASK) != 0; 1157 } 1158 1159 public final Directions getLineDirections(int line) { 1160 return mLineDirections[line]; 1161 } 1162 1163 public int getTopPadding() { 1164 return mTopPadding; 1165 } 1166 1167 public int getBottomPadding() { 1168 return mBottomPadding; 1169 } 1170 1171 @Override 1172 public int getEllipsisCount(int line) { 1173 if (mColumns < COLUMNS_ELLIPSIZE) { 1174 return 0; 1175 } 1176 1177 return mLines[mColumns * line + ELLIPSIS_COUNT]; 1178 } 1179 1180 @Override 1181 public int getEllipsisStart(int line) { 1182 if (mColumns < COLUMNS_ELLIPSIZE) { 1183 return 0; 1184 } 1185 1186 return mLines[mColumns * line + ELLIPSIS_START]; 1187 } 1188 1189 @Override 1190 public int getEllipsizedWidth() { 1191 return mEllipsizedWidth; 1192 } 1193 1194 private int mLineCount; 1195 private int mTopPadding, mBottomPadding; 1196 private int mColumns; 1197 private int mEllipsizedWidth; 1198 1199 private static final int COLUMNS_NORMAL = 3; 1200 private static final int COLUMNS_ELLIPSIZE = 5; 1201 private static final int START = 0; 1202 private static final int DIR = START; 1203 private static final int TAB = START; 1204 private static final int TOP = 1; 1205 private static final int DESCENT = 2; 1206 private static final int ELLIPSIS_START = 3; 1207 private static final int ELLIPSIS_COUNT = 4; 1208 1209 private int[] mLines; 1210 private Directions[] mLineDirections; 1211 1212 private static final int START_MASK = 0x1FFFFFFF; 1213 private static final int DIR_MASK = 0xC0000000; 1214 private static final int DIR_SHIFT = 30; 1215 private static final int TAB_MASK = 0x20000000; 1216 1217 private static final char FIRST_RIGHT_TO_LEFT = '\u0590'; 1218 1219 /* 1220 * These are reused across calls to generate() 1221 */ 1222 private byte[] mChdirs; 1223 private char[] mChs; 1224 private float[] mWidths; 1225 private Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt(); 1226} 1227