StaticLayout.java revision 54b6cfa9a9e5b861a9930af873580d6dc20f773c
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 != null) { 911 calculateEllipsis(start, end, widths, widstart, widoff, 912 ellipsiswidth, ellipsize, j, 913 textwidth, paint); 914 } 915 } 916 917 mLineCount++; 918 return v; 919 } 920 921 private void calculateEllipsis(int linestart, int lineend, 922 float[] widths, int widstart, int widoff, 923 float avail, TextUtils.TruncateAt where, 924 int line, float textwidth, TextPaint paint) { 925 int len = lineend - linestart; 926 927 if (textwidth <= avail) { 928 // Everything fits! 929 mLines[mColumns * line + ELLIPSIS_START] = 0; 930 mLines[mColumns * line + ELLIPSIS_COUNT] = 0; 931 return; 932 } 933 934 float ellipsiswid = paint.measureText("\u2026"); 935 int ellipsisStart, ellipsisCount; 936 937 if (where == TextUtils.TruncateAt.START) { 938 float sum = 0; 939 int i; 940 941 for (i = len; i >= 0; i--) { 942 float w = widths[i - 1 + linestart - widstart + widoff]; 943 944 if (w + sum + ellipsiswid > avail) { 945 break; 946 } 947 948 sum += w; 949 } 950 951 ellipsisStart = 0; 952 ellipsisCount = i; 953 } else if (where == TextUtils.TruncateAt.END) { 954 float sum = 0; 955 int i; 956 957 for (i = 0; i < len; i++) { 958 float w = widths[i + linestart - widstart + widoff]; 959 960 if (w + sum + ellipsiswid > avail) { 961 break; 962 } 963 964 sum += w; 965 } 966 967 ellipsisStart = i; 968 ellipsisCount = len - i; 969 } else /* where = TextUtils.TruncateAt.MIDDLE */ { 970 float lsum = 0, rsum = 0; 971 int left = 0, right = len; 972 973 float ravail = (avail - ellipsiswid) / 2; 974 for (right = len; right >= 0; right--) { 975 float w = widths[right - 1 + linestart - widstart + widoff]; 976 977 if (w + rsum > ravail) { 978 break; 979 } 980 981 rsum += w; 982 } 983 984 float lavail = avail - ellipsiswid - rsum; 985 for (left = 0; left < right; left++) { 986 float w = widths[left + linestart - widstart + widoff]; 987 988 if (w + lsum > lavail) { 989 break; 990 } 991 992 lsum += w; 993 } 994 995 ellipsisStart = left; 996 ellipsisCount = right - left; 997 } 998 999 mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart; 1000 mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount; 1001 } 1002 1003 // Override the baseclass so we can directly access our members, 1004 // rather than relying on member functions. 1005 // The logic mirrors that of Layout.getLineForVertical 1006 // FIXME: It may be faster to do a linear search for layouts without many lines. 1007 public int getLineForVertical(int vertical) { 1008 int high = mLineCount; 1009 int low = -1; 1010 int guess; 1011 int[] lines = mLines; 1012 while (high - low > 1) { 1013 guess = (high + low) >> 1; 1014 if (lines[mColumns * guess + TOP] > vertical){ 1015 high = guess; 1016 } else { 1017 low = guess; 1018 } 1019 } 1020 if (low < 0) { 1021 return 0; 1022 } else { 1023 return low; 1024 } 1025 } 1026 1027 public int getLineCount() { 1028 return mLineCount; 1029 } 1030 1031 public int getLineTop(int line) { 1032 return mLines[mColumns * line + TOP]; 1033 } 1034 1035 public int getLineDescent(int line) { 1036 return mLines[mColumns * line + DESCENT]; 1037 } 1038 1039 public int getLineStart(int line) { 1040 return mLines[mColumns * line + START] & START_MASK; 1041 } 1042 1043 public int getParagraphDirection(int line) { 1044 return mLines[mColumns * line + DIR] >> DIR_SHIFT; 1045 } 1046 1047 public boolean getLineContainsTab(int line) { 1048 return (mLines[mColumns * line + TAB] & TAB_MASK) != 0; 1049 } 1050 1051 public final Directions getLineDirections(int line) { 1052 return mLineDirections[line]; 1053 } 1054 1055 public int getTopPadding() { 1056 return mTopPadding; 1057 } 1058 1059 public int getBottomPadding() { 1060 return mBottomPadding; 1061 } 1062 1063 @Override 1064 public int getEllipsisCount(int line) { 1065 if (mColumns < COLUMNS_ELLIPSIZE) { 1066 return 0; 1067 } 1068 1069 return mLines[mColumns * line + ELLIPSIS_COUNT]; 1070 } 1071 1072 @Override 1073 public int getEllipsisStart(int line) { 1074 if (mColumns < COLUMNS_ELLIPSIZE) { 1075 return 0; 1076 } 1077 1078 return mLines[mColumns * line + ELLIPSIS_START]; 1079 } 1080 1081 @Override 1082 public int getEllipsizedWidth() { 1083 return mEllipsizedWidth; 1084 } 1085 1086 private int mLineCount; 1087 private int mTopPadding, mBottomPadding; 1088 private int mColumns; 1089 private int mEllipsizedWidth; 1090 1091 private static final int COLUMNS_NORMAL = 3; 1092 private static final int COLUMNS_ELLIPSIZE = 5; 1093 private static final int START = 0; 1094 private static final int DIR = START; 1095 private static final int TAB = START; 1096 private static final int TOP = 1; 1097 private static final int DESCENT = 2; 1098 private static final int ELLIPSIS_START = 3; 1099 private static final int ELLIPSIS_COUNT = 4; 1100 1101 private int[] mLines; 1102 private Directions[] mLineDirections; 1103 1104 private static final int START_MASK = 0x1FFFFFFF; 1105 private static final int DIR_MASK = 0xC0000000; 1106 private static final int DIR_SHIFT = 30; 1107 private static final int TAB_MASK = 0x20000000; 1108 1109 private static final char FIRST_RIGHT_TO_LEFT = '\u0590'; 1110 1111 /* 1112 * These are reused across calls to generate() 1113 */ 1114 private byte[] mChdirs; 1115 private char[] mChs; 1116 private float[] mWidths; 1117 private Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt(); 1118} 1119