Layout.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.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 int fn = TextKeyListener.getMetaState(editingBuffer, 1079 KeyEvent.META_ALT_ON); 1080 int dist = 0; 1081 1082 if (caps != 0 || fn != 0) { 1083 dist = (bottom - top) >> 2; 1084 1085 if (fn != 0) 1086 top += dist; 1087 if (caps != 0) 1088 bottom -= dist; 1089 } 1090 1091 if (h1 < 0.5f) 1092 h1 = 0.5f; 1093 if (h2 < 0.5f) 1094 h2 = 0.5f; 1095 1096 if (h1 == h2) { 1097 dest.moveTo(h1, top); 1098 dest.lineTo(h1, bottom); 1099 } else { 1100 dest.moveTo(h1, top); 1101 dest.lineTo(h1, (top + bottom) >> 1); 1102 1103 dest.moveTo(h2, (top + bottom) >> 1); 1104 dest.lineTo(h2, bottom); 1105 } 1106 1107 if (caps == 2) { 1108 dest.moveTo(h2, bottom); 1109 dest.lineTo(h2 - dist, bottom + dist); 1110 dest.lineTo(h2, bottom); 1111 dest.lineTo(h2 + dist, bottom + dist); 1112 } else if (caps == 1) { 1113 dest.moveTo(h2, bottom); 1114 dest.lineTo(h2 - dist, bottom + dist); 1115 1116 dest.moveTo(h2 - dist, bottom + dist - 0.5f); 1117 dest.lineTo(h2 + dist, bottom + dist - 0.5f); 1118 1119 dest.moveTo(h2 + dist, bottom + dist); 1120 dest.lineTo(h2, bottom); 1121 } 1122 1123 if (fn == 2) { 1124 dest.moveTo(h1, top); 1125 dest.lineTo(h1 - dist, top - dist); 1126 dest.lineTo(h1, top); 1127 dest.lineTo(h1 + dist, top - dist); 1128 } else if (fn == 1) { 1129 dest.moveTo(h1, top); 1130 dest.lineTo(h1 - dist, top - dist); 1131 1132 dest.moveTo(h1 - dist, top - dist + 0.5f); 1133 dest.lineTo(h1 + dist, top - dist + 0.5f); 1134 1135 dest.moveTo(h1 + dist, top - dist); 1136 dest.lineTo(h1, top); 1137 } 1138 } 1139 1140 private void addSelection(int line, int start, int end, 1141 int top, int bottom, Path dest) { 1142 int linestart = getLineStart(line); 1143 int lineend = getLineEnd(line); 1144 Directions dirs = getLineDirections(line); 1145 1146 if (lineend > linestart && mText.charAt(lineend - 1) == '\n') 1147 lineend--; 1148 1149 int here = linestart; 1150 for (int i = 0; i < dirs.mDirections.length; i++) { 1151 int there = here + dirs.mDirections[i]; 1152 if (there > lineend) 1153 there = lineend; 1154 1155 if (start <= there && end >= here) { 1156 int st = Math.max(start, here); 1157 int en = Math.min(end, there); 1158 1159 if (st != en) { 1160 float h1 = getHorizontal(st, false, false, line); 1161 float h2 = getHorizontal(en, true, false, line); 1162 1163 dest.addRect(h1, top, h2, bottom, Path.Direction.CW); 1164 } 1165 } 1166 1167 here = there; 1168 } 1169 } 1170 1171 /** 1172 * Fills in the specified Path with a representation of a highlight 1173 * between the specified offsets. This will often be a rectangle 1174 * or a potentially discontinuous set of rectangles. If the start 1175 * and end are the same, the returned path is empty. 1176 */ 1177 public void getSelectionPath(int start, int end, Path dest) { 1178 dest.reset(); 1179 1180 if (start == end) 1181 return; 1182 1183 if (end < start) { 1184 int temp = end; 1185 end = start; 1186 start = temp; 1187 } 1188 1189 int startline = getLineForOffset(start); 1190 int endline = getLineForOffset(end); 1191 1192 int top = getLineTop(startline); 1193 int bottom = getLineBottom(endline); 1194 1195 if (startline == endline) { 1196 addSelection(startline, start, end, top, bottom, dest); 1197 } else { 1198 final float width = mWidth; 1199 1200 addSelection(startline, start, getLineEnd(startline), 1201 top, getLineBottom(startline), dest); 1202 1203 if (getParagraphDirection(startline) == DIR_RIGHT_TO_LEFT) 1204 dest.addRect(getLineLeft(startline), top, 1205 0, getLineBottom(startline), Path.Direction.CW); 1206 else 1207 dest.addRect(getLineRight(startline), top, 1208 width, getLineBottom(startline), Path.Direction.CW); 1209 1210 for (int i = startline + 1; i < endline; i++) { 1211 top = getLineTop(i); 1212 bottom = getLineBottom(i); 1213 dest.addRect(0, top, width, bottom, Path.Direction.CW); 1214 } 1215 1216 top = getLineTop(endline); 1217 bottom = getLineBottom(endline); 1218 1219 addSelection(endline, getLineStart(endline), end, 1220 top, bottom, dest); 1221 1222 if (getParagraphDirection(endline) == DIR_RIGHT_TO_LEFT) 1223 dest.addRect(width, top, getLineRight(endline), bottom, Path.Direction.CW); 1224 else 1225 dest.addRect(0, top, getLineLeft(endline), bottom, Path.Direction.CW); 1226 } 1227 } 1228 1229 /** 1230 * Get the alignment of the specified paragraph, taking into account 1231 * markup attached to it. 1232 */ 1233 public final Alignment getParagraphAlignment(int line) { 1234 Alignment align = mAlignment; 1235 1236 if (mSpannedText) { 1237 Spanned sp = (Spanned) mText; 1238 AlignmentSpan[] spans = sp.getSpans(getLineStart(line), 1239 getLineEnd(line), 1240 AlignmentSpan.class); 1241 1242 int spanLength = spans.length; 1243 if (spanLength > 0) { 1244 align = spans[spanLength-1].getAlignment(); 1245 } 1246 } 1247 1248 return align; 1249 } 1250 1251 /** 1252 * Get the left edge of the specified paragraph, inset by left margins. 1253 */ 1254 public final int getParagraphLeft(int line) { 1255 int dir = getParagraphDirection(line); 1256 1257 int left = 0; 1258 1259 boolean par = false; 1260 int off = getLineStart(line); 1261 if (off == 0 || mText.charAt(off - 1) == '\n') 1262 par = true; 1263 1264 if (dir == DIR_LEFT_TO_RIGHT) { 1265 if (mSpannedText) { 1266 Spanned sp = (Spanned) mText; 1267 LeadingMarginSpan[] spans = sp.getSpans(getLineStart(line), 1268 getLineEnd(line), 1269 LeadingMarginSpan.class); 1270 1271 for (int i = 0; i < spans.length; i++) { 1272 left += spans[i].getLeadingMargin(par); 1273 } 1274 } 1275 } 1276 1277 return left; 1278 } 1279 1280 /** 1281 * Get the right edge of the specified paragraph, inset by right margins. 1282 */ 1283 public final int getParagraphRight(int line) { 1284 int dir = getParagraphDirection(line); 1285 1286 int right = mWidth; 1287 1288 boolean par = false; 1289 int off = getLineStart(line); 1290 if (off == 0 || mText.charAt(off - 1) == '\n') 1291 par = true; 1292 1293 1294 if (dir == DIR_RIGHT_TO_LEFT) { 1295 if (mSpannedText) { 1296 Spanned sp = (Spanned) mText; 1297 LeadingMarginSpan[] spans = sp.getSpans(getLineStart(line), 1298 getLineEnd(line), 1299 LeadingMarginSpan.class); 1300 1301 for (int i = 0; i < spans.length; i++) { 1302 right -= spans[i].getLeadingMargin(par); 1303 } 1304 } 1305 } 1306 1307 return right; 1308 } 1309 1310 private static void drawText(Canvas canvas, 1311 CharSequence text, int start, int end, 1312 int dir, Directions directions, 1313 float x, int top, int y, int bottom, 1314 TextPaint paint, 1315 TextPaint workPaint, 1316 boolean hasTabs, Object[] parspans) { 1317 char[] buf; 1318 if (!hasTabs) { 1319 if (directions == DIRS_ALL_LEFT_TO_RIGHT) { 1320 if (Config.DEBUG) { 1321 Assert.assertTrue(DIR_LEFT_TO_RIGHT == dir); 1322 } 1323 Styled.drawText(canvas, text, start, end, dir, false, x, top, y, bottom, paint, workPaint, false); 1324 return; 1325 } 1326 buf = null; 1327 } else { 1328 buf = TextUtils.obtain(end - start); 1329 TextUtils.getChars(text, start, end, buf, 0); 1330 } 1331 1332 float h = 0; 1333 1334 int here = 0; 1335 for (int i = 0; i < directions.mDirections.length; i++) { 1336 int there = here + directions.mDirections[i]; 1337 if (there > end - start) 1338 there = end - start; 1339 1340 int segstart = here; 1341 for (int j = hasTabs ? here : there; j <= there; j++) { 1342 if (j == there || buf[j] == '\t') { 1343 h += Styled.drawText(canvas, text, 1344 start + segstart, start + j, 1345 dir, (i & 1) != 0, x + h, 1346 top, y, bottom, paint, workPaint, 1347 start + j != end); 1348 1349 if (j != there && buf[j] == '\t') 1350 h = dir * nextTab(text, start, end, h * dir, parspans); 1351 1352 segstart = j + 1; 1353 } 1354 } 1355 1356 here = there; 1357 } 1358 1359 if (hasTabs) 1360 TextUtils.recycle(buf); 1361 } 1362 1363 private static float measureText(TextPaint paint, 1364 TextPaint workPaint, 1365 CharSequence text, 1366 int start, int offset, int end, 1367 int dir, Directions directions, 1368 boolean trailing, boolean alt, 1369 boolean hasTabs, Object[] tabs) { 1370 char[] buf = null; 1371 1372 if (hasTabs) { 1373 buf = TextUtils.obtain(end - start); 1374 TextUtils.getChars(text, start, end, buf, 0); 1375 } 1376 1377 float h = 0; 1378 1379 if (alt) { 1380 if (dir == DIR_RIGHT_TO_LEFT) 1381 trailing = !trailing; 1382 } 1383 1384 int here = 0; 1385 for (int i = 0; i < directions.mDirections.length; i++) { 1386 if (alt) 1387 trailing = !trailing; 1388 1389 int there = here + directions.mDirections[i]; 1390 if (there > end - start) 1391 there = end - start; 1392 1393 int segstart = here; 1394 for (int j = hasTabs ? here : there; j <= there; j++) { 1395 if (j == there || buf[j] == '\t') { 1396 float segw; 1397 1398 if (offset < start + j || 1399 (trailing && offset <= start + j)) { 1400 if (dir == DIR_LEFT_TO_RIGHT && (i & 1) == 0) { 1401 h += Styled.measureText(paint, workPaint, text, 1402 start + segstart, offset, 1403 null); 1404 return h; 1405 } 1406 1407 if (dir == DIR_RIGHT_TO_LEFT && (i & 1) != 0) { 1408 h -= Styled.measureText(paint, workPaint, text, 1409 start + segstart, offset, 1410 null); 1411 return h; 1412 } 1413 } 1414 1415 segw = Styled.measureText(paint, workPaint, text, 1416 start + segstart, start + j, 1417 null); 1418 1419 if (offset < start + j || 1420 (trailing && offset <= start + j)) { 1421 if (dir == DIR_LEFT_TO_RIGHT) { 1422 h += segw - Styled.measureText(paint, workPaint, 1423 text, 1424 start + segstart, 1425 offset, null); 1426 return h; 1427 } 1428 1429 if (dir == DIR_RIGHT_TO_LEFT) { 1430 h -= segw - Styled.measureText(paint, workPaint, 1431 text, 1432 start + segstart, 1433 offset, null); 1434 return h; 1435 } 1436 } 1437 1438 if (dir == DIR_RIGHT_TO_LEFT) 1439 h -= segw; 1440 else 1441 h += segw; 1442 1443 if (j != there && buf[j] == '\t') { 1444 if (offset == start + j) 1445 return h; 1446 1447 h = dir * nextTab(text, start, end, h * dir, tabs); 1448 } 1449 1450 segstart = j + 1; 1451 } 1452 } 1453 1454 here = there; 1455 } 1456 1457 if (hasTabs) 1458 TextUtils.recycle(buf); 1459 1460 return h; 1461 } 1462 1463 /* package */ static float measureText(TextPaint paint, 1464 TextPaint workPaint, 1465 CharSequence text, 1466 int start, int end, 1467 Paint.FontMetricsInt fm, 1468 boolean hasTabs, Object[] tabs) { 1469 char[] buf = null; 1470 1471 if (hasTabs) { 1472 buf = TextUtils.obtain(end - start); 1473 TextUtils.getChars(text, start, end, buf, 0); 1474 } 1475 1476 int len = end - start; 1477 1478 int here = 0; 1479 float h = 0; 1480 int ab = 0, be = 0; 1481 int top = 0, bot = 0; 1482 1483 if (fm != null) { 1484 fm.ascent = 0; 1485 fm.descent = 0; 1486 } 1487 1488 for (int i = hasTabs ? 0 : len; i <= len; i++) { 1489 if (i == len || buf[i] == '\t') { 1490 workPaint.baselineShift = 0; 1491 1492 h += Styled.measureText(paint, workPaint, text, 1493 start + here, start + i, 1494 fm); 1495 1496 if (fm != null) { 1497 if (workPaint.baselineShift < 0) { 1498 fm.ascent += workPaint.baselineShift; 1499 fm.top += workPaint.baselineShift; 1500 } else { 1501 fm.descent += workPaint.baselineShift; 1502 fm.bottom += workPaint.baselineShift; 1503 } 1504 } 1505 1506 if (i != len) 1507 h = nextTab(text, start, end, h, tabs); 1508 1509 if (fm != null) { 1510 if (fm.ascent < ab) { 1511 ab = fm.ascent; 1512 } 1513 if (fm.descent > be) { 1514 be = fm.descent; 1515 } 1516 1517 if (fm.top < top) { 1518 top = fm.top; 1519 } 1520 if (fm.bottom > bot) { 1521 bot = fm.bottom; 1522 } 1523 } 1524 1525 here = i + 1; 1526 } 1527 } 1528 1529 if (fm != null) { 1530 fm.ascent = ab; 1531 fm.descent = be; 1532 fm.top = top; 1533 fm.bottom = bot; 1534 } 1535 1536 if (hasTabs) 1537 TextUtils.recycle(buf); 1538 1539 return h; 1540 } 1541 1542 /* package */ static float nextTab(CharSequence text, int start, int end, 1543 float h, Object[] tabs) { 1544 float nh = Float.MAX_VALUE; 1545 boolean alltabs = false; 1546 1547 if (text instanceof Spanned) { 1548 if (tabs == null) { 1549 tabs = ((Spanned) text).getSpans(start, end, TabStopSpan.class); 1550 alltabs = true; 1551 } 1552 1553 for (int i = 0; i < tabs.length; i++) { 1554 if (!alltabs) { 1555 if (!(tabs[i] instanceof TabStopSpan)) 1556 continue; 1557 } 1558 1559 int where = ((TabStopSpan) tabs[i]).getTabStop(); 1560 1561 if (where < nh && where > h) 1562 nh = where; 1563 } 1564 1565 if (nh != Float.MAX_VALUE) 1566 return nh; 1567 } 1568 1569 return ((int) ((h + TAB_INCREMENT) / TAB_INCREMENT)) * TAB_INCREMENT; 1570 } 1571 1572 protected final boolean isSpanned() { 1573 return mSpannedText; 1574 } 1575 1576 private void ellipsize(int start, int end, int line, 1577 char[] dest, int destoff) { 1578 int ellipsisCount = getEllipsisCount(line); 1579 1580 if (ellipsisCount == 0) { 1581 return; 1582 } 1583 1584 int ellipsisStart = getEllipsisStart(line); 1585 int linestart = getLineStart(line); 1586 1587 for (int i = ellipsisStart; i < ellipsisStart + ellipsisCount; i++) { 1588 char c; 1589 1590 if (i == ellipsisStart) { 1591 c = '\u2026'; // ellipsis 1592 } else { 1593 c = '\uFEFF'; // 0-width space 1594 } 1595 1596 int a = i + linestart; 1597 1598 if (a >= start && a < end) { 1599 dest[destoff + a - start] = c; 1600 } 1601 } 1602 } 1603 1604 /** 1605 * Stores information about bidirectional (left-to-right or right-to-left) 1606 * text within the layout of a line. TODO: This work is not complete 1607 * or correct and will be fleshed out in a later revision. 1608 */ 1609 public static class Directions { 1610 private short[] mDirections; 1611 1612 /* package */ Directions(short[] dirs) { 1613 mDirections = dirs; 1614 } 1615 } 1616 1617 /** 1618 * Return the offset of the first character to be ellipsized away, 1619 * relative to the start of the line. (So 0 if the beginning of the 1620 * line is ellipsized, not getLineStart().) 1621 */ 1622 public abstract int getEllipsisStart(int line); 1623 /** 1624 * Returns the number of characters to be ellipsized away, or 0 if 1625 * no ellipsis is to take place. 1626 */ 1627 public abstract int getEllipsisCount(int line); 1628 1629 /* package */ static class Ellipsizer implements CharSequence, GetChars { 1630 /* package */ CharSequence mText; 1631 /* package */ Layout mLayout; 1632 /* package */ int mWidth; 1633 /* package */ TextUtils.TruncateAt mMethod; 1634 1635 public Ellipsizer(CharSequence s) { 1636 mText = s; 1637 } 1638 1639 public char charAt(int off) { 1640 char[] buf = TextUtils.obtain(1); 1641 getChars(off, off + 1, buf, 0); 1642 char ret = buf[0]; 1643 1644 TextUtils.recycle(buf); 1645 return ret; 1646 } 1647 1648 public void getChars(int start, int end, char[] dest, int destoff) { 1649 int line1 = mLayout.getLineForOffset(start); 1650 int line2 = mLayout.getLineForOffset(end); 1651 1652 TextUtils.getChars(mText, start, end, dest, destoff); 1653 1654 for (int i = line1; i <= line2; i++) { 1655 mLayout.ellipsize(start, end, i, dest, destoff); 1656 } 1657 } 1658 1659 public int length() { 1660 return mText.length(); 1661 } 1662 1663 public CharSequence subSequence(int start, int end) { 1664 char[] s = new char[end - start]; 1665 getChars(start, end, s, 0); 1666 return new String(s); 1667 } 1668 1669 public String toString() { 1670 char[] s = new char[length()]; 1671 getChars(0, length(), s, 0); 1672 return new String(s); 1673 } 1674 1675 } 1676 1677 /* package */ static class SpannedEllipsizer 1678 extends Ellipsizer implements Spanned { 1679 private Spanned mSpanned; 1680 1681 public SpannedEllipsizer(CharSequence display) { 1682 super(display); 1683 mSpanned = (Spanned) display; 1684 } 1685 1686 public <T> T[] getSpans(int start, int end, Class<T> type) { 1687 return mSpanned.getSpans(start, end, type); 1688 } 1689 1690 public int getSpanStart(Object tag) { 1691 return mSpanned.getSpanStart(tag); 1692 } 1693 1694 public int getSpanEnd(Object tag) { 1695 return mSpanned.getSpanEnd(tag); 1696 } 1697 1698 public int getSpanFlags(Object tag) { 1699 return mSpanned.getSpanFlags(tag); 1700 } 1701 1702 public int nextSpanTransition(int start, int limit, Class type) { 1703 return mSpanned.nextSpanTransition(start, limit, type); 1704 } 1705 1706 public CharSequence subSequence(int start, int end) { 1707 char[] s = new char[end - start]; 1708 getChars(start, end, s, 0); 1709 1710 SpannableString ss = new SpannableString(new String(s)); 1711 TextUtils.copySpansFrom(mSpanned, start, end, Object.class, ss, 0); 1712 return ss; 1713 } 1714 } 1715 1716 private CharSequence mText; 1717 private TextPaint mPaint; 1718 /* package */ TextPaint mWorkPaint; 1719 private int mWidth; 1720 private Alignment mAlignment = Alignment.ALIGN_NORMAL; 1721 private float mSpacingMult; 1722 private float mSpacingAdd; 1723 private static Rect sTempRect = new Rect(); 1724 private boolean mSpannedText; 1725 1726 public static final int DIR_LEFT_TO_RIGHT = 1; 1727 public static final int DIR_RIGHT_TO_LEFT = -1; 1728 1729 public enum Alignment { 1730 ALIGN_NORMAL, 1731 ALIGN_OPPOSITE, 1732 ALIGN_CENTER, 1733 // XXX ALIGN_LEFT, 1734 // XXX ALIGN_RIGHT, 1735 } 1736 1737 private static final int TAB_INCREMENT = 20; 1738 1739 /* package */ static final Directions DIRS_ALL_LEFT_TO_RIGHT = 1740 new Directions(new short[] { 32767 }); 1741 /* package */ static final Directions DIRS_ALL_RIGHT_TO_LEFT = 1742 new Directions(new short[] { 0, 32767 }); 1743 1744} 1745 1746