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