Layout.java revision b798689749c64baba81f02e10cf2157c747d6b46
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.Canvas; 20import android.graphics.Paint; 21import android.graphics.Rect; 22import android.graphics.Path; 23import com.android.internal.util.ArrayUtils; 24import android.util.Config; 25 26import junit.framework.Assert; 27import android.text.style.*; 28import android.text.method.TextKeyListener; 29import android.view.KeyEvent; 30 31/** 32 * A base class that manages text layout in visual elements on 33 * the screen. 34 * <p>For text that will be edited, use a {@link DynamicLayout}, 35 * which will be updated as the text changes. 36 * For text that will not change, use a {@link StaticLayout}. 37 */ 38public abstract class Layout { 39 /** 40 * Return how wide a layout would be necessary to display the 41 * specified text with one line per paragraph. 42 */ 43 public static float getDesiredWidth(CharSequence source, 44 TextPaint paint) { 45 return getDesiredWidth(source, 0, source.length(), paint); 46 } 47 48 /** 49 * Return how wide a layout would be necessary to display the 50 * specified text slice with one line per paragraph. 51 */ 52 public static float getDesiredWidth(CharSequence source, 53 int start, int end, 54 TextPaint paint) { 55 float need = 0; 56 TextPaint workPaint = new TextPaint(); 57 58 int next; 59 for (int i = start; i <= end; i = next) { 60 next = TextUtils.indexOf(source, '\n', i, end); 61 62 if (next < 0) 63 next = end; 64 65 float w = measureText(paint, workPaint, 66 source, i, next, null, true, null); 67 68 if (w > need) 69 need = w; 70 71 next++; 72 } 73 74 return need; 75 } 76 77 /** 78 * Subclasses of Layout use this constructor to set the display text, 79 * width, and other standard properties. 80 */ 81 protected Layout(CharSequence text, TextPaint paint, 82 int width, Alignment align, 83 float spacingmult, float spacingadd) { 84 if (width < 0) 85 throw new IllegalArgumentException("Layout: " + width + " < 0"); 86 87 mText = text; 88 mPaint = paint; 89 mWorkPaint = new TextPaint(); 90 mWidth = width; 91 mAlignment = align; 92 mSpacingMult = spacingmult; 93 mSpacingAdd = spacingadd; 94 mSpannedText = text instanceof Spanned; 95 } 96 97 /** 98 * Replace constructor properties of this Layout with new ones. Be careful. 99 */ 100 /* package */ void replaceWith(CharSequence text, TextPaint paint, 101 int width, Alignment align, 102 float spacingmult, float spacingadd) { 103 if (width < 0) { 104 throw new IllegalArgumentException("Layout: " + width + " < 0"); 105 } 106 107 mText = text; 108 mPaint = paint; 109 mWidth = width; 110 mAlignment = align; 111 mSpacingMult = spacingmult; 112 mSpacingAdd = spacingadd; 113 mSpannedText = text instanceof Spanned; 114 } 115 116 /** 117 * Draw this Layout on the specified Canvas. 118 */ 119 public void draw(Canvas c) { 120 draw(c, null, null, 0); 121 } 122 123 /** 124 * Draw the specified rectangle from this Layout on the specified Canvas, 125 * with the specified path drawn between the background and the text. 126 */ 127 public void draw(Canvas c, Path highlight, Paint highlightpaint, 128 int cursorOffsetVertical) { 129 int dtop, dbottom; 130 131 synchronized (sTempRect) { 132 if (!c.getClipBounds(sTempRect)) { 133 return; 134 } 135 136 dtop = sTempRect.top; 137 dbottom = sTempRect.bottom; 138 } 139 140 TextPaint paint = mPaint; 141 142 int top = 0; 143 // getLineBottom(getLineCount() -1) just calls getLineTop(getLineCount) 144 int bottom = getLineTop(getLineCount()); 145 146 147 if (dtop > top) { 148 top = dtop; 149 } 150 if (dbottom < bottom) { 151 bottom = dbottom; 152 } 153 154 int first = getLineForVertical(top); 155 int last = getLineForVertical(bottom); 156 157 int previousLineBottom = getLineTop(first); 158 int previousLineEnd = getLineStart(first); 159 160 CharSequence buf = mText; 161 162 ParagraphStyle[] nospans = ArrayUtils.emptyArray(ParagraphStyle.class); 163 ParagraphStyle[] spans = nospans; 164 int spanend = 0; 165 int textLength = 0; 166 boolean spannedText = mSpannedText; 167 168 if (spannedText) { 169 spanend = 0; 170 textLength = buf.length(); 171 for (int i = first; i <= last; i++) { 172 int start = previousLineEnd; 173 int end = getLineStart(i+1); 174 previousLineEnd = end; 175 176 int ltop = previousLineBottom; 177 int lbottom = getLineTop(i+1); 178 previousLineBottom = lbottom; 179 int lbaseline = lbottom - getLineDescent(i); 180 181 if (start >= spanend) { 182 Spanned sp = (Spanned) buf; 183 spanend = sp.nextSpanTransition(start, textLength, 184 LineBackgroundSpan.class); 185 spans = sp.getSpans(start, spanend, 186 LineBackgroundSpan.class); 187 } 188 189 for (int n = 0; n < spans.length; n++) { 190 LineBackgroundSpan back = (LineBackgroundSpan) spans[n]; 191 192 back.drawBackground(c, paint, 0, mWidth, 193 ltop, lbaseline, lbottom, 194 buf, start, end, 195 i); 196 } 197 } 198 // reset to their original values 199 spanend = 0; 200 previousLineBottom = getLineTop(first); 201 previousLineEnd = getLineStart(first); 202 spans = nospans; 203 } 204 205 // There can be a highlight even without spans if we are drawing 206 // a non-spanned transformation of a spanned editing buffer. 207 if (highlight != null) { 208 if (cursorOffsetVertical != 0) { 209 c.translate(0, cursorOffsetVertical); 210 } 211 212 c.drawPath(highlight, highlightpaint); 213 214 if (cursorOffsetVertical != 0) { 215 c.translate(0, -cursorOffsetVertical); 216 } 217 } 218 219 Alignment align = mAlignment; 220 221 for (int i = first; i <= last; i++) { 222 int start = previousLineEnd; 223 224 previousLineEnd = getLineStart(i+1); 225 int end = getLineVisibleEnd(i, start, previousLineEnd); 226 227 int ltop = previousLineBottom; 228 int lbottom = getLineTop(i+1); 229 previousLineBottom = lbottom; 230 int lbaseline = lbottom - getLineDescent(i); 231 232 boolean par = false; 233 if (spannedText) { 234 if (start == 0 || buf.charAt(start - 1) == '\n') { 235 par = true; 236 } 237 if (start >= spanend) { 238 239 Spanned sp = (Spanned) buf; 240 241 spanend = sp.nextSpanTransition(start, textLength, 242 ParagraphStyle.class); 243 spans = sp.getSpans(start, spanend, ParagraphStyle.class); 244 245 align = mAlignment; 246 247 for (int n = spans.length-1; n >= 0; n--) { 248 if (spans[n] instanceof AlignmentSpan) { 249 align = ((AlignmentSpan) spans[n]).getAlignment(); 250 break; 251 } 252 } 253 } 254 } 255 256 int dir = getParagraphDirection(i); 257 int left = 0; 258 int right = mWidth; 259 260 if (spannedText) { 261 final int length = spans.length; 262 for (int n = 0; n < length; n++) { 263 if (spans[n] instanceof LeadingMarginSpan) { 264 LeadingMarginSpan margin = (LeadingMarginSpan) spans[n]; 265 266 if (dir == DIR_RIGHT_TO_LEFT) { 267 margin.drawLeadingMargin(c, paint, right, dir, ltop, 268 lbaseline, lbottom, buf, 269 start, end, par, this); 270 271 right -= margin.getLeadingMargin(par); 272 } else { 273 margin.drawLeadingMargin(c, paint, left, dir, ltop, 274 lbaseline, lbottom, buf, 275 start, end, par, this); 276 277 left += margin.getLeadingMargin(par); 278 } 279 } 280 } 281 } 282 283 int x; 284 if (align == Alignment.ALIGN_NORMAL) { 285 if (dir == DIR_LEFT_TO_RIGHT) { 286 x = left; 287 } else { 288 x = right; 289 } 290 } else { 291 int max = (int)getLineMax(i, spans, false); 292 if (align == Alignment.ALIGN_OPPOSITE) { 293 if (dir == DIR_RIGHT_TO_LEFT) { 294 x = left + max; 295 } else { 296 x = right - max; 297 } 298 } else { 299 // Alignment.ALIGN_CENTER 300 max = max & ~1; 301 int half = (right - left - max) >> 1; 302 if (dir == DIR_RIGHT_TO_LEFT) { 303 x = right - half; 304 } else { 305 x = left + half; 306 } 307 } 308 } 309 310 Directions directions = getLineDirections(i); 311 boolean hasTab = getLineContainsTab(i); 312 if (directions == DIRS_ALL_LEFT_TO_RIGHT && 313 !spannedText && !hasTab) { 314 if (Config.DEBUG) { 315 Assert.assertTrue(dir == DIR_LEFT_TO_RIGHT); 316 Assert.assertNotNull(c); 317 } 318 c.drawText(buf, start, end, x, lbaseline, paint); 319 } else { 320 drawText(c, buf, start, end, dir, directions, 321 x, ltop, lbaseline, lbottom, paint, mWorkPaint, 322 hasTab, spans); 323 } 324 } 325 } 326 327 /** 328 * Return the text that is displayed by this Layout. 329 */ 330 public final CharSequence getText() { 331 return mText; 332 } 333 334 /** 335 * Return the base Paint properties for this layout. 336 * Do NOT change the paint, which may result in funny 337 * drawing for this layout. 338 */ 339 public final TextPaint getPaint() { 340 return mPaint; 341 } 342 343 /** 344 * Return the width of this layout. 345 */ 346 public final int getWidth() { 347 return mWidth; 348 } 349 350 /** 351 * Return the width to which this Layout is ellipsizing, or 352 * {@link #getWidth} if it is not doing anything special. 353 */ 354 public int getEllipsizedWidth() { 355 return mWidth; 356 } 357 358 /** 359 * Increase the width of this layout to the specified width. 360 * Be careful to use this only when you know it is appropriate -- 361 * it does not cause the text to reflow to use the full new width. 362 */ 363 public final void increaseWidthTo(int wid) { 364 if (wid < mWidth) { 365 throw new RuntimeException("attempted to reduce Layout width"); 366 } 367 368 mWidth = wid; 369 } 370 371 /** 372 * Return the total height of this layout. 373 */ 374 public int getHeight() { 375 return getLineTop(getLineCount()); // same as getLineBottom(getLineCount() - 1); 376 } 377 378 /** 379 * Return the base alignment of this layout. 380 */ 381 public final Alignment getAlignment() { 382 return mAlignment; 383 } 384 385 /** 386 * Return what the text height is multiplied by to get the line height. 387 */ 388 public final float getSpacingMultiplier() { 389 return mSpacingMult; 390 } 391 392 /** 393 * Return the number of units of leading that are added to each line. 394 */ 395 public final float getSpacingAdd() { 396 return mSpacingAdd; 397 } 398 399 /** 400 * Return the number of lines of text in this layout. 401 */ 402 public abstract int getLineCount(); 403 404 /** 405 * Return the baseline for the specified line (0…getLineCount() - 1) 406 * If bounds is not null, return the top, left, right, bottom extents 407 * of the specified line in it. 408 * @param line which line to examine (0..getLineCount() - 1) 409 * @param bounds Optional. If not null, it returns the extent of the line 410 * @return the Y-coordinate of the baseline 411 */ 412 public int getLineBounds(int line, Rect bounds) { 413 if (bounds != null) { 414 bounds.left = 0; // ??? 415 bounds.top = getLineTop(line); 416 bounds.right = mWidth; // ??? 417 bounds.bottom = getLineBottom(line); 418 } 419 return getLineBaseline(line); 420 } 421 422 /** 423 * Return the vertical position of the top of the specified line. 424 * If the specified line is one beyond the last line, returns the 425 * bottom of the last line. 426 */ 427 public abstract int getLineTop(int line); 428 429 /** 430 * Return the descent of the specified line. 431 */ 432 public abstract int getLineDescent(int line); 433 434 /** 435 * Return the text offset of the beginning of the specified line. 436 * If the specified line is one beyond the last line, returns the 437 * end of the last line. 438 */ 439 public abstract int getLineStart(int line); 440 441 /** 442 * Returns the primary directionality of the paragraph containing 443 * the specified line. 444 */ 445 public abstract int getParagraphDirection(int line); 446 447 /** 448 * Returns whether the specified line contains one or more tabs. 449 */ 450 public abstract boolean getLineContainsTab(int line); 451 452 /** 453 * Returns an array of directionalities for the specified line. 454 * The array alternates counts of characters in left-to-right 455 * and right-to-left segments of the line. 456 */ 457 public abstract Directions getLineDirections(int line); 458 459 /** 460 * Returns the (negative) number of extra pixels of ascent padding in the 461 * top line of the Layout. 462 */ 463 public abstract int getTopPadding(); 464 465 /** 466 * Returns the number of extra pixels of descent padding in the 467 * bottom line of the Layout. 468 */ 469 public abstract int getBottomPadding(); 470 471 /** 472 * Get the primary horizontal position for the specified text offset. 473 * This is the location where a new character would be inserted in 474 * the paragraph's primary direction. 475 */ 476 public float getPrimaryHorizontal(int offset) { 477 return getHorizontal(offset, false, true); 478 } 479 480 /** 481 * Get the secondary horizontal position for the specified text offset. 482 * This is the location where a new character would be inserted in 483 * the direction other than the paragraph's primary direction. 484 */ 485 public float getSecondaryHorizontal(int offset) { 486 return getHorizontal(offset, true, true); 487 } 488 489 private float getHorizontal(int offset, boolean trailing, boolean alt) { 490 int line = getLineForOffset(offset); 491 492 return getHorizontal(offset, trailing, alt, line); 493 } 494 495 private float getHorizontal(int offset, boolean trailing, boolean alt, 496 int line) { 497 int start = getLineStart(line); 498 int end = getLineVisibleEnd(line); 499 int dir = getParagraphDirection(line); 500 boolean tab = getLineContainsTab(line); 501 Directions directions = getLineDirections(line); 502 503 TabStopSpan[] tabs = null; 504 if (tab && mText instanceof Spanned) { 505 tabs = ((Spanned) mText).getSpans(start, end, TabStopSpan.class); 506 } 507 508 float wid = measureText(mPaint, mWorkPaint, mText, start, offset, end, 509 dir, directions, trailing, alt, tab, tabs); 510 511 if (offset > end) { 512 if (dir == DIR_RIGHT_TO_LEFT) 513 wid -= measureText(mPaint, mWorkPaint, 514 mText, end, offset, null, tab, tabs); 515 else 516 wid += measureText(mPaint, mWorkPaint, 517 mText, end, offset, null, tab, tabs); 518 } 519 520 Alignment align = getParagraphAlignment(line); 521 int left = getParagraphLeft(line); 522 int right = getParagraphRight(line); 523 524 if (align == Alignment.ALIGN_NORMAL) { 525 if (dir == DIR_RIGHT_TO_LEFT) 526 return right + wid; 527 else 528 return left + wid; 529 } 530 531 float max = getLineMax(line); 532 533 if (align == Alignment.ALIGN_OPPOSITE) { 534 if (dir == DIR_RIGHT_TO_LEFT) 535 return left + max + wid; 536 else 537 return right - max + wid; 538 } else { /* align == Alignment.ALIGN_CENTER */ 539 int imax = ((int) max) & ~1; 540 541 if (dir == DIR_RIGHT_TO_LEFT) 542 return right - (((right - left) - imax) / 2) + wid; 543 else 544 return left + ((right - left) - imax) / 2 + wid; 545 } 546 } 547 548 /** 549 * Get the leftmost position that should be exposed for horizontal 550 * scrolling on the specified line. 551 */ 552 public float getLineLeft(int line) { 553 int dir = getParagraphDirection(line); 554 Alignment align = getParagraphAlignment(line); 555 556 if (align == Alignment.ALIGN_NORMAL) { 557 if (dir == DIR_RIGHT_TO_LEFT) 558 return getParagraphRight(line) - getLineMax(line); 559 else 560 return 0; 561 } else if (align == Alignment.ALIGN_OPPOSITE) { 562 if (dir == DIR_RIGHT_TO_LEFT) 563 return 0; 564 else 565 return mWidth - getLineMax(line); 566 } else { /* align == Alignment.ALIGN_CENTER */ 567 int left = getParagraphLeft(line); 568 int right = getParagraphRight(line); 569 int max = ((int) getLineMax(line)) & ~1; 570 571 return left + ((right - left) - max) / 2; 572 } 573 } 574 575 /** 576 * Get the rightmost position that should be exposed for horizontal 577 * scrolling on the specified line. 578 */ 579 public float getLineRight(int line) { 580 int dir = getParagraphDirection(line); 581 Alignment align = getParagraphAlignment(line); 582 583 if (align == Alignment.ALIGN_NORMAL) { 584 if (dir == DIR_RIGHT_TO_LEFT) 585 return mWidth; 586 else 587 return getParagraphLeft(line) + getLineMax(line); 588 } else if (align == Alignment.ALIGN_OPPOSITE) { 589 if (dir == DIR_RIGHT_TO_LEFT) 590 return getLineMax(line); 591 else 592 return mWidth; 593 } else { /* align == Alignment.ALIGN_CENTER */ 594 int left = getParagraphLeft(line); 595 int right = getParagraphRight(line); 596 int max = ((int) getLineMax(line)) & ~1; 597 598 return right - ((right - left) - max) / 2; 599 } 600 } 601 602 /** 603 * Gets the horizontal extent of the specified line, excluding 604 * trailing whitespace. 605 */ 606 public float getLineMax(int line) { 607 return getLineMax(line, null, false); 608 } 609 610 /** 611 * Gets the horizontal extent of the specified line, including 612 * trailing whitespace. 613 */ 614 public float getLineWidth(int line) { 615 return getLineMax(line, null, true); 616 } 617 618 private float getLineMax(int line, Object[] tabs, boolean full) { 619 int start = getLineStart(line); 620 int end; 621 622 if (full) { 623 end = getLineEnd(line); 624 } else { 625 end = getLineVisibleEnd(line); 626 } 627 boolean tab = getLineContainsTab(line); 628 629 if (tabs == null && tab && mText instanceof Spanned) { 630 tabs = ((Spanned) mText).getSpans(start, end, TabStopSpan.class); 631 } 632 633 return measureText(mPaint, mWorkPaint, 634 mText, start, end, null, tab, tabs); 635 } 636 637 /** 638 * Get the line number corresponding to the specified vertical position. 639 * If you ask for a position above 0, you get 0; if you ask for a position 640 * below the bottom of the text, you get the last line. 641 */ 642 // FIXME: It may be faster to do a linear search for layouts without many lines. 643 public int getLineForVertical(int vertical) { 644 int high = getLineCount(), low = -1, guess; 645 646 while (high - low > 1) { 647 guess = (high + low) / 2; 648 649 if (getLineTop(guess) > vertical) 650 high = guess; 651 else 652 low = guess; 653 } 654 655 if (low < 0) 656 return 0; 657 else 658 return low; 659 } 660 661 /** 662 * Get the line number on which the specified text offset appears. 663 * If you ask for a position before 0, you get 0; if you ask for a position 664 * beyond the end of the text, you get the last line. 665 */ 666 public int getLineForOffset(int offset) { 667 int high = getLineCount(), low = -1, guess; 668 669 while (high - low > 1) { 670 guess = (high + low) / 2; 671 672 if (getLineStart(guess) > offset) 673 high = guess; 674 else 675 low = guess; 676 } 677 678 if (low < 0) 679 return 0; 680 else 681 return low; 682 } 683 684 /** 685 * Get the character offset on the specfied line whose position is 686 * closest to the specified horizontal position. 687 */ 688 public int getOffsetForHorizontal(int line, float horiz) { 689 int max = getLineEnd(line) - 1; 690 int min = getLineStart(line); 691 Directions dirs = getLineDirections(line); 692 693 if (line == getLineCount() - 1) 694 max++; 695 696 int best = min; 697 float bestdist = Math.abs(getPrimaryHorizontal(best) - horiz); 698 699 int here = min; 700 for (int i = 0; i < dirs.mDirections.length; i++) { 701 int there = here + dirs.mDirections[i]; 702 int swap = ((i & 1) == 0) ? 1 : -1; 703 704 if (there > max) 705 there = max; 706 707 int high = there - 1 + 1, low = here + 1 - 1, guess; 708 709 while (high - low > 1) { 710 guess = (high + low) / 2; 711 int adguess = getOffsetAtStartOf(guess); 712 713 if (getPrimaryHorizontal(adguess) * swap >= horiz * swap) 714 high = guess; 715 else 716 low = guess; 717 } 718 719 if (low < here + 1) 720 low = here + 1; 721 722 if (low < there) { 723 low = getOffsetAtStartOf(low); 724 725 float dist = Math.abs(getPrimaryHorizontal(low) - horiz); 726 727 int aft = TextUtils.getOffsetAfter(mText, low); 728 if (aft < there) { 729 float other = Math.abs(getPrimaryHorizontal(aft) - horiz); 730 731 if (other < dist) { 732 dist = other; 733 low = aft; 734 } 735 } 736 737 if (dist < bestdist) { 738 bestdist = dist; 739 best = low; 740 } 741 } 742 743 float dist = Math.abs(getPrimaryHorizontal(here) - horiz); 744 745 if (dist < bestdist) { 746 bestdist = dist; 747 best = here; 748 } 749 750 here = there; 751 } 752 753 float dist = Math.abs(getPrimaryHorizontal(max) - horiz); 754 755 if (dist < bestdist) { 756 bestdist = dist; 757 best = max; 758 } 759 760 return best; 761 } 762 763 /** 764 * Return the text offset after the last character on the specified line. 765 */ 766 public final int getLineEnd(int line) { 767 return getLineStart(line + 1); 768 } 769 770 /** 771 * Return the text offset after the last visible character (so whitespace 772 * is not counted) on the specified line. 773 */ 774 public int getLineVisibleEnd(int line) { 775 return getLineVisibleEnd(line, getLineStart(line), getLineStart(line+1)); 776 } 777 778 private int getLineVisibleEnd(int line, int start, int end) { 779 if (Config.DEBUG) { 780 Assert.assertTrue(getLineStart(line) == start && getLineStart(line+1) == end); 781 } 782 783 CharSequence text = mText; 784 char ch; 785 if (line == getLineCount() - 1) { 786 return end; 787 } 788 789 for (; end > start; end--) { 790 ch = text.charAt(end - 1); 791 792 if (ch == '\n') { 793 return end - 1; 794 } 795 796 if (ch != ' ' && ch != '\t') { 797 break; 798 } 799 800 } 801 802 return end; 803 } 804 805 /** 806 * Return the vertical position of the bottom of the specified line. 807 */ 808 public final int getLineBottom(int line) { 809 return getLineTop(line + 1); 810 } 811 812 /** 813 * Return the vertical position of the baseline of the specified line. 814 */ 815 public final int getLineBaseline(int line) { 816 // getLineTop(line+1) == getLineTop(line) 817 return getLineTop(line+1) - getLineDescent(line); 818 } 819 820 /** 821 * Get the ascent of the text on the specified line. 822 * The return value is negative to match the Paint.ascent() convention. 823 */ 824 public final int getLineAscent(int line) { 825 // getLineTop(line+1) - getLineDescent(line) == getLineBaseLine(line) 826 return getLineTop(line) - (getLineTop(line+1) - getLineDescent(line)); 827 } 828 829 /** 830 * Return the text offset that would be reached by moving left 831 * (possibly onto another line) from the specified offset. 832 */ 833 public int getOffsetToLeftOf(int offset) { 834 int line = getLineForOffset(offset); 835 int start = getLineStart(line); 836 int end = getLineEnd(line); 837 Directions dirs = getLineDirections(line); 838 839 if (line != getLineCount() - 1) 840 end--; 841 842 float horiz = getPrimaryHorizontal(offset); 843 844 int best = offset; 845 float besth = Integer.MIN_VALUE; 846 int candidate; 847 848 candidate = TextUtils.getOffsetBefore(mText, offset); 849 if (candidate >= start && candidate <= end) { 850 float h = getPrimaryHorizontal(candidate); 851 852 if (h < horiz && h > besth) { 853 best = candidate; 854 besth = h; 855 } 856 } 857 858 candidate = TextUtils.getOffsetAfter(mText, offset); 859 if (candidate >= start && candidate <= end) { 860 float h = getPrimaryHorizontal(candidate); 861 862 if (h < horiz && h > besth) { 863 best = candidate; 864 besth = h; 865 } 866 } 867 868 int here = start; 869 for (int i = 0; i < dirs.mDirections.length; i++) { 870 int there = here + dirs.mDirections[i]; 871 if (there > end) 872 there = end; 873 874 float h = getPrimaryHorizontal(here); 875 876 if (h < horiz && h > besth) { 877 best = here; 878 besth = h; 879 } 880 881 candidate = TextUtils.getOffsetAfter(mText, here); 882 if (candidate >= start && candidate <= end) { 883 h = getPrimaryHorizontal(candidate); 884 885 if (h < horiz && h > besth) { 886 best = candidate; 887 besth = h; 888 } 889 } 890 891 candidate = TextUtils.getOffsetBefore(mText, there); 892 if (candidate >= start && candidate <= end) { 893 h = getPrimaryHorizontal(candidate); 894 895 if (h < horiz && h > besth) { 896 best = candidate; 897 besth = h; 898 } 899 } 900 901 here = there; 902 } 903 904 float h = getPrimaryHorizontal(end); 905 906 if (h < horiz && h > besth) { 907 best = end; 908 besth = h; 909 } 910 911 if (best != offset) 912 return best; 913 914 int dir = getParagraphDirection(line); 915 916 if (dir > 0) { 917 if (line == 0) 918 return best; 919 else 920 return getOffsetForHorizontal(line - 1, 10000); 921 } else { 922 if (line == getLineCount() - 1) 923 return best; 924 else 925 return getOffsetForHorizontal(line + 1, 10000); 926 } 927 } 928 929 /** 930 * Return the text offset that would be reached by moving right 931 * (possibly onto another line) from the specified offset. 932 */ 933 public int getOffsetToRightOf(int offset) { 934 int line = getLineForOffset(offset); 935 int start = getLineStart(line); 936 int end = getLineEnd(line); 937 Directions dirs = getLineDirections(line); 938 939 if (line != getLineCount() - 1) 940 end--; 941 942 float horiz = getPrimaryHorizontal(offset); 943 944 int best = offset; 945 float besth = Integer.MAX_VALUE; 946 int candidate; 947 948 candidate = TextUtils.getOffsetBefore(mText, offset); 949 if (candidate >= start && candidate <= end) { 950 float h = getPrimaryHorizontal(candidate); 951 952 if (h > horiz && h < besth) { 953 best = candidate; 954 besth = h; 955 } 956 } 957 958 candidate = TextUtils.getOffsetAfter(mText, offset); 959 if (candidate >= start && candidate <= end) { 960 float h = getPrimaryHorizontal(candidate); 961 962 if (h > horiz && h < besth) { 963 best = candidate; 964 besth = h; 965 } 966 } 967 968 int here = start; 969 for (int i = 0; i < dirs.mDirections.length; i++) { 970 int there = here + dirs.mDirections[i]; 971 if (there > end) 972 there = end; 973 974 float h = getPrimaryHorizontal(here); 975 976 if (h > horiz && h < besth) { 977 best = here; 978 besth = h; 979 } 980 981 candidate = TextUtils.getOffsetAfter(mText, here); 982 if (candidate >= start && candidate <= end) { 983 h = getPrimaryHorizontal(candidate); 984 985 if (h > horiz && h < besth) { 986 best = candidate; 987 besth = h; 988 } 989 } 990 991 candidate = TextUtils.getOffsetBefore(mText, there); 992 if (candidate >= start && candidate <= end) { 993 h = getPrimaryHorizontal(candidate); 994 995 if (h > horiz && h < besth) { 996 best = candidate; 997 besth = h; 998 } 999 } 1000 1001 here = there; 1002 } 1003 1004 float h = getPrimaryHorizontal(end); 1005 1006 if (h > horiz && h < besth) { 1007 best = end; 1008 besth = h; 1009 } 1010 1011 if (best != offset) 1012 return best; 1013 1014 int dir = getParagraphDirection(line); 1015 1016 if (dir > 0) { 1017 if (line == getLineCount() - 1) 1018 return best; 1019 else 1020 return getOffsetForHorizontal(line + 1, -10000); 1021 } else { 1022 if (line == 0) 1023 return best; 1024 else 1025 return getOffsetForHorizontal(line - 1, -10000); 1026 } 1027 } 1028 1029 private int getOffsetAtStartOf(int offset) { 1030 if (offset == 0) 1031 return 0; 1032 1033 CharSequence text = mText; 1034 char c = text.charAt(offset); 1035 1036 if (c >= '\uDC00' && c <= '\uDFFF') { 1037 char c1 = text.charAt(offset - 1); 1038 1039 if (c1 >= '\uD800' && c1 <= '\uDBFF') 1040 offset -= 1; 1041 } 1042 1043 if (mSpannedText) { 1044 ReplacementSpan[] spans = ((Spanned) text).getSpans(offset, offset, 1045 ReplacementSpan.class); 1046 1047 for (int i = 0; i < spans.length; i++) { 1048 int start = ((Spanned) text).getSpanStart(spans[i]); 1049 int end = ((Spanned) text).getSpanEnd(spans[i]); 1050 1051 if (start < offset && end > offset) 1052 offset = start; 1053 } 1054 } 1055 1056 return offset; 1057 } 1058 1059 /** 1060 * Fills in the specified Path with a representation of a cursor 1061 * at the specified offset. This will often be a vertical line 1062 * but can be multiple discontinous lines in text with multiple 1063 * directionalities. 1064 */ 1065 public void getCursorPath(int point, Path dest, 1066 CharSequence editingBuffer) { 1067 dest.reset(); 1068 1069 int line = getLineForOffset(point); 1070 int top = getLineTop(line); 1071 int bottom = getLineTop(line+1); 1072 1073 float h1 = getPrimaryHorizontal(point) - 0.5f; 1074 float h2 = getSecondaryHorizontal(point) - 0.5f; 1075 1076 int caps = TextKeyListener.getMetaState(editingBuffer, 1077 KeyEvent.META_SHIFT_ON) | 1078 TextKeyListener.getMetaState(editingBuffer, 1079 TextKeyListener.META_SELECTING); 1080 int fn = TextKeyListener.getMetaState(editingBuffer, 1081 KeyEvent.META_ALT_ON); 1082 int dist = 0; 1083 1084 if (caps != 0 || fn != 0) { 1085 dist = (bottom - top) >> 2; 1086 1087 if (fn != 0) 1088 top += dist; 1089 if (caps != 0) 1090 bottom -= dist; 1091 } 1092 1093 if (h1 < 0.5f) 1094 h1 = 0.5f; 1095 if (h2 < 0.5f) 1096 h2 = 0.5f; 1097 1098 if (h1 == h2) { 1099 dest.moveTo(h1, top); 1100 dest.lineTo(h1, bottom); 1101 } else { 1102 dest.moveTo(h1, top); 1103 dest.lineTo(h1, (top + bottom) >> 1); 1104 1105 dest.moveTo(h2, (top + bottom) >> 1); 1106 dest.lineTo(h2, bottom); 1107 } 1108 1109 if (caps == 2) { 1110 dest.moveTo(h2, bottom); 1111 dest.lineTo(h2 - dist, bottom + dist); 1112 dest.lineTo(h2, bottom); 1113 dest.lineTo(h2 + dist, bottom + dist); 1114 } else if (caps == 1) { 1115 dest.moveTo(h2, bottom); 1116 dest.lineTo(h2 - dist, bottom + dist); 1117 1118 dest.moveTo(h2 - dist, bottom + dist - 0.5f); 1119 dest.lineTo(h2 + dist, bottom + dist - 0.5f); 1120 1121 dest.moveTo(h2 + dist, bottom + dist); 1122 dest.lineTo(h2, bottom); 1123 } 1124 1125 if (fn == 2) { 1126 dest.moveTo(h1, top); 1127 dest.lineTo(h1 - dist, top - dist); 1128 dest.lineTo(h1, top); 1129 dest.lineTo(h1 + dist, top - dist); 1130 } else if (fn == 1) { 1131 dest.moveTo(h1, top); 1132 dest.lineTo(h1 - dist, top - dist); 1133 1134 dest.moveTo(h1 - dist, top - dist + 0.5f); 1135 dest.lineTo(h1 + dist, top - dist + 0.5f); 1136 1137 dest.moveTo(h1 + dist, top - dist); 1138 dest.lineTo(h1, top); 1139 } 1140 } 1141 1142 private void addSelection(int line, int start, int end, 1143 int top, int bottom, Path dest) { 1144 int linestart = getLineStart(line); 1145 int lineend = getLineEnd(line); 1146 Directions dirs = getLineDirections(line); 1147 1148 if (lineend > linestart && mText.charAt(lineend - 1) == '\n') 1149 lineend--; 1150 1151 int here = linestart; 1152 for (int i = 0; i < dirs.mDirections.length; i++) { 1153 int there = here + dirs.mDirections[i]; 1154 if (there > lineend) 1155 there = lineend; 1156 1157 if (start <= there && end >= here) { 1158 int st = Math.max(start, here); 1159 int en = Math.min(end, there); 1160 1161 if (st != en) { 1162 float h1 = getHorizontal(st, false, false, line); 1163 float h2 = getHorizontal(en, true, false, line); 1164 1165 dest.addRect(h1, top, h2, bottom, Path.Direction.CW); 1166 } 1167 } 1168 1169 here = there; 1170 } 1171 } 1172 1173 /** 1174 * Fills in the specified Path with a representation of a highlight 1175 * between the specified offsets. This will often be a rectangle 1176 * or a potentially discontinuous set of rectangles. If the start 1177 * and end are the same, the returned path is empty. 1178 */ 1179 public void getSelectionPath(int start, int end, Path dest) { 1180 dest.reset(); 1181 1182 if (start == end) 1183 return; 1184 1185 if (end < start) { 1186 int temp = end; 1187 end = start; 1188 start = temp; 1189 } 1190 1191 int startline = getLineForOffset(start); 1192 int endline = getLineForOffset(end); 1193 1194 int top = getLineTop(startline); 1195 int bottom = getLineBottom(endline); 1196 1197 if (startline == endline) { 1198 addSelection(startline, start, end, top, bottom, dest); 1199 } else { 1200 final float width = mWidth; 1201 1202 addSelection(startline, start, getLineEnd(startline), 1203 top, getLineBottom(startline), dest); 1204 1205 if (getParagraphDirection(startline) == DIR_RIGHT_TO_LEFT) 1206 dest.addRect(getLineLeft(startline), top, 1207 0, getLineBottom(startline), Path.Direction.CW); 1208 else 1209 dest.addRect(getLineRight(startline), top, 1210 width, getLineBottom(startline), Path.Direction.CW); 1211 1212 for (int i = startline + 1; i < endline; i++) { 1213 top = getLineTop(i); 1214 bottom = getLineBottom(i); 1215 dest.addRect(0, top, width, bottom, Path.Direction.CW); 1216 } 1217 1218 top = getLineTop(endline); 1219 bottom = getLineBottom(endline); 1220 1221 addSelection(endline, getLineStart(endline), end, 1222 top, bottom, dest); 1223 1224 if (getParagraphDirection(endline) == DIR_RIGHT_TO_LEFT) 1225 dest.addRect(width, top, getLineRight(endline), bottom, Path.Direction.CW); 1226 else 1227 dest.addRect(0, top, getLineLeft(endline), bottom, Path.Direction.CW); 1228 } 1229 } 1230 1231 /** 1232 * Get the alignment of the specified paragraph, taking into account 1233 * markup attached to it. 1234 */ 1235 public final Alignment getParagraphAlignment(int line) { 1236 Alignment align = mAlignment; 1237 1238 if (mSpannedText) { 1239 Spanned sp = (Spanned) mText; 1240 AlignmentSpan[] spans = sp.getSpans(getLineStart(line), 1241 getLineEnd(line), 1242 AlignmentSpan.class); 1243 1244 int spanLength = spans.length; 1245 if (spanLength > 0) { 1246 align = spans[spanLength-1].getAlignment(); 1247 } 1248 } 1249 1250 return align; 1251 } 1252 1253 /** 1254 * Get the left edge of the specified paragraph, inset by left margins. 1255 */ 1256 public final int getParagraphLeft(int line) { 1257 int dir = getParagraphDirection(line); 1258 1259 int left = 0; 1260 1261 boolean par = false; 1262 int off = getLineStart(line); 1263 if (off == 0 || mText.charAt(off - 1) == '\n') 1264 par = true; 1265 1266 if (dir == DIR_LEFT_TO_RIGHT) { 1267 if (mSpannedText) { 1268 Spanned sp = (Spanned) mText; 1269 LeadingMarginSpan[] spans = sp.getSpans(getLineStart(line), 1270 getLineEnd(line), 1271 LeadingMarginSpan.class); 1272 1273 for (int i = 0; i < spans.length; i++) { 1274 left += spans[i].getLeadingMargin(par); 1275 } 1276 } 1277 } 1278 1279 return left; 1280 } 1281 1282 /** 1283 * Get the right edge of the specified paragraph, inset by right margins. 1284 */ 1285 public final int getParagraphRight(int line) { 1286 int dir = getParagraphDirection(line); 1287 1288 int right = mWidth; 1289 1290 boolean par = false; 1291 int off = getLineStart(line); 1292 if (off == 0 || mText.charAt(off - 1) == '\n') 1293 par = true; 1294 1295 1296 if (dir == DIR_RIGHT_TO_LEFT) { 1297 if (mSpannedText) { 1298 Spanned sp = (Spanned) mText; 1299 LeadingMarginSpan[] spans = sp.getSpans(getLineStart(line), 1300 getLineEnd(line), 1301 LeadingMarginSpan.class); 1302 1303 for (int i = 0; i < spans.length; i++) { 1304 right -= spans[i].getLeadingMargin(par); 1305 } 1306 } 1307 } 1308 1309 return right; 1310 } 1311 1312 private static void drawText(Canvas canvas, 1313 CharSequence text, int start, int end, 1314 int dir, Directions directions, 1315 float x, int top, int y, int bottom, 1316 TextPaint paint, 1317 TextPaint workPaint, 1318 boolean hasTabs, Object[] parspans) { 1319 char[] buf; 1320 if (!hasTabs) { 1321 if (directions == DIRS_ALL_LEFT_TO_RIGHT) { 1322 if (Config.DEBUG) { 1323 Assert.assertTrue(DIR_LEFT_TO_RIGHT == dir); 1324 } 1325 Styled.drawText(canvas, text, start, end, dir, false, x, top, y, bottom, paint, workPaint, false); 1326 return; 1327 } 1328 buf = null; 1329 } else { 1330 buf = TextUtils.obtain(end - start); 1331 TextUtils.getChars(text, start, end, buf, 0); 1332 } 1333 1334 float h = 0; 1335 1336 int here = 0; 1337 for (int i = 0; i < directions.mDirections.length; i++) { 1338 int there = here + directions.mDirections[i]; 1339 if (there > end - start) 1340 there = end - start; 1341 1342 int segstart = here; 1343 for (int j = hasTabs ? here : there; j <= there; j++) { 1344 if (j == there || buf[j] == '\t') { 1345 h += Styled.drawText(canvas, text, 1346 start + segstart, start + j, 1347 dir, (i & 1) != 0, x + h, 1348 top, y, bottom, paint, workPaint, 1349 start + j != end); 1350 1351 if (j != there && buf[j] == '\t') 1352 h = dir * nextTab(text, start, end, h * dir, parspans); 1353 1354 segstart = j + 1; 1355 } 1356 } 1357 1358 here = there; 1359 } 1360 1361 if (hasTabs) 1362 TextUtils.recycle(buf); 1363 } 1364 1365 private static float measureText(TextPaint paint, 1366 TextPaint workPaint, 1367 CharSequence text, 1368 int start, int offset, int end, 1369 int dir, Directions directions, 1370 boolean trailing, boolean alt, 1371 boolean hasTabs, Object[] tabs) { 1372 char[] buf = null; 1373 1374 if (hasTabs) { 1375 buf = TextUtils.obtain(end - start); 1376 TextUtils.getChars(text, start, end, buf, 0); 1377 } 1378 1379 float h = 0; 1380 1381 if (alt) { 1382 if (dir == DIR_RIGHT_TO_LEFT) 1383 trailing = !trailing; 1384 } 1385 1386 int here = 0; 1387 for (int i = 0; i < directions.mDirections.length; i++) { 1388 if (alt) 1389 trailing = !trailing; 1390 1391 int there = here + directions.mDirections[i]; 1392 if (there > end - start) 1393 there = end - start; 1394 1395 int segstart = here; 1396 for (int j = hasTabs ? here : there; j <= there; j++) { 1397 if (j == there || buf[j] == '\t') { 1398 float segw; 1399 1400 if (offset < start + j || 1401 (trailing && offset <= start + j)) { 1402 if (dir == DIR_LEFT_TO_RIGHT && (i & 1) == 0) { 1403 h += Styled.measureText(paint, workPaint, text, 1404 start + segstart, offset, 1405 null); 1406 return h; 1407 } 1408 1409 if (dir == DIR_RIGHT_TO_LEFT && (i & 1) != 0) { 1410 h -= Styled.measureText(paint, workPaint, text, 1411 start + segstart, offset, 1412 null); 1413 return h; 1414 } 1415 } 1416 1417 segw = Styled.measureText(paint, workPaint, text, 1418 start + segstart, start + j, 1419 null); 1420 1421 if (offset < start + j || 1422 (trailing && offset <= start + j)) { 1423 if (dir == DIR_LEFT_TO_RIGHT) { 1424 h += segw - Styled.measureText(paint, workPaint, 1425 text, 1426 start + segstart, 1427 offset, null); 1428 return h; 1429 } 1430 1431 if (dir == DIR_RIGHT_TO_LEFT) { 1432 h -= segw - Styled.measureText(paint, workPaint, 1433 text, 1434 start + segstart, 1435 offset, null); 1436 return h; 1437 } 1438 } 1439 1440 if (dir == DIR_RIGHT_TO_LEFT) 1441 h -= segw; 1442 else 1443 h += segw; 1444 1445 if (j != there && buf[j] == '\t') { 1446 if (offset == start + j) 1447 return h; 1448 1449 h = dir * nextTab(text, start, end, h * dir, tabs); 1450 } 1451 1452 segstart = j + 1; 1453 } 1454 } 1455 1456 here = there; 1457 } 1458 1459 if (hasTabs) 1460 TextUtils.recycle(buf); 1461 1462 return h; 1463 } 1464 1465 /* package */ static float measureText(TextPaint paint, 1466 TextPaint workPaint, 1467 CharSequence text, 1468 int start, int end, 1469 Paint.FontMetricsInt fm, 1470 boolean hasTabs, Object[] tabs) { 1471 char[] buf = null; 1472 1473 if (hasTabs) { 1474 buf = TextUtils.obtain(end - start); 1475 TextUtils.getChars(text, start, end, buf, 0); 1476 } 1477 1478 int len = end - start; 1479 1480 int here = 0; 1481 float h = 0; 1482 int ab = 0, be = 0; 1483 int top = 0, bot = 0; 1484 1485 if (fm != null) { 1486 fm.ascent = 0; 1487 fm.descent = 0; 1488 } 1489 1490 for (int i = hasTabs ? 0 : len; i <= len; i++) { 1491 if (i == len || buf[i] == '\t') { 1492 workPaint.baselineShift = 0; 1493 1494 h += Styled.measureText(paint, workPaint, text, 1495 start + here, start + i, 1496 fm); 1497 1498 if (fm != null) { 1499 if (workPaint.baselineShift < 0) { 1500 fm.ascent += workPaint.baselineShift; 1501 fm.top += workPaint.baselineShift; 1502 } else { 1503 fm.descent += workPaint.baselineShift; 1504 fm.bottom += workPaint.baselineShift; 1505 } 1506 } 1507 1508 if (i != len) 1509 h = nextTab(text, start, end, h, tabs); 1510 1511 if (fm != null) { 1512 if (fm.ascent < ab) { 1513 ab = fm.ascent; 1514 } 1515 if (fm.descent > be) { 1516 be = fm.descent; 1517 } 1518 1519 if (fm.top < top) { 1520 top = fm.top; 1521 } 1522 if (fm.bottom > bot) { 1523 bot = fm.bottom; 1524 } 1525 } 1526 1527 here = i + 1; 1528 } 1529 } 1530 1531 if (fm != null) { 1532 fm.ascent = ab; 1533 fm.descent = be; 1534 fm.top = top; 1535 fm.bottom = bot; 1536 } 1537 1538 if (hasTabs) 1539 TextUtils.recycle(buf); 1540 1541 return h; 1542 } 1543 1544 /* package */ static float nextTab(CharSequence text, int start, int end, 1545 float h, Object[] tabs) { 1546 float nh = Float.MAX_VALUE; 1547 boolean alltabs = false; 1548 1549 if (text instanceof Spanned) { 1550 if (tabs == null) { 1551 tabs = ((Spanned) text).getSpans(start, end, TabStopSpan.class); 1552 alltabs = true; 1553 } 1554 1555 for (int i = 0; i < tabs.length; i++) { 1556 if (!alltabs) { 1557 if (!(tabs[i] instanceof TabStopSpan)) 1558 continue; 1559 } 1560 1561 int where = ((TabStopSpan) tabs[i]).getTabStop(); 1562 1563 if (where < nh && where > h) 1564 nh = where; 1565 } 1566 1567 if (nh != Float.MAX_VALUE) 1568 return nh; 1569 } 1570 1571 return ((int) ((h + TAB_INCREMENT) / TAB_INCREMENT)) * TAB_INCREMENT; 1572 } 1573 1574 protected final boolean isSpanned() { 1575 return mSpannedText; 1576 } 1577 1578 private void ellipsize(int start, int end, int line, 1579 char[] dest, int destoff) { 1580 int ellipsisCount = getEllipsisCount(line); 1581 1582 if (ellipsisCount == 0) { 1583 return; 1584 } 1585 1586 int ellipsisStart = getEllipsisStart(line); 1587 int linestart = getLineStart(line); 1588 1589 for (int i = ellipsisStart; i < ellipsisStart + ellipsisCount; i++) { 1590 char c; 1591 1592 if (i == ellipsisStart) { 1593 c = '\u2026'; // ellipsis 1594 } else { 1595 c = '\uFEFF'; // 0-width space 1596 } 1597 1598 int a = i + linestart; 1599 1600 if (a >= start && a < end) { 1601 dest[destoff + a - start] = c; 1602 } 1603 } 1604 } 1605 1606 /** 1607 * Stores information about bidirectional (left-to-right or right-to-left) 1608 * text within the layout of a line. TODO: This work is not complete 1609 * or correct and will be fleshed out in a later revision. 1610 */ 1611 public static class Directions { 1612 private short[] mDirections; 1613 1614 /* package */ Directions(short[] dirs) { 1615 mDirections = dirs; 1616 } 1617 } 1618 1619 /** 1620 * Return the offset of the first character to be ellipsized away, 1621 * relative to the start of the line. (So 0 if the beginning of the 1622 * line is ellipsized, not getLineStart().) 1623 */ 1624 public abstract int getEllipsisStart(int line); 1625 /** 1626 * Returns the number of characters to be ellipsized away, or 0 if 1627 * no ellipsis is to take place. 1628 */ 1629 public abstract int getEllipsisCount(int line); 1630 1631 /* package */ static class Ellipsizer implements CharSequence, GetChars { 1632 /* package */ CharSequence mText; 1633 /* package */ Layout mLayout; 1634 /* package */ int mWidth; 1635 /* package */ TextUtils.TruncateAt mMethod; 1636 1637 public Ellipsizer(CharSequence s) { 1638 mText = s; 1639 } 1640 1641 public char charAt(int off) { 1642 char[] buf = TextUtils.obtain(1); 1643 getChars(off, off + 1, buf, 0); 1644 char ret = buf[0]; 1645 1646 TextUtils.recycle(buf); 1647 return ret; 1648 } 1649 1650 public void getChars(int start, int end, char[] dest, int destoff) { 1651 int line1 = mLayout.getLineForOffset(start); 1652 int line2 = mLayout.getLineForOffset(end); 1653 1654 TextUtils.getChars(mText, start, end, dest, destoff); 1655 1656 for (int i = line1; i <= line2; i++) { 1657 mLayout.ellipsize(start, end, i, dest, destoff); 1658 } 1659 } 1660 1661 public int length() { 1662 return mText.length(); 1663 } 1664 1665 public CharSequence subSequence(int start, int end) { 1666 char[] s = new char[end - start]; 1667 getChars(start, end, s, 0); 1668 return new String(s); 1669 } 1670 1671 public String toString() { 1672 char[] s = new char[length()]; 1673 getChars(0, length(), s, 0); 1674 return new String(s); 1675 } 1676 1677 } 1678 1679 /* package */ static class SpannedEllipsizer 1680 extends Ellipsizer implements Spanned { 1681 private Spanned mSpanned; 1682 1683 public SpannedEllipsizer(CharSequence display) { 1684 super(display); 1685 mSpanned = (Spanned) display; 1686 } 1687 1688 public <T> T[] getSpans(int start, int end, Class<T> type) { 1689 return mSpanned.getSpans(start, end, type); 1690 } 1691 1692 public int getSpanStart(Object tag) { 1693 return mSpanned.getSpanStart(tag); 1694 } 1695 1696 public int getSpanEnd(Object tag) { 1697 return mSpanned.getSpanEnd(tag); 1698 } 1699 1700 public int getSpanFlags(Object tag) { 1701 return mSpanned.getSpanFlags(tag); 1702 } 1703 1704 public int nextSpanTransition(int start, int limit, Class type) { 1705 return mSpanned.nextSpanTransition(start, limit, type); 1706 } 1707 1708 public CharSequence subSequence(int start, int end) { 1709 char[] s = new char[end - start]; 1710 getChars(start, end, s, 0); 1711 1712 SpannableString ss = new SpannableString(new String(s)); 1713 TextUtils.copySpansFrom(mSpanned, start, end, Object.class, ss, 0); 1714 return ss; 1715 } 1716 } 1717 1718 private CharSequence mText; 1719 private TextPaint mPaint; 1720 /* package */ TextPaint mWorkPaint; 1721 private int mWidth; 1722 private Alignment mAlignment = Alignment.ALIGN_NORMAL; 1723 private float mSpacingMult; 1724 private float mSpacingAdd; 1725 private static Rect sTempRect = new Rect(); 1726 private boolean mSpannedText; 1727 1728 public static final int DIR_LEFT_TO_RIGHT = 1; 1729 public static final int DIR_RIGHT_TO_LEFT = -1; 1730 1731 public enum Alignment { 1732 ALIGN_NORMAL, 1733 ALIGN_OPPOSITE, 1734 ALIGN_CENTER, 1735 // XXX ALIGN_LEFT, 1736 // XXX ALIGN_RIGHT, 1737 } 1738 1739 private static final int TAB_INCREMENT = 20; 1740 1741 /* package */ static final Directions DIRS_ALL_LEFT_TO_RIGHT = 1742 new Directions(new short[] { 32767 }); 1743 /* package */ static final Directions DIRS_ALL_RIGHT_TO_LEFT = 1744 new Directions(new short[] { 0, 32767 }); 1745 1746} 1747 1748