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