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