StaticLayout.java revision f013e1afd1e68af5e3b868c26a653bbfb39538f8
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.Paint; 20import com.android.internal.util.ArrayUtils; 21import android.util.Log; 22import android.text.style.LeadingMarginSpan; 23import android.text.style.LineHeightSpan; 24import android.text.style.MetricAffectingSpan; 25import android.text.style.ReplacementSpan; 26 27/** 28 * StaticLayout is a Layout for text that will not be edited after it 29 * is laid out. Use {@link DynamicLayout} for text that may change. 30 * <p>This is used by widgets to control text layout. You should not need 31 * to use this class directly unless you are implementing your own widget 32 * or custom display object, or would be tempted to call 33 * {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int, float, float, android.graphics.Paint) 34 * Canvas.drawText()} directly.</p> 35 */ 36public class 37StaticLayout 38extends Layout 39{ 40 public StaticLayout(CharSequence source, TextPaint paint, 41 int width, 42 Alignment align, float spacingmult, float spacingadd, 43 boolean includepad) { 44 this(source, 0, source.length(), paint, width, align, 45 spacingmult, spacingadd, includepad); 46 } 47 48 public StaticLayout(CharSequence source, int bufstart, int bufend, 49 TextPaint paint, int outerwidth, 50 Alignment align, 51 float spacingmult, float spacingadd, 52 boolean includepad) { 53 this(source, bufstart, bufend, paint, outerwidth, align, 54 spacingmult, spacingadd, includepad, null, 0); 55 } 56 57 public StaticLayout(CharSequence source, int bufstart, int bufend, 58 TextPaint paint, int outerwidth, 59 Alignment align, 60 float spacingmult, float spacingadd, 61 boolean includepad, 62 TextUtils.TruncateAt ellipsize, int ellipsizedWidth) { 63 super((ellipsize == null) 64 ? source 65 : (source instanceof Spanned) 66 ? new SpannedEllipsizer(source) 67 : new Ellipsizer(source), 68 paint, outerwidth, align, spacingmult, spacingadd); 69 70 /* 71 * This is annoying, but we can't refer to the layout until 72 * superclass construction is finished, and the superclass 73 * constructor wants the reference to the display text. 74 * 75 * This will break if the superclass constructor ever actually 76 * cares about the content instead of just holding the reference. 77 */ 78 if (ellipsize != null) { 79 Ellipsizer e = (Ellipsizer) getText(); 80 81 e.mLayout = this; 82 e.mWidth = ellipsizedWidth; 83 e.mMethod = ellipsize; 84 mEllipsizedWidth = ellipsizedWidth; 85 86 mColumns = COLUMNS_ELLIPSIZE; 87 } else { 88 mColumns = COLUMNS_NORMAL; 89 mEllipsizedWidth = outerwidth; 90 } 91 92 mLines = new int[ArrayUtils.idealIntArraySize(2 * mColumns)]; 93 mLineDirections = new Directions[ 94 ArrayUtils.idealIntArraySize(2 * mColumns)]; 95 96 generate(source, bufstart, bufend, paint, outerwidth, align, 97 spacingmult, spacingadd, includepad, includepad, 98 ellipsize != null, ellipsizedWidth, ellipsize); 99 100 mChdirs = null; 101 mChs = null; 102 mWidths = null; 103 mFontMetricsInt = null; 104 } 105 106 /* package */ StaticLayout(boolean ellipsize) { 107 super(null, null, 0, null, 0, 0); 108 109 mColumns = COLUMNS_ELLIPSIZE; 110 mLines = new int[ArrayUtils.idealIntArraySize(2 * mColumns)]; 111 mLineDirections = new Directions[ 112 ArrayUtils.idealIntArraySize(2 * mColumns)]; 113 } 114 115 /* package */ void generate(CharSequence source, int bufstart, int bufend, 116 TextPaint paint, int outerwidth, 117 Alignment align, 118 float spacingmult, float spacingadd, 119 boolean includepad, boolean trackpad, 120 boolean breakOnlyAtSpaces, 121 float ellipsizedWidth, TextUtils.TruncateAt where) { 122 mLineCount = 0; 123 124 int v = 0; 125 boolean needMultiply = (spacingmult != 1 || spacingadd != 0); 126 127 Paint.FontMetricsInt fm = mFontMetricsInt; 128 int[] choosehtv = null; 129 130 int end = TextUtils.indexOf(source, '\n', bufstart, bufend); 131 int bufsiz = end >= 0 ? end - bufstart : bufend - bufstart; 132 boolean first = true; 133 134 if (mChdirs == null) { 135 mChdirs = new byte[ArrayUtils.idealByteArraySize(bufsiz + 1)]; 136 mChs = new char[ArrayUtils.idealCharArraySize(bufsiz + 1)]; 137 mWidths = new float[ArrayUtils.idealIntArraySize((bufsiz + 1) * 2)]; 138 } 139 140 byte[] chdirs = mChdirs; 141 char[] chs = mChs; 142 float[] widths = mWidths; 143 144 AlteredCharSequence alter = null; 145 Spanned spanned = null; 146 147 if (source instanceof Spanned) 148 spanned = (Spanned) source; 149 150 int DEFAULT_DIR = DIR_LEFT_TO_RIGHT; // XXX 151 152 for (int start = bufstart; start <= bufend; start = end) { 153 if (first) 154 first = false; 155 else 156 end = TextUtils.indexOf(source, '\n', start, bufend); 157 158 if (end < 0) 159 end = bufend; 160 else 161 end++; 162 163 int firstwidth = outerwidth; 164 int restwidth = outerwidth; 165 166 LineHeightSpan[] chooseht = null; 167 168 if (spanned != null) { 169 LeadingMarginSpan[] sp; 170 171 sp = spanned.getSpans(start, end, LeadingMarginSpan.class); 172 for (int i = 0; i < sp.length; i++) { 173 firstwidth -= sp[i].getLeadingMargin(true); 174 restwidth -= sp[i].getLeadingMargin(false); 175 } 176 177 chooseht = spanned.getSpans(start, end, LineHeightSpan.class); 178 179 if (chooseht.length != 0) { 180 if (choosehtv == null || 181 choosehtv.length < chooseht.length) { 182 choosehtv = new int[ArrayUtils.idealIntArraySize( 183 chooseht.length)]; 184 } 185 186 for (int i = 0; i < chooseht.length; i++) { 187 int o = spanned.getSpanStart(chooseht[i]); 188 189 if (o < start) { 190 // starts in this layout, before the 191 // current paragraph 192 193 choosehtv[i] = getLineTop(getLineForOffset(o)); 194 } else { 195 // starts in this paragraph 196 197 choosehtv[i] = v; 198 } 199 } 200 } 201 } 202 203 if (end - start > chdirs.length) { 204 chdirs = new byte[ArrayUtils.idealByteArraySize(end - start)]; 205 mChdirs = chdirs; 206 } 207 if (end - start > chs.length) { 208 chs = new char[ArrayUtils.idealCharArraySize(end - start)]; 209 mChs = chs; 210 } 211 if ((end - start) * 2 > widths.length) { 212 widths = new float[ArrayUtils.idealIntArraySize((end - start) * 2)]; 213 mWidths = widths; 214 } 215 216 TextUtils.getChars(source, start, end, chs, 0); 217 final int n = end - start; 218 219 boolean easy = true; 220 boolean altered = false; 221 int dir = DEFAULT_DIR; // XXX 222 223 for (int i = 0; i < n; i++) { 224 if (chs[i] >= FIRST_RIGHT_TO_LEFT) { 225 easy = false; 226 break; 227 } 228 } 229 230 if (!easy) { 231 AndroidCharacter.getDirectionalities(chs, chdirs, end - start); 232 233 /* 234 * Determine primary paragraph direction 235 */ 236 237 for (int j = start; j < end; j++) { 238 int d = chdirs[j - start]; 239 240 if (d == Character.DIRECTIONALITY_LEFT_TO_RIGHT) { 241 dir = DIR_LEFT_TO_RIGHT; 242 break; 243 } 244 if (d == Character.DIRECTIONALITY_RIGHT_TO_LEFT) { 245 dir = DIR_RIGHT_TO_LEFT; 246 break; 247 } 248 } 249 250 /* 251 * XXX Explicit overrides should go here 252 */ 253 254 /* 255 * Weak type resolution 256 */ 257 258 final byte SOR = dir == DIR_LEFT_TO_RIGHT ? 259 Character.DIRECTIONALITY_LEFT_TO_RIGHT : 260 Character.DIRECTIONALITY_RIGHT_TO_LEFT; 261 262 // dump(chdirs, n, "initial"); 263 264 // W1 non spacing marks 265 for (int j = 0; j < n; j++) { 266 if (chdirs[j] == Character.NON_SPACING_MARK) { 267 if (j == 0) 268 chdirs[j] = SOR; 269 else 270 chdirs[j] = chdirs[j - 1]; 271 } 272 } 273 274 // dump(chdirs, n, "W1"); 275 276 // W2 european numbers 277 byte cur = SOR; 278 for (int j = 0; j < n; j++) { 279 byte d = chdirs[j]; 280 281 if (d == Character.DIRECTIONALITY_LEFT_TO_RIGHT || 282 d == Character.DIRECTIONALITY_RIGHT_TO_LEFT || 283 d == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC) 284 cur = d; 285 else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER) { 286 if (cur == 287 Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC) 288 chdirs[j] = Character.DIRECTIONALITY_ARABIC_NUMBER; 289 } 290 } 291 292 // dump(chdirs, n, "W2"); 293 294 // W3 arabic letters 295 for (int j = 0; j < n; j++) { 296 if (chdirs[j] == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC) 297 chdirs[j] = Character.DIRECTIONALITY_RIGHT_TO_LEFT; 298 } 299 300 // dump(chdirs, n, "W3"); 301 302 // W4 single separator between numbers 303 for (int j = 1; j < n - 1; j++) { 304 byte d = chdirs[j]; 305 byte prev = chdirs[j - 1]; 306 byte next = chdirs[j + 1]; 307 308 if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_SEPARATOR) { 309 if (prev == Character.DIRECTIONALITY_EUROPEAN_NUMBER && 310 next == Character.DIRECTIONALITY_EUROPEAN_NUMBER) 311 chdirs[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER; 312 } else if (d == Character.DIRECTIONALITY_COMMON_NUMBER_SEPARATOR) { 313 if (prev == Character.DIRECTIONALITY_EUROPEAN_NUMBER && 314 next == Character.DIRECTIONALITY_EUROPEAN_NUMBER) 315 chdirs[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER; 316 if (prev == Character.DIRECTIONALITY_ARABIC_NUMBER && 317 next == Character.DIRECTIONALITY_ARABIC_NUMBER) 318 chdirs[j] = Character.DIRECTIONALITY_ARABIC_NUMBER; 319 } 320 } 321 322 // dump(chdirs, n, "W4"); 323 324 // W5 european number terminators 325 boolean adjacent = false; 326 for (int j = 0; j < n; j++) { 327 byte d = chdirs[j]; 328 329 if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER) 330 adjacent = true; 331 else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR && adjacent) 332 chdirs[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER; 333 else 334 adjacent = false; 335 } 336 337 //dump(chdirs, n, "W5"); 338 339 // W5 european number terminators part 2, 340 // W6 separators and terminators 341 adjacent = false; 342 for (int j = n - 1; j >= 0; j--) { 343 byte d = chdirs[j]; 344 345 if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER) 346 adjacent = true; 347 else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR) { 348 if (adjacent) 349 chdirs[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER; 350 else 351 chdirs[j] = Character.DIRECTIONALITY_OTHER_NEUTRALS; 352 } 353 else { 354 adjacent = false; 355 356 if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_SEPARATOR || 357 d == Character.DIRECTIONALITY_COMMON_NUMBER_SEPARATOR || 358 d == Character.DIRECTIONALITY_PARAGRAPH_SEPARATOR || 359 d == Character.DIRECTIONALITY_SEGMENT_SEPARATOR) 360 chdirs[j] = Character.DIRECTIONALITY_OTHER_NEUTRALS; 361 } 362 } 363 364 // dump(chdirs, n, "W6"); 365 366 // W7 strong direction of european numbers 367 cur = SOR; 368 for (int j = 0; j < n; j++) { 369 byte d = chdirs[j]; 370 371 if (d == SOR || 372 d == Character.DIRECTIONALITY_LEFT_TO_RIGHT || 373 d == Character.DIRECTIONALITY_RIGHT_TO_LEFT) 374 cur = d; 375 376 if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER) 377 chdirs[j] = cur; 378 } 379 380 // dump(chdirs, n, "W7"); 381 382 // N1, N2 neutrals 383 cur = SOR; 384 for (int j = 0; j < n; j++) { 385 byte d = chdirs[j]; 386 387 if (d == Character.DIRECTIONALITY_LEFT_TO_RIGHT || 388 d == Character.DIRECTIONALITY_RIGHT_TO_LEFT) { 389 cur = d; 390 } else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER || 391 d == Character.DIRECTIONALITY_ARABIC_NUMBER) { 392 cur = Character.DIRECTIONALITY_RIGHT_TO_LEFT; 393 } else { 394 byte dd = SOR; 395 int k; 396 397 for (k = j + 1; k < n; k++) { 398 dd = chdirs[k]; 399 400 if (dd == Character.DIRECTIONALITY_LEFT_TO_RIGHT || 401 dd == Character.DIRECTIONALITY_RIGHT_TO_LEFT) { 402 break; 403 } 404 if (dd == Character.DIRECTIONALITY_EUROPEAN_NUMBER || 405 dd == Character.DIRECTIONALITY_ARABIC_NUMBER) { 406 dd = Character.DIRECTIONALITY_RIGHT_TO_LEFT; 407 break; 408 } 409 } 410 411 for (int y = j; y < k; y++) { 412 if (dd == cur) 413 chdirs[y] = cur; 414 else 415 chdirs[y] = SOR; 416 } 417 418 j = k - 1; 419 } 420 } 421 422 // dump(chdirs, n, "final"); 423 424 // extra: enforce that all tabs go the primary direction 425 426 for (int j = 0; j < n; j++) { 427 if (chs[j] == '\t') 428 chdirs[j] = SOR; 429 } 430 431 // extra: enforce that object replacements go to the 432 // primary direction 433 // and that none of the underlying characters are treated 434 // as viable breakpoints 435 436 if (source instanceof Spanned) { 437 Spanned sp = (Spanned) source; 438 ReplacementSpan[] spans = sp.getSpans(start, end, ReplacementSpan.class); 439 440 for (int y = 0; y < spans.length; y++) { 441 int a = sp.getSpanStart(spans[y]); 442 int b = sp.getSpanEnd(spans[y]); 443 444 for (int x = a; x < b; x++) { 445 chdirs[x - start] = SOR; 446 chs[x - start] = '\uFFFC'; 447 } 448 } 449 } 450 451 // Do mirroring for right-to-left segments 452 453 for (int i = 0; i < n; i++) { 454 if (chdirs[i] == Character.DIRECTIONALITY_RIGHT_TO_LEFT) { 455 int j; 456 457 for (j = i; j < n; j++) { 458 if (chdirs[j] != 459 Character.DIRECTIONALITY_RIGHT_TO_LEFT) 460 break; 461 } 462 463 if (AndroidCharacter.mirror(chs, i, j - i)) 464 altered = true; 465 466 i = j - 1; 467 } 468 } 469 } 470 471 CharSequence sub; 472 473 if (altered) { 474 if (alter == null) 475 alter = AlteredCharSequence.make(source, chs, start, end); 476 else 477 alter.update(chs, start, end); 478 479 sub = alter; 480 } else { 481 sub = source; 482 } 483 484 int width = firstwidth; 485 486 float w = 0; 487 int here = start; 488 489 int ok = start; 490 float okwidth = w; 491 int okascent = 0, okdescent = 0, oktop = 0, okbottom = 0; 492 493 int fit = start; 494 float fitwidth = w; 495 int fitascent = 0, fitdescent = 0, fittop = 0, fitbottom = 0; 496 497 boolean tab = false; 498 499 int next; 500 for (int i = start; i < end; i = next) { 501 if (spanned == null) 502 next = end; 503 else 504 next = spanned.nextSpanTransition(i, end, 505 MetricAffectingSpan. 506 class); 507 508 if (spanned == null) { 509 paint.getTextWidths(sub, i, next, widths); 510 System.arraycopy(widths, 0, widths, 511 end - start + (i - start), next - i); 512 513 paint.getFontMetricsInt(fm); 514 } else { 515 mWorkPaint.baselineShift = 0; 516 517 Styled.getTextWidths(paint, mWorkPaint, 518 spanned, i, next, 519 widths, fm); 520 System.arraycopy(widths, 0, widths, 521 end - start + (i - start), next - i); 522 523 if (mWorkPaint.baselineShift < 0) { 524 fm.ascent += mWorkPaint.baselineShift; 525 fm.top += mWorkPaint.baselineShift; 526 } else { 527 fm.descent += mWorkPaint.baselineShift; 528 fm.bottom += mWorkPaint.baselineShift; 529 } 530 } 531 532 int fmtop = fm.top; 533 int fmbottom = fm.bottom; 534 int fmascent = fm.ascent; 535 int fmdescent = fm.descent; 536 537 if (false) { 538 StringBuilder sb = new StringBuilder(); 539 for (int j = i; j < next; j++) { 540 sb.append(widths[j - start + (end - start)]); 541 sb.append(' '); 542 } 543 544 Log.e("text", sb.toString()); 545 } 546 547 for (int j = i; j < next; j++) { 548 char c = chs[j - start]; 549 float before = w; 550 551 switch (c) { 552 case '\n': 553 break; 554 555 case '\t': 556 w = Layout.nextTab(sub, start, end, w, null); 557 tab = true; 558 break; 559 560 default: 561 w += widths[j - start + (end - start)]; 562 } 563 564 // Log.e("text", "was " + before + " now " + w + " after " + c + " within " + width); 565 566 if (w <= width) { 567 fitwidth = w; 568 fit = j + 1; 569 570 if (fmtop < fittop) 571 fittop = fmtop; 572 if (fmascent < fitascent) 573 fitascent = fmascent; 574 if (fmdescent > fitdescent) 575 fitdescent = fmdescent; 576 if (fmbottom > fitbottom) 577 fitbottom = fmbottom; 578 579 if (c == ' ' || c == '\t') { 580 okwidth = w; 581 ok = j + 1; 582 583 if (fittop < oktop) 584 oktop = fittop; 585 if (fitascent < okascent) 586 okascent = fitascent; 587 if (fitdescent > okdescent) 588 okdescent = fitdescent; 589 if (fitbottom > okbottom) 590 okbottom = fitbottom; 591 } 592 } else if (breakOnlyAtSpaces) { 593 if (ok != here) { 594 // Log.e("text", "output ok " + here + " to " +ok); 595 v = out(source, 596 here, ok, 597 okascent, okdescent, oktop, okbottom, 598 v, 599 spacingmult, spacingadd, chooseht, 600 choosehtv, fm, tab, 601 needMultiply, start, chdirs, dir, easy, 602 ok == bufend, includepad, trackpad, 603 widths, start, end - start, 604 where, ellipsizedWidth, okwidth, 605 paint); 606 607 here = ok; 608 } else { 609 // Act like it fit even though it didn't. 610 611 fitwidth = w; 612 fit = j + 1; 613 614 if (fmtop < fittop) 615 fittop = fmtop; 616 if (fmascent < fitascent) 617 fitascent = fmascent; 618 if (fmdescent > fitdescent) 619 fitdescent = fmdescent; 620 if (fmbottom > fitbottom) 621 fitbottom = fmbottom; 622 } 623 } else { 624 if (ok != here) { 625 // Log.e("text", "output ok " + here + " to " +ok); 626 v = out(source, 627 here, ok, 628 okascent, okdescent, oktop, okbottom, 629 v, 630 spacingmult, spacingadd, chooseht, 631 choosehtv, fm, tab, 632 needMultiply, start, chdirs, dir, easy, 633 ok == bufend, includepad, trackpad, 634 widths, start, end - start, 635 where, ellipsizedWidth, okwidth, 636 paint); 637 638 here = ok; 639 } else if (fit != here) { 640 // Log.e("text", "output fit " + here + " to " +fit); 641 v = out(source, 642 here, fit, 643 fitascent, fitdescent, 644 fittop, fitbottom, 645 v, 646 spacingmult, spacingadd, chooseht, 647 choosehtv, fm, tab, 648 needMultiply, start, chdirs, dir, easy, 649 fit == bufend, includepad, trackpad, 650 widths, start, end - start, 651 where, ellipsizedWidth, fitwidth, 652 paint); 653 654 here = fit; 655 } else { 656 // Log.e("text", "output one " + here + " to " +(here + 1)); 657 measureText(paint, mWorkPaint, 658 source, here, here + 1, fm, tab, 659 null); 660 661 v = out(source, 662 here, here+1, 663 fm.ascent, fm.descent, 664 fm.top, fm.bottom, 665 v, 666 spacingmult, spacingadd, chooseht, 667 choosehtv, fm, tab, 668 needMultiply, start, chdirs, dir, easy, 669 here + 1 == bufend, includepad, 670 trackpad, 671 widths, start, end - start, 672 where, ellipsizedWidth, 673 widths[here - start], paint); 674 675 here = here + 1; 676 } 677 678 if (here < i) { 679 j = next = here; // must remeasure 680 } else { 681 j = here - 1; // continue looping 682 } 683 684 ok = fit = here; 685 w = 0; 686 fitascent = fitdescent = fittop = fitbottom = 0; 687 okascent = okdescent = oktop = okbottom = 0; 688 689 width = restwidth; 690 } 691 } 692 } 693 694 if (end != here) { 695 if ((fittop | fitbottom | fitdescent | fitascent) == 0) { 696 paint.getFontMetricsInt(fm); 697 698 fittop = fm.top; 699 fitbottom = fm.bottom; 700 fitascent = fm.ascent; 701 fitdescent = fm.descent; 702 } 703 704 // Log.e("text", "output rest " + here + " to " + end); 705 706 v = out(source, 707 here, end, fitascent, fitdescent, 708 fittop, fitbottom, 709 v, 710 spacingmult, spacingadd, chooseht, 711 choosehtv, fm, tab, 712 needMultiply, start, chdirs, dir, easy, 713 end == bufend, includepad, trackpad, 714 widths, start, end - start, 715 where, ellipsizedWidth, w, paint); 716 } 717 718 start = end; 719 720 if (end == bufend) 721 break; 722 } 723 724 if (bufend == bufstart || source.charAt(bufend - 1) == '\n') { 725 // Log.e("text", "output last " + bufend); 726 727 paint.getFontMetricsInt(fm); 728 729 v = out(source, 730 bufend, bufend, fm.ascent, fm.descent, 731 fm.top, fm.bottom, 732 v, 733 spacingmult, spacingadd, null, 734 null, fm, false, 735 needMultiply, bufend, chdirs, DEFAULT_DIR, true, 736 true, includepad, trackpad, 737 widths, bufstart, 0, 738 where, ellipsizedWidth, 0, paint); 739 } 740 } 741 742/* 743 private static void dump(byte[] data, int count, String label) { 744 if (false) { 745 System.out.print(label); 746 747 for (int i = 0; i < count; i++) 748 System.out.print(" " + data[i]); 749 750 System.out.println(); 751 } 752 } 753*/ 754 755 private static int getFit(TextPaint paint, 756 TextPaint workPaint, 757 CharSequence text, int start, int end, 758 float wid) { 759 int high = end + 1, low = start - 1, guess; 760 761 while (high - low > 1) { 762 guess = (high + low) / 2; 763 764 if (measureText(paint, workPaint, 765 text, start, guess, null, true, null) > wid) 766 high = guess; 767 else 768 low = guess; 769 } 770 771 if (low < start) 772 return start; 773 else 774 return low; 775 } 776 777 private int out(CharSequence text, int start, int end, 778 int above, int below, int top, int bottom, int v, 779 float spacingmult, float spacingadd, 780 LineHeightSpan[] chooseht, int[] choosehtv, 781 Paint.FontMetricsInt fm, boolean tab, 782 boolean needMultiply, int pstart, byte[] chdirs, 783 int dir, boolean easy, boolean last, 784 boolean includepad, boolean trackpad, 785 float[] widths, int widstart, int widoff, 786 TextUtils.TruncateAt ellipsize, float ellipsiswidth, 787 float textwidth, TextPaint paint) { 788 int j = mLineCount; 789 int off = j * mColumns; 790 int want = off + mColumns + TOP; 791 int[] lines = mLines; 792 793 // Log.e("text", "line " + start + " to " + end + (last ? "===" : "")); 794 795 if (want >= lines.length) { 796 int nlen = ArrayUtils.idealIntArraySize(want + 1); 797 int[] grow = new int[nlen]; 798 System.arraycopy(lines, 0, grow, 0, lines.length); 799 mLines = grow; 800 lines = grow; 801 802 Directions[] grow2 = new Directions[nlen]; 803 System.arraycopy(mLineDirections, 0, grow2, 0, 804 mLineDirections.length); 805 mLineDirections = grow2; 806 } 807 808 if (chooseht != null) { 809 fm.ascent = above; 810 fm.descent = below; 811 fm.top = top; 812 fm.bottom = bottom; 813 814 for (int i = 0; i < chooseht.length; i++) { 815 chooseht[i].chooseHeight(text, start, end, choosehtv[i], v, fm); 816 } 817 818 above = fm.ascent; 819 below = fm.descent; 820 top = fm.top; 821 bottom = fm.bottom; 822 } 823 824 if (j == 0) { 825 if (trackpad) { 826 mTopPadding = top - above; 827 } 828 829 if (includepad) { 830 above = top; 831 } 832 } 833 if (last) { 834 if (trackpad) { 835 mBottomPadding = bottom - below; 836 } 837 838 if (includepad) { 839 below = bottom; 840 } 841 } 842 843 int extra; 844 845 if (needMultiply) { 846 extra = (int) ((below - above) * (spacingmult - 1) 847 + spacingadd + 0.5); 848 } else { 849 extra = 0; 850 } 851 852 lines[off + START] = start; 853 lines[off + TOP] = v; 854 lines[off + DESCENT] = below + extra; 855 856 v += (below - above) + extra; 857 lines[off + mColumns + START] = end; 858 lines[off + mColumns + TOP] = v; 859 860 if (tab) 861 lines[off + TAB] |= TAB_MASK; 862 863 { 864 lines[off + DIR] |= dir << DIR_SHIFT; 865 866 int cur = Character.DIRECTIONALITY_LEFT_TO_RIGHT; 867 int count = 0; 868 869 if (!easy) { 870 for (int k = start; k < end; k++) { 871 if (chdirs[k - pstart] != cur) { 872 count++; 873 cur = chdirs[k - pstart]; 874 } 875 } 876 } 877 878 Directions linedirs; 879 880 if (count == 0) { 881 linedirs = DIRS_ALL_LEFT_TO_RIGHT; 882 } else { 883 short[] ld = new short[count + 1]; 884 885 cur = Character.DIRECTIONALITY_LEFT_TO_RIGHT; 886 count = 0; 887 int here = start; 888 889 for (int k = start; k < end; k++) { 890 if (chdirs[k - pstart] != cur) { 891 // XXX check to make sure we don't 892 // overflow short 893 ld[count++] = (short) (k - here); 894 cur = chdirs[k - pstart]; 895 here = k; 896 } 897 } 898 899 ld[count] = (short) (end - here); 900 901 if (count == 1 && ld[0] == 0) { 902 linedirs = DIRS_ALL_RIGHT_TO_LEFT; 903 } else { 904 linedirs = new Directions(ld); 905 } 906 } 907 908 mLineDirections[j] = linedirs; 909 910 // If ellipsize is in marquee mode, do not apply ellipsis on the first line 911 if (ellipsize != null && (ellipsize != TextUtils.TruncateAt.MARQUEE || j != 0)) { 912 calculateEllipsis(start, end, widths, widstart, widoff, 913 ellipsiswidth, ellipsize, j, 914 textwidth, paint); 915 } 916 } 917 918 mLineCount++; 919 return v; 920 } 921 922 private void calculateEllipsis(int linestart, int lineend, 923 float[] widths, int widstart, int widoff, 924 float avail, TextUtils.TruncateAt where, 925 int line, float textwidth, TextPaint paint) { 926 int len = lineend - linestart; 927 928 if (textwidth <= avail) { 929 // Everything fits! 930 mLines[mColumns * line + ELLIPSIS_START] = 0; 931 mLines[mColumns * line + ELLIPSIS_COUNT] = 0; 932 return; 933 } 934 935 float ellipsiswid = paint.measureText("\u2026"); 936 int ellipsisStart, ellipsisCount; 937 938 if (where == TextUtils.TruncateAt.START) { 939 float sum = 0; 940 int i; 941 942 for (i = len; i >= 0; i--) { 943 float w = widths[i - 1 + linestart - widstart + widoff]; 944 945 if (w + sum + ellipsiswid > avail) { 946 break; 947 } 948 949 sum += w; 950 } 951 952 ellipsisStart = 0; 953 ellipsisCount = i; 954 } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE) { 955 float sum = 0; 956 int i; 957 958 for (i = 0; i < len; i++) { 959 float w = widths[i + linestart - widstart + widoff]; 960 961 if (w + sum + ellipsiswid > avail) { 962 break; 963 } 964 965 sum += w; 966 } 967 968 ellipsisStart = i; 969 ellipsisCount = len - i; 970 } else /* where = TextUtils.TruncateAt.MIDDLE */ { 971 float lsum = 0, rsum = 0; 972 int left = 0, right = len; 973 974 float ravail = (avail - ellipsiswid) / 2; 975 for (right = len; right >= 0; right--) { 976 float w = widths[right - 1 + linestart - widstart + widoff]; 977 978 if (w + rsum > ravail) { 979 break; 980 } 981 982 rsum += w; 983 } 984 985 float lavail = avail - ellipsiswid - rsum; 986 for (left = 0; left < right; left++) { 987 float w = widths[left + linestart - widstart + widoff]; 988 989 if (w + lsum > lavail) { 990 break; 991 } 992 993 lsum += w; 994 } 995 996 ellipsisStart = left; 997 ellipsisCount = right - left; 998 } 999 1000 mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart; 1001 mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount; 1002 } 1003 1004 // Override the baseclass so we can directly access our members, 1005 // rather than relying on member functions. 1006 // The logic mirrors that of Layout.getLineForVertical 1007 // FIXME: It may be faster to do a linear search for layouts without many lines. 1008 public int getLineForVertical(int vertical) { 1009 int high = mLineCount; 1010 int low = -1; 1011 int guess; 1012 int[] lines = mLines; 1013 while (high - low > 1) { 1014 guess = (high + low) >> 1; 1015 if (lines[mColumns * guess + TOP] > vertical){ 1016 high = guess; 1017 } else { 1018 low = guess; 1019 } 1020 } 1021 if (low < 0) { 1022 return 0; 1023 } else { 1024 return low; 1025 } 1026 } 1027 1028 public int getLineCount() { 1029 return mLineCount; 1030 } 1031 1032 public int getLineTop(int line) { 1033 return mLines[mColumns * line + TOP]; 1034 } 1035 1036 public int getLineDescent(int line) { 1037 return mLines[mColumns * line + DESCENT]; 1038 } 1039 1040 public int getLineStart(int line) { 1041 return mLines[mColumns * line + START] & START_MASK; 1042 } 1043 1044 public int getParagraphDirection(int line) { 1045 return mLines[mColumns * line + DIR] >> DIR_SHIFT; 1046 } 1047 1048 public boolean getLineContainsTab(int line) { 1049 return (mLines[mColumns * line + TAB] & TAB_MASK) != 0; 1050 } 1051 1052 public final Directions getLineDirections(int line) { 1053 return mLineDirections[line]; 1054 } 1055 1056 public int getTopPadding() { 1057 return mTopPadding; 1058 } 1059 1060 public int getBottomPadding() { 1061 return mBottomPadding; 1062 } 1063 1064 @Override 1065 public int getEllipsisCount(int line) { 1066 if (mColumns < COLUMNS_ELLIPSIZE) { 1067 return 0; 1068 } 1069 1070 return mLines[mColumns * line + ELLIPSIS_COUNT]; 1071 } 1072 1073 @Override 1074 public int getEllipsisStart(int line) { 1075 if (mColumns < COLUMNS_ELLIPSIZE) { 1076 return 0; 1077 } 1078 1079 return mLines[mColumns * line + ELLIPSIS_START]; 1080 } 1081 1082 @Override 1083 public int getEllipsizedWidth() { 1084 return mEllipsizedWidth; 1085 } 1086 1087 private int mLineCount; 1088 private int mTopPadding, mBottomPadding; 1089 private int mColumns; 1090 private int mEllipsizedWidth; 1091 1092 private static final int COLUMNS_NORMAL = 3; 1093 private static final int COLUMNS_ELLIPSIZE = 5; 1094 private static final int START = 0; 1095 private static final int DIR = START; 1096 private static final int TAB = START; 1097 private static final int TOP = 1; 1098 private static final int DESCENT = 2; 1099 private static final int ELLIPSIS_START = 3; 1100 private static final int ELLIPSIS_COUNT = 4; 1101 1102 private int[] mLines; 1103 private Directions[] mLineDirections; 1104 1105 private static final int START_MASK = 0x1FFFFFFF; 1106 private static final int DIR_MASK = 0xC0000000; 1107 private static final int DIR_SHIFT = 30; 1108 private static final int TAB_MASK = 0x20000000; 1109 1110 private static final char FIRST_RIGHT_TO_LEFT = '\u0590'; 1111 1112 /* 1113 * These are reused across calls to generate() 1114 */ 1115 private byte[] mChdirs; 1116 private char[] mChs; 1117 private float[] mWidths; 1118 private Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt(); 1119} 1120