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