Home | History | Annotate | Download | only in text
      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 
     17 package android.text;
     18 
     19 import android.annotation.IntDef;
     20 import android.emoji.EmojiFactory;
     21 import android.graphics.Canvas;
     22 import android.graphics.Paint;
     23 import android.graphics.Path;
     24 import android.graphics.Rect;
     25 import android.text.method.TextKeyListener;
     26 import android.text.style.AlignmentSpan;
     27 import android.text.style.LeadingMarginSpan;
     28 import android.text.style.LeadingMarginSpan.LeadingMarginSpan2;
     29 import android.text.style.LineBackgroundSpan;
     30 import android.text.style.ParagraphStyle;
     31 import android.text.style.ReplacementSpan;
     32 import android.text.style.TabStopSpan;
     33 
     34 import com.android.internal.util.ArrayUtils;
     35 import com.android.internal.util.GrowingArrayUtils;
     36 
     37 import java.lang.annotation.Retention;
     38 import java.lang.annotation.RetentionPolicy;
     39 import java.util.Arrays;
     40 
     41 /**
     42  * A base class that manages text layout in visual elements on
     43  * the screen.
     44  * <p>For text that will be edited, use a {@link DynamicLayout},
     45  * which will be updated as the text changes.
     46  * For text that will not change, use a {@link StaticLayout}.
     47  */
     48 public abstract class Layout {
     49     /** @hide */
     50     @IntDef({BREAK_STRATEGY_SIMPLE, BREAK_STRATEGY_HIGH_QUALITY, BREAK_STRATEGY_BALANCED})
     51     @Retention(RetentionPolicy.SOURCE)
     52     public @interface BreakStrategy {}
     53 
     54     /**
     55      * Value for break strategy indicating simple line breaking. Automatic hyphens are not added
     56      * (though soft hyphens are respected), and modifying text generally doesn't affect the layout
     57      * before it (which yields a more consistent user experience when editing), but layout may not
     58      * be the highest quality.
     59      */
     60     public static final int BREAK_STRATEGY_SIMPLE = 0;
     61 
     62     /**
     63      * Value for break strategy indicating high quality line breaking, including automatic
     64      * hyphenation and doing whole-paragraph optimization of line breaks.
     65      */
     66     public static final int BREAK_STRATEGY_HIGH_QUALITY = 1;
     67 
     68     /**
     69      * Value for break strategy indicating balanced line breaking. The breaks are chosen to
     70      * make all lines as close to the same length as possible, including automatic hyphenation.
     71      */
     72     public static final int BREAK_STRATEGY_BALANCED = 2;
     73 
     74     /** @hide */
     75     @IntDef({HYPHENATION_FREQUENCY_NORMAL, HYPHENATION_FREQUENCY_FULL,
     76              HYPHENATION_FREQUENCY_NONE})
     77     @Retention(RetentionPolicy.SOURCE)
     78     public @interface HyphenationFrequency {}
     79 
     80     /**
     81      * Value for hyphenation frequency indicating no automatic hyphenation. Useful
     82      * for backward compatibility, and for cases where the automatic hyphenation algorithm results
     83      * in incorrect hyphenation. Mid-word breaks may still happen when a word is wider than the
     84      * layout and there is otherwise no valid break. Soft hyphens are ignored and will not be used
     85      * as suggestions for potential line breaks.
     86      */
     87     public static final int HYPHENATION_FREQUENCY_NONE = 0;
     88 
     89     /**
     90      * Value for hyphenation frequency indicating a light amount of automatic hyphenation, which
     91      * is a conservative default. Useful for informal cases, such as short sentences or chat
     92      * messages.
     93      */
     94     public static final int HYPHENATION_FREQUENCY_NORMAL = 1;
     95 
     96     /**
     97      * Value for hyphenation frequency indicating the full amount of automatic hyphenation, typical
     98      * in typography. Useful for running text and where it's important to put the maximum amount of
     99      * text in a screen with limited space.
    100      */
    101     public static final int HYPHENATION_FREQUENCY_FULL = 2;
    102 
    103     private static final ParagraphStyle[] NO_PARA_SPANS =
    104         ArrayUtils.emptyArray(ParagraphStyle.class);
    105 
    106     /* package */ static final EmojiFactory EMOJI_FACTORY = EmojiFactory.newAvailableInstance();
    107     /* package */ static final int MIN_EMOJI, MAX_EMOJI;
    108 
    109     static {
    110         if (EMOJI_FACTORY != null) {
    111             MIN_EMOJI = EMOJI_FACTORY.getMinimumAndroidPua();
    112             MAX_EMOJI = EMOJI_FACTORY.getMaximumAndroidPua();
    113         } else {
    114             MIN_EMOJI = -1;
    115             MAX_EMOJI = -1;
    116         }
    117     }
    118 
    119     /**
    120      * Return how wide a layout must be in order to display the
    121      * specified text with one line per paragraph.
    122      */
    123     public static float getDesiredWidth(CharSequence source,
    124                                         TextPaint paint) {
    125         return getDesiredWidth(source, 0, source.length(), paint);
    126     }
    127 
    128     /**
    129      * Return how wide a layout must be in order to display the
    130      * specified text slice with one line per paragraph.
    131      */
    132     public static float getDesiredWidth(CharSequence source,
    133                                         int start, int end,
    134                                         TextPaint paint) {
    135         float need = 0;
    136 
    137         int next;
    138         for (int i = start; i <= end; i = next) {
    139             next = TextUtils.indexOf(source, '\n', i, end);
    140 
    141             if (next < 0)
    142                 next = end;
    143 
    144             // note, omits trailing paragraph char
    145             float w = measurePara(paint, source, i, next);
    146 
    147             if (w > need)
    148                 need = w;
    149 
    150             next++;
    151         }
    152 
    153         return need;
    154     }
    155 
    156     /**
    157      * Subclasses of Layout use this constructor to set the display text,
    158      * width, and other standard properties.
    159      * @param text the text to render
    160      * @param paint the default paint for the layout.  Styles can override
    161      * various attributes of the paint.
    162      * @param width the wrapping width for the text.
    163      * @param align whether to left, right, or center the text.  Styles can
    164      * override the alignment.
    165      * @param spacingMult factor by which to scale the font size to get the
    166      * default line spacing
    167      * @param spacingAdd amount to add to the default line spacing
    168      */
    169     protected Layout(CharSequence text, TextPaint paint,
    170                      int width, Alignment align,
    171                      float spacingMult, float spacingAdd) {
    172         this(text, paint, width, align, TextDirectionHeuristics.FIRSTSTRONG_LTR,
    173                 spacingMult, spacingAdd);
    174     }
    175 
    176     /**
    177      * Subclasses of Layout use this constructor to set the display text,
    178      * width, and other standard properties.
    179      * @param text the text to render
    180      * @param paint the default paint for the layout.  Styles can override
    181      * various attributes of the paint.
    182      * @param width the wrapping width for the text.
    183      * @param align whether to left, right, or center the text.  Styles can
    184      * override the alignment.
    185      * @param spacingMult factor by which to scale the font size to get the
    186      * default line spacing
    187      * @param spacingAdd amount to add to the default line spacing
    188      *
    189      * @hide
    190      */
    191     protected Layout(CharSequence text, TextPaint paint,
    192                      int width, Alignment align, TextDirectionHeuristic textDir,
    193                      float spacingMult, float spacingAdd) {
    194 
    195         if (width < 0)
    196             throw new IllegalArgumentException("Layout: " + width + " < 0");
    197 
    198         // Ensure paint doesn't have baselineShift set.
    199         // While normally we don't modify the paint the user passed in,
    200         // we were already doing this in Styled.drawUniformRun with both
    201         // baselineShift and bgColor.  We probably should reevaluate bgColor.
    202         if (paint != null) {
    203             paint.bgColor = 0;
    204             paint.baselineShift = 0;
    205         }
    206 
    207         mText = text;
    208         mPaint = paint;
    209         mWidth = width;
    210         mAlignment = align;
    211         mSpacingMult = spacingMult;
    212         mSpacingAdd = spacingAdd;
    213         mSpannedText = text instanceof Spanned;
    214         mTextDir = textDir;
    215     }
    216 
    217     /**
    218      * Replace constructor properties of this Layout with new ones.  Be careful.
    219      */
    220     /* package */ void replaceWith(CharSequence text, TextPaint paint,
    221                               int width, Alignment align,
    222                               float spacingmult, float spacingadd) {
    223         if (width < 0) {
    224             throw new IllegalArgumentException("Layout: " + width + " < 0");
    225         }
    226 
    227         mText = text;
    228         mPaint = paint;
    229         mWidth = width;
    230         mAlignment = align;
    231         mSpacingMult = spacingmult;
    232         mSpacingAdd = spacingadd;
    233         mSpannedText = text instanceof Spanned;
    234     }
    235 
    236     /**
    237      * Draw this Layout on the specified Canvas.
    238      */
    239     public void draw(Canvas c) {
    240         draw(c, null, null, 0);
    241     }
    242 
    243     /**
    244      * Draw this Layout on the specified canvas, with the highlight path drawn
    245      * between the background and the text.
    246      *
    247      * @param canvas the canvas
    248      * @param highlight the path of the highlight or cursor; can be null
    249      * @param highlightPaint the paint for the highlight
    250      * @param cursorOffsetVertical the amount to temporarily translate the
    251      *        canvas while rendering the highlight
    252      */
    253     public void draw(Canvas canvas, Path highlight, Paint highlightPaint,
    254             int cursorOffsetVertical) {
    255         final long lineRange = getLineRangeForDraw(canvas);
    256         int firstLine = TextUtils.unpackRangeStartFromLong(lineRange);
    257         int lastLine = TextUtils.unpackRangeEndFromLong(lineRange);
    258         if (lastLine < 0) return;
    259 
    260         drawBackground(canvas, highlight, highlightPaint, cursorOffsetVertical,
    261                 firstLine, lastLine);
    262         drawText(canvas, firstLine, lastLine);
    263     }
    264 
    265     /**
    266      * @hide
    267      */
    268     public void drawText(Canvas canvas, int firstLine, int lastLine) {
    269         int previousLineBottom = getLineTop(firstLine);
    270         int previousLineEnd = getLineStart(firstLine);
    271         ParagraphStyle[] spans = NO_PARA_SPANS;
    272         int spanEnd = 0;
    273         TextPaint paint = mPaint;
    274         CharSequence buf = mText;
    275 
    276         Alignment paraAlign = mAlignment;
    277         TabStops tabStops = null;
    278         boolean tabStopsIsInitialized = false;
    279 
    280         TextLine tl = TextLine.obtain();
    281 
    282         // Draw the lines, one at a time.
    283         // The baseline is the top of the following line minus the current line's descent.
    284         for (int lineNum = firstLine; lineNum <= lastLine; lineNum++) {
    285             int start = previousLineEnd;
    286             previousLineEnd = getLineStart(lineNum + 1);
    287             int end = getLineVisibleEnd(lineNum, start, previousLineEnd);
    288 
    289             int ltop = previousLineBottom;
    290             int lbottom = getLineTop(lineNum + 1);
    291             previousLineBottom = lbottom;
    292             int lbaseline = lbottom - getLineDescent(lineNum);
    293 
    294             int dir = getParagraphDirection(lineNum);
    295             int left = 0;
    296             int right = mWidth;
    297 
    298             if (mSpannedText) {
    299                 Spanned sp = (Spanned) buf;
    300                 int textLength = buf.length();
    301                 boolean isFirstParaLine = (start == 0 || buf.charAt(start - 1) == '\n');
    302 
    303                 // New batch of paragraph styles, collect into spans array.
    304                 // Compute the alignment, last alignment style wins.
    305                 // Reset tabStops, we'll rebuild if we encounter a line with
    306                 // tabs.
    307                 // We expect paragraph spans to be relatively infrequent, use
    308                 // spanEnd so that we can check less frequently.  Since
    309                 // paragraph styles ought to apply to entire paragraphs, we can
    310                 // just collect the ones present at the start of the paragraph.
    311                 // If spanEnd is before the end of the paragraph, that's not
    312                 // our problem.
    313                 if (start >= spanEnd && (lineNum == firstLine || isFirstParaLine)) {
    314                     spanEnd = sp.nextSpanTransition(start, textLength,
    315                                                     ParagraphStyle.class);
    316                     spans = getParagraphSpans(sp, start, spanEnd, ParagraphStyle.class);
    317 
    318                     paraAlign = mAlignment;
    319                     for (int n = spans.length - 1; n >= 0; n--) {
    320                         if (spans[n] instanceof AlignmentSpan) {
    321                             paraAlign = ((AlignmentSpan) spans[n]).getAlignment();
    322                             break;
    323                         }
    324                     }
    325 
    326                     tabStopsIsInitialized = false;
    327                 }
    328 
    329                 // Draw all leading margin spans.  Adjust left or right according
    330                 // to the paragraph direction of the line.
    331                 final int length = spans.length;
    332                 boolean useFirstLineMargin = isFirstParaLine;
    333                 for (int n = 0; n < length; n++) {
    334                     if (spans[n] instanceof LeadingMarginSpan2) {
    335                         int count = ((LeadingMarginSpan2) spans[n]).getLeadingMarginLineCount();
    336                         int startLine = getLineForOffset(sp.getSpanStart(spans[n]));
    337                         // if there is more than one LeadingMarginSpan2, use
    338                         // the count that is greatest
    339                         if (lineNum < startLine + count) {
    340                             useFirstLineMargin = true;
    341                             break;
    342                         }
    343                     }
    344                 }
    345                 for (int n = 0; n < length; n++) {
    346                     if (spans[n] instanceof LeadingMarginSpan) {
    347                         LeadingMarginSpan margin = (LeadingMarginSpan) spans[n];
    348                         if (dir == DIR_RIGHT_TO_LEFT) {
    349                             margin.drawLeadingMargin(canvas, paint, right, dir, ltop,
    350                                                      lbaseline, lbottom, buf,
    351                                                      start, end, isFirstParaLine, this);
    352                             right -= margin.getLeadingMargin(useFirstLineMargin);
    353                         } else {
    354                             margin.drawLeadingMargin(canvas, paint, left, dir, ltop,
    355                                                      lbaseline, lbottom, buf,
    356                                                      start, end, isFirstParaLine, this);
    357                             left += margin.getLeadingMargin(useFirstLineMargin);
    358                         }
    359                     }
    360                 }
    361             }
    362 
    363             boolean hasTabOrEmoji = getLineContainsTab(lineNum);
    364             // Can't tell if we have tabs for sure, currently
    365             if (hasTabOrEmoji && !tabStopsIsInitialized) {
    366                 if (tabStops == null) {
    367                     tabStops = new TabStops(TAB_INCREMENT, spans);
    368                 } else {
    369                     tabStops.reset(TAB_INCREMENT, spans);
    370                 }
    371                 tabStopsIsInitialized = true;
    372             }
    373 
    374             // Determine whether the line aligns to normal, opposite, or center.
    375             Alignment align = paraAlign;
    376             if (align == Alignment.ALIGN_LEFT) {
    377                 align = (dir == DIR_LEFT_TO_RIGHT) ?
    378                     Alignment.ALIGN_NORMAL : Alignment.ALIGN_OPPOSITE;
    379             } else if (align == Alignment.ALIGN_RIGHT) {
    380                 align = (dir == DIR_LEFT_TO_RIGHT) ?
    381                     Alignment.ALIGN_OPPOSITE : Alignment.ALIGN_NORMAL;
    382             }
    383 
    384             int x;
    385             if (align == Alignment.ALIGN_NORMAL) {
    386                 if (dir == DIR_LEFT_TO_RIGHT) {
    387                     x = left + getIndentAdjust(lineNum, Alignment.ALIGN_LEFT);
    388                 } else {
    389                     x = right + getIndentAdjust(lineNum, Alignment.ALIGN_RIGHT);
    390                 }
    391             } else {
    392                 int max = (int)getLineExtent(lineNum, tabStops, false);
    393                 if (align == Alignment.ALIGN_OPPOSITE) {
    394                     if (dir == DIR_LEFT_TO_RIGHT) {
    395                         x = right - max + getIndentAdjust(lineNum, Alignment.ALIGN_RIGHT);
    396                     } else {
    397                         x = left - max + getIndentAdjust(lineNum, Alignment.ALIGN_LEFT);
    398                     }
    399                 } else { // Alignment.ALIGN_CENTER
    400                     max = max & ~1;
    401                     x = ((right + left - max) >> 1) +
    402                             getIndentAdjust(lineNum, Alignment.ALIGN_CENTER);
    403                 }
    404             }
    405 
    406             paint.setHyphenEdit(getHyphen(lineNum));
    407             Directions directions = getLineDirections(lineNum);
    408             if (directions == DIRS_ALL_LEFT_TO_RIGHT && !mSpannedText && !hasTabOrEmoji) {
    409                 // XXX: assumes there's nothing additional to be done
    410                 canvas.drawText(buf, start, end, x, lbaseline, paint);
    411             } else {
    412                 tl.set(paint, buf, start, end, dir, directions, hasTabOrEmoji, tabStops);
    413                 tl.draw(canvas, x, ltop, lbaseline, lbottom);
    414             }
    415             paint.setHyphenEdit(0);
    416         }
    417 
    418         TextLine.recycle(tl);
    419     }
    420 
    421     /**
    422      * @hide
    423      */
    424     public void drawBackground(Canvas canvas, Path highlight, Paint highlightPaint,
    425             int cursorOffsetVertical, int firstLine, int lastLine) {
    426         // First, draw LineBackgroundSpans.
    427         // LineBackgroundSpans know nothing about the alignment, margins, or
    428         // direction of the layout or line.  XXX: Should they?
    429         // They are evaluated at each line.
    430         if (mSpannedText) {
    431             if (mLineBackgroundSpans == null) {
    432                 mLineBackgroundSpans = new SpanSet<LineBackgroundSpan>(LineBackgroundSpan.class);
    433             }
    434 
    435             Spanned buffer = (Spanned) mText;
    436             int textLength = buffer.length();
    437             mLineBackgroundSpans.init(buffer, 0, textLength);
    438 
    439             if (mLineBackgroundSpans.numberOfSpans > 0) {
    440                 int previousLineBottom = getLineTop(firstLine);
    441                 int previousLineEnd = getLineStart(firstLine);
    442                 ParagraphStyle[] spans = NO_PARA_SPANS;
    443                 int spansLength = 0;
    444                 TextPaint paint = mPaint;
    445                 int spanEnd = 0;
    446                 final int width = mWidth;
    447                 for (int i = firstLine; i <= lastLine; i++) {
    448                     int start = previousLineEnd;
    449                     int end = getLineStart(i + 1);
    450                     previousLineEnd = end;
    451 
    452                     int ltop = previousLineBottom;
    453                     int lbottom = getLineTop(i + 1);
    454                     previousLineBottom = lbottom;
    455                     int lbaseline = lbottom - getLineDescent(i);
    456 
    457                     if (start >= spanEnd) {
    458                         // These should be infrequent, so we'll use this so that
    459                         // we don't have to check as often.
    460                         spanEnd = mLineBackgroundSpans.getNextTransition(start, textLength);
    461                         // All LineBackgroundSpans on a line contribute to its background.
    462                         spansLength = 0;
    463                         // Duplication of the logic of getParagraphSpans
    464                         if (start != end || start == 0) {
    465                             // Equivalent to a getSpans(start, end), but filling the 'spans' local
    466                             // array instead to reduce memory allocation
    467                             for (int j = 0; j < mLineBackgroundSpans.numberOfSpans; j++) {
    468                                 // equal test is valid since both intervals are not empty by
    469                                 // construction
    470                                 if (mLineBackgroundSpans.spanStarts[j] >= end ||
    471                                         mLineBackgroundSpans.spanEnds[j] <= start) continue;
    472                                 spans = GrowingArrayUtils.append(
    473                                         spans, spansLength, mLineBackgroundSpans.spans[j]);
    474                                 spansLength++;
    475                             }
    476                         }
    477                     }
    478 
    479                     for (int n = 0; n < spansLength; n++) {
    480                         LineBackgroundSpan lineBackgroundSpan = (LineBackgroundSpan) spans[n];
    481                         lineBackgroundSpan.drawBackground(canvas, paint, 0, width,
    482                                 ltop, lbaseline, lbottom,
    483                                 buffer, start, end, i);
    484                     }
    485                 }
    486             }
    487             mLineBackgroundSpans.recycle();
    488         }
    489 
    490         // There can be a highlight even without spans if we are drawing
    491         // a non-spanned transformation of a spanned editing buffer.
    492         if (highlight != null) {
    493             if (cursorOffsetVertical != 0) canvas.translate(0, cursorOffsetVertical);
    494             canvas.drawPath(highlight, highlightPaint);
    495             if (cursorOffsetVertical != 0) canvas.translate(0, -cursorOffsetVertical);
    496         }
    497     }
    498 
    499     /**
    500      * @param canvas
    501      * @return The range of lines that need to be drawn, possibly empty.
    502      * @hide
    503      */
    504     public long getLineRangeForDraw(Canvas canvas) {
    505         int dtop, dbottom;
    506 
    507         synchronized (sTempRect) {
    508             if (!canvas.getClipBounds(sTempRect)) {
    509                 // Negative range end used as a special flag
    510                 return TextUtils.packRangeInLong(0, -1);
    511             }
    512 
    513             dtop = sTempRect.top;
    514             dbottom = sTempRect.bottom;
    515         }
    516 
    517         final int top = Math.max(dtop, 0);
    518         final int bottom = Math.min(getLineTop(getLineCount()), dbottom);
    519 
    520         if (top >= bottom) return TextUtils.packRangeInLong(0, -1);
    521         return TextUtils.packRangeInLong(getLineForVertical(top), getLineForVertical(bottom));
    522     }
    523 
    524     /**
    525      * Return the start position of the line, given the left and right bounds
    526      * of the margins.
    527      *
    528      * @param line the line index
    529      * @param left the left bounds (0, or leading margin if ltr para)
    530      * @param right the right bounds (width, minus leading margin if rtl para)
    531      * @return the start position of the line (to right of line if rtl para)
    532      */
    533     private int getLineStartPos(int line, int left, int right) {
    534         // Adjust the point at which to start rendering depending on the
    535         // alignment of the paragraph.
    536         Alignment align = getParagraphAlignment(line);
    537         int dir = getParagraphDirection(line);
    538 
    539         if (align == Alignment.ALIGN_LEFT) {
    540             align = (dir == DIR_LEFT_TO_RIGHT) ? Alignment.ALIGN_NORMAL : Alignment.ALIGN_OPPOSITE;
    541         } else if (align == Alignment.ALIGN_RIGHT) {
    542             align = (dir == DIR_LEFT_TO_RIGHT) ? Alignment.ALIGN_OPPOSITE : Alignment.ALIGN_NORMAL;
    543         }
    544 
    545         int x;
    546         if (align == Alignment.ALIGN_NORMAL) {
    547             if (dir == DIR_LEFT_TO_RIGHT) {
    548                 x = left + getIndentAdjust(line, Alignment.ALIGN_LEFT);
    549             } else {
    550                 x = right + getIndentAdjust(line, Alignment.ALIGN_RIGHT);
    551             }
    552         } else {
    553             TabStops tabStops = null;
    554             if (mSpannedText && getLineContainsTab(line)) {
    555                 Spanned spanned = (Spanned) mText;
    556                 int start = getLineStart(line);
    557                 int spanEnd = spanned.nextSpanTransition(start, spanned.length(),
    558                         TabStopSpan.class);
    559                 TabStopSpan[] tabSpans = getParagraphSpans(spanned, start, spanEnd,
    560                         TabStopSpan.class);
    561                 if (tabSpans.length > 0) {
    562                     tabStops = new TabStops(TAB_INCREMENT, tabSpans);
    563                 }
    564             }
    565             int max = (int)getLineExtent(line, tabStops, false);
    566             if (align == Alignment.ALIGN_OPPOSITE) {
    567                 if (dir == DIR_LEFT_TO_RIGHT) {
    568                     x = right - max + getIndentAdjust(line, Alignment.ALIGN_RIGHT);
    569                 } else {
    570                     // max is negative here
    571                     x = left - max + getIndentAdjust(line, Alignment.ALIGN_LEFT);
    572                 }
    573             } else { // Alignment.ALIGN_CENTER
    574                 max = max & ~1;
    575                 x = (left + right - max) >> 1 + getIndentAdjust(line, Alignment.ALIGN_CENTER);
    576             }
    577         }
    578         return x;
    579     }
    580 
    581     /**
    582      * Return the text that is displayed by this Layout.
    583      */
    584     public final CharSequence getText() {
    585         return mText;
    586     }
    587 
    588     /**
    589      * Return the base Paint properties for this layout.
    590      * Do NOT change the paint, which may result in funny
    591      * drawing for this layout.
    592      */
    593     public final TextPaint getPaint() {
    594         return mPaint;
    595     }
    596 
    597     /**
    598      * Return the width of this layout.
    599      */
    600     public final int getWidth() {
    601         return mWidth;
    602     }
    603 
    604     /**
    605      * Return the width to which this Layout is ellipsizing, or
    606      * {@link #getWidth} if it is not doing anything special.
    607      */
    608     public int getEllipsizedWidth() {
    609         return mWidth;
    610     }
    611 
    612     /**
    613      * Increase the width of this layout to the specified width.
    614      * Be careful to use this only when you know it is appropriate&mdash;
    615      * it does not cause the text to reflow to use the full new width.
    616      */
    617     public final void increaseWidthTo(int wid) {
    618         if (wid < mWidth) {
    619             throw new RuntimeException("attempted to reduce Layout width");
    620         }
    621 
    622         mWidth = wid;
    623     }
    624 
    625     /**
    626      * Return the total height of this layout.
    627      */
    628     public int getHeight() {
    629         return getLineTop(getLineCount());
    630     }
    631 
    632     /**
    633      * Return the base alignment of this layout.
    634      */
    635     public final Alignment getAlignment() {
    636         return mAlignment;
    637     }
    638 
    639     /**
    640      * Return what the text height is multiplied by to get the line height.
    641      */
    642     public final float getSpacingMultiplier() {
    643         return mSpacingMult;
    644     }
    645 
    646     /**
    647      * Return the number of units of leading that are added to each line.
    648      */
    649     public final float getSpacingAdd() {
    650         return mSpacingAdd;
    651     }
    652 
    653     /**
    654      * Return the heuristic used to determine paragraph text direction.
    655      * @hide
    656      */
    657     public final TextDirectionHeuristic getTextDirectionHeuristic() {
    658         return mTextDir;
    659     }
    660 
    661     /**
    662      * Return the number of lines of text in this layout.
    663      */
    664     public abstract int getLineCount();
    665 
    666     /**
    667      * Return the baseline for the specified line (0&hellip;getLineCount() - 1)
    668      * If bounds is not null, return the top, left, right, bottom extents
    669      * of the specified line in it.
    670      * @param line which line to examine (0..getLineCount() - 1)
    671      * @param bounds Optional. If not null, it returns the extent of the line
    672      * @return the Y-coordinate of the baseline
    673      */
    674     public int getLineBounds(int line, Rect bounds) {
    675         if (bounds != null) {
    676             bounds.left = 0;     // ???
    677             bounds.top = getLineTop(line);
    678             bounds.right = mWidth;   // ???
    679             bounds.bottom = getLineTop(line + 1);
    680         }
    681         return getLineBaseline(line);
    682     }
    683 
    684     /**
    685      * Return the vertical position of the top of the specified line
    686      * (0&hellip;getLineCount()).
    687      * If the specified line is equal to the line count, returns the
    688      * bottom of the last line.
    689      */
    690     public abstract int getLineTop(int line);
    691 
    692     /**
    693      * Return the descent of the specified line(0&hellip;getLineCount() - 1).
    694      */
    695     public abstract int getLineDescent(int line);
    696 
    697     /**
    698      * Return the text offset of the beginning of the specified line (
    699      * 0&hellip;getLineCount()). If the specified line is equal to the line
    700      * count, returns the length of the text.
    701      */
    702     public abstract int getLineStart(int line);
    703 
    704     /**
    705      * Returns the primary directionality of the paragraph containing the
    706      * specified line, either 1 for left-to-right lines, or -1 for right-to-left
    707      * lines (see {@link #DIR_LEFT_TO_RIGHT}, {@link #DIR_RIGHT_TO_LEFT}).
    708      */
    709     public abstract int getParagraphDirection(int line);
    710 
    711     /**
    712      * Returns whether the specified line contains one or more
    713      * characters that need to be handled specially, like tabs
    714      * or emoji.
    715      */
    716     public abstract boolean getLineContainsTab(int line);
    717 
    718     /**
    719      * Returns the directional run information for the specified line.
    720      * The array alternates counts of characters in left-to-right
    721      * and right-to-left segments of the line.
    722      *
    723      * <p>NOTE: this is inadequate to support bidirectional text, and will change.
    724      */
    725     public abstract Directions getLineDirections(int line);
    726 
    727     /**
    728      * Returns the (negative) number of extra pixels of ascent padding in the
    729      * top line of the Layout.
    730      */
    731     public abstract int getTopPadding();
    732 
    733     /**
    734      * Returns the number of extra pixels of descent padding in the
    735      * bottom line of the Layout.
    736      */
    737     public abstract int getBottomPadding();
    738 
    739     /**
    740      * Returns the hyphen edit for a line.
    741      *
    742      * @hide
    743      */
    744     public int getHyphen(int line) {
    745         return 0;
    746     }
    747 
    748     /**
    749      * Returns the left indent for a line.
    750      *
    751      * @hide
    752      */
    753     public int getIndentAdjust(int line, Alignment alignment) {
    754         return 0;
    755     }
    756 
    757     /**
    758      * Returns true if the character at offset and the preceding character
    759      * are at different run levels (and thus there's a split caret).
    760      * @param offset the offset
    761      * @return true if at a level boundary
    762      * @hide
    763      */
    764     public boolean isLevelBoundary(int offset) {
    765         int line = getLineForOffset(offset);
    766         Directions dirs = getLineDirections(line);
    767         if (dirs == DIRS_ALL_LEFT_TO_RIGHT || dirs == DIRS_ALL_RIGHT_TO_LEFT) {
    768             return false;
    769         }
    770 
    771         int[] runs = dirs.mDirections;
    772         int lineStart = getLineStart(line);
    773         int lineEnd = getLineEnd(line);
    774         if (offset == lineStart || offset == lineEnd) {
    775             int paraLevel = getParagraphDirection(line) == 1 ? 0 : 1;
    776             int runIndex = offset == lineStart ? 0 : runs.length - 2;
    777             return ((runs[runIndex + 1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK) != paraLevel;
    778         }
    779 
    780         offset -= lineStart;
    781         for (int i = 0; i < runs.length; i += 2) {
    782             if (offset == runs[i]) {
    783                 return true;
    784             }
    785         }
    786         return false;
    787     }
    788 
    789     /**
    790      * Returns true if the character at offset is right to left (RTL).
    791      * @param offset the offset
    792      * @return true if the character is RTL, false if it is LTR
    793      */
    794     public boolean isRtlCharAt(int offset) {
    795         int line = getLineForOffset(offset);
    796         Directions dirs = getLineDirections(line);
    797         if (dirs == DIRS_ALL_LEFT_TO_RIGHT) {
    798             return false;
    799         }
    800         if (dirs == DIRS_ALL_RIGHT_TO_LEFT) {
    801             return  true;
    802         }
    803         int[] runs = dirs.mDirections;
    804         int lineStart = getLineStart(line);
    805         for (int i = 0; i < runs.length; i += 2) {
    806             int start = lineStart + runs[i];
    807             int limit = start + (runs[i+1] & RUN_LENGTH_MASK);
    808             if (offset >= start && offset < limit) {
    809                 int level = (runs[i+1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK;
    810                 return ((level & 1) != 0);
    811             }
    812         }
    813         // Should happen only if the offset is "out of bounds"
    814         return false;
    815     }
    816 
    817     private boolean primaryIsTrailingPrevious(int offset) {
    818         int line = getLineForOffset(offset);
    819         int lineStart = getLineStart(line);
    820         int lineEnd = getLineEnd(line);
    821         int[] runs = getLineDirections(line).mDirections;
    822 
    823         int levelAt = -1;
    824         for (int i = 0; i < runs.length; i += 2) {
    825             int start = lineStart + runs[i];
    826             int limit = start + (runs[i+1] & RUN_LENGTH_MASK);
    827             if (limit > lineEnd) {
    828                 limit = lineEnd;
    829             }
    830             if (offset >= start && offset < limit) {
    831                 if (offset > start) {
    832                     // Previous character is at same level, so don't use trailing.
    833                     return false;
    834                 }
    835                 levelAt = (runs[i+1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK;
    836                 break;
    837             }
    838         }
    839         if (levelAt == -1) {
    840             // Offset was limit of line.
    841             levelAt = getParagraphDirection(line) == 1 ? 0 : 1;
    842         }
    843 
    844         // At level boundary, check previous level.
    845         int levelBefore = -1;
    846         if (offset == lineStart) {
    847             levelBefore = getParagraphDirection(line) == 1 ? 0 : 1;
    848         } else {
    849             offset -= 1;
    850             for (int i = 0; i < runs.length; i += 2) {
    851                 int start = lineStart + runs[i];
    852                 int limit = start + (runs[i+1] & RUN_LENGTH_MASK);
    853                 if (limit > lineEnd) {
    854                     limit = lineEnd;
    855                 }
    856                 if (offset >= start && offset < limit) {
    857                     levelBefore = (runs[i+1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK;
    858                     break;
    859                 }
    860             }
    861         }
    862 
    863         return levelBefore < levelAt;
    864     }
    865 
    866     /**
    867      * Get the primary horizontal position for the specified text offset.
    868      * This is the location where a new character would be inserted in
    869      * the paragraph's primary direction.
    870      */
    871     public float getPrimaryHorizontal(int offset) {
    872         return getPrimaryHorizontal(offset, false /* not clamped */);
    873     }
    874 
    875     /**
    876      * Get the primary horizontal position for the specified text offset, but
    877      * optionally clamp it so that it doesn't exceed the width of the layout.
    878      * @hide
    879      */
    880     public float getPrimaryHorizontal(int offset, boolean clamped) {
    881         boolean trailing = primaryIsTrailingPrevious(offset);
    882         return getHorizontal(offset, trailing, clamped);
    883     }
    884 
    885     /**
    886      * Get the secondary horizontal position for the specified text offset.
    887      * This is the location where a new character would be inserted in
    888      * the direction other than the paragraph's primary direction.
    889      */
    890     public float getSecondaryHorizontal(int offset) {
    891         return getSecondaryHorizontal(offset, false /* not clamped */);
    892     }
    893 
    894     /**
    895      * Get the secondary horizontal position for the specified text offset, but
    896      * optionally clamp it so that it doesn't exceed the width of the layout.
    897      * @hide
    898      */
    899     public float getSecondaryHorizontal(int offset, boolean clamped) {
    900         boolean trailing = primaryIsTrailingPrevious(offset);
    901         return getHorizontal(offset, !trailing, clamped);
    902     }
    903 
    904     private float getHorizontal(int offset, boolean trailing, boolean clamped) {
    905         int line = getLineForOffset(offset);
    906 
    907         return getHorizontal(offset, trailing, line, clamped);
    908     }
    909 
    910     private float getHorizontal(int offset, boolean trailing, int line, boolean clamped) {
    911         int start = getLineStart(line);
    912         int end = getLineEnd(line);
    913         int dir = getParagraphDirection(line);
    914         boolean hasTabOrEmoji = getLineContainsTab(line);
    915         Directions directions = getLineDirections(line);
    916 
    917         TabStops tabStops = null;
    918         if (hasTabOrEmoji && mText instanceof Spanned) {
    919             // Just checking this line should be good enough, tabs should be
    920             // consistent across all lines in a paragraph.
    921             TabStopSpan[] tabs = getParagraphSpans((Spanned) mText, start, end, TabStopSpan.class);
    922             if (tabs.length > 0) {
    923                 tabStops = new TabStops(TAB_INCREMENT, tabs); // XXX should reuse
    924             }
    925         }
    926 
    927         TextLine tl = TextLine.obtain();
    928         tl.set(mPaint, mText, start, end, dir, directions, hasTabOrEmoji, tabStops);
    929         float wid = tl.measure(offset - start, trailing, null);
    930         TextLine.recycle(tl);
    931 
    932         if (clamped && wid > mWidth) {
    933             wid = mWidth;
    934         }
    935         int left = getParagraphLeft(line);
    936         int right = getParagraphRight(line);
    937 
    938         return getLineStartPos(line, left, right) + wid;
    939     }
    940 
    941     /**
    942      * Get the leftmost position that should be exposed for horizontal
    943      * scrolling on the specified line.
    944      */
    945     public float getLineLeft(int line) {
    946         int dir = getParagraphDirection(line);
    947         Alignment align = getParagraphAlignment(line);
    948 
    949         if (align == Alignment.ALIGN_LEFT) {
    950             return 0;
    951         } else if (align == Alignment.ALIGN_NORMAL) {
    952             if (dir == DIR_RIGHT_TO_LEFT)
    953                 return getParagraphRight(line) - getLineMax(line);
    954             else
    955                 return 0;
    956         } else if (align == Alignment.ALIGN_RIGHT) {
    957             return mWidth - getLineMax(line);
    958         } else if (align == Alignment.ALIGN_OPPOSITE) {
    959             if (dir == DIR_RIGHT_TO_LEFT)
    960                 return 0;
    961             else
    962                 return mWidth - getLineMax(line);
    963         } else { /* align == Alignment.ALIGN_CENTER */
    964             int left = getParagraphLeft(line);
    965             int right = getParagraphRight(line);
    966             int max = ((int) getLineMax(line)) & ~1;
    967 
    968             return left + ((right - left) - max) / 2;
    969         }
    970     }
    971 
    972     /**
    973      * Get the rightmost position that should be exposed for horizontal
    974      * scrolling on the specified line.
    975      */
    976     public float getLineRight(int line) {
    977         int dir = getParagraphDirection(line);
    978         Alignment align = getParagraphAlignment(line);
    979 
    980         if (align == Alignment.ALIGN_LEFT) {
    981             return getParagraphLeft(line) + getLineMax(line);
    982         } else if (align == Alignment.ALIGN_NORMAL) {
    983             if (dir == DIR_RIGHT_TO_LEFT)
    984                 return mWidth;
    985             else
    986                 return getParagraphLeft(line) + getLineMax(line);
    987         } else if (align == Alignment.ALIGN_RIGHT) {
    988             return mWidth;
    989         } else if (align == Alignment.ALIGN_OPPOSITE) {
    990             if (dir == DIR_RIGHT_TO_LEFT)
    991                 return getLineMax(line);
    992             else
    993                 return mWidth;
    994         } else { /* align == Alignment.ALIGN_CENTER */
    995             int left = getParagraphLeft(line);
    996             int right = getParagraphRight(line);
    997             int max = ((int) getLineMax(line)) & ~1;
    998 
    999             return right - ((right - left) - max) / 2;
   1000         }
   1001     }
   1002 
   1003     /**
   1004      * Gets the unsigned horizontal extent of the specified line, including
   1005      * leading margin indent, but excluding trailing whitespace.
   1006      */
   1007     public float getLineMax(int line) {
   1008         float margin = getParagraphLeadingMargin(line);
   1009         float signedExtent = getLineExtent(line, false);
   1010         return margin + (signedExtent >= 0 ? signedExtent : -signedExtent);
   1011     }
   1012 
   1013     /**
   1014      * Gets the unsigned horizontal extent of the specified line, including
   1015      * leading margin indent and trailing whitespace.
   1016      */
   1017     public float getLineWidth(int line) {
   1018         float margin = getParagraphLeadingMargin(line);
   1019         float signedExtent = getLineExtent(line, true);
   1020         return margin + (signedExtent >= 0 ? signedExtent : -signedExtent);
   1021     }
   1022 
   1023     /**
   1024      * Like {@link #getLineExtent(int,TabStops,boolean)} but determines the
   1025      * tab stops instead of using the ones passed in.
   1026      * @param line the index of the line
   1027      * @param full whether to include trailing whitespace
   1028      * @return the extent of the line
   1029      */
   1030     private float getLineExtent(int line, boolean full) {
   1031         int start = getLineStart(line);
   1032         int end = full ? getLineEnd(line) : getLineVisibleEnd(line);
   1033 
   1034         boolean hasTabsOrEmoji = getLineContainsTab(line);
   1035         TabStops tabStops = null;
   1036         if (hasTabsOrEmoji && mText instanceof Spanned) {
   1037             // Just checking this line should be good enough, tabs should be
   1038             // consistent across all lines in a paragraph.
   1039             TabStopSpan[] tabs = getParagraphSpans((Spanned) mText, start, end, TabStopSpan.class);
   1040             if (tabs.length > 0) {
   1041                 tabStops = new TabStops(TAB_INCREMENT, tabs); // XXX should reuse
   1042             }
   1043         }
   1044         Directions directions = getLineDirections(line);
   1045         // Returned directions can actually be null
   1046         if (directions == null) {
   1047             return 0f;
   1048         }
   1049         int dir = getParagraphDirection(line);
   1050 
   1051         TextLine tl = TextLine.obtain();
   1052         tl.set(mPaint, mText, start, end, dir, directions, hasTabsOrEmoji, tabStops);
   1053         float width = tl.metrics(null);
   1054         TextLine.recycle(tl);
   1055         return width;
   1056     }
   1057 
   1058     /**
   1059      * Returns the signed horizontal extent of the specified line, excluding
   1060      * leading margin.  If full is false, excludes trailing whitespace.
   1061      * @param line the index of the line
   1062      * @param tabStops the tab stops, can be null if we know they're not used.
   1063      * @param full whether to include trailing whitespace
   1064      * @return the extent of the text on this line
   1065      */
   1066     private float getLineExtent(int line, TabStops tabStops, boolean full) {
   1067         int start = getLineStart(line);
   1068         int end = full ? getLineEnd(line) : getLineVisibleEnd(line);
   1069         boolean hasTabsOrEmoji = getLineContainsTab(line);
   1070         Directions directions = getLineDirections(line);
   1071         int dir = getParagraphDirection(line);
   1072 
   1073         TextLine tl = TextLine.obtain();
   1074         tl.set(mPaint, mText, start, end, dir, directions, hasTabsOrEmoji, tabStops);
   1075         float width = tl.metrics(null);
   1076         TextLine.recycle(tl);
   1077         return width;
   1078     }
   1079 
   1080     /**
   1081      * Get the line number corresponding to the specified vertical position.
   1082      * If you ask for a position above 0, you get 0; if you ask for a position
   1083      * below the bottom of the text, you get the last line.
   1084      */
   1085     // FIXME: It may be faster to do a linear search for layouts without many lines.
   1086     public int getLineForVertical(int vertical) {
   1087         int high = getLineCount(), low = -1, guess;
   1088 
   1089         while (high - low > 1) {
   1090             guess = (high + low) / 2;
   1091 
   1092             if (getLineTop(guess) > vertical)
   1093                 high = guess;
   1094             else
   1095                 low = guess;
   1096         }
   1097 
   1098         if (low < 0)
   1099             return 0;
   1100         else
   1101             return low;
   1102     }
   1103 
   1104     /**
   1105      * Get the line number on which the specified text offset appears.
   1106      * If you ask for a position before 0, you get 0; if you ask for a position
   1107      * beyond the end of the text, you get the last line.
   1108      */
   1109     public int getLineForOffset(int offset) {
   1110         int high = getLineCount(), low = -1, guess;
   1111 
   1112         while (high - low > 1) {
   1113             guess = (high + low) / 2;
   1114 
   1115             if (getLineStart(guess) > offset)
   1116                 high = guess;
   1117             else
   1118                 low = guess;
   1119         }
   1120 
   1121         if (low < 0)
   1122             return 0;
   1123         else
   1124             return low;
   1125     }
   1126 
   1127     /**
   1128      * Get the character offset on the specified line whose position is
   1129      * closest to the specified horizontal position.
   1130      */
   1131     public int getOffsetForHorizontal(int line, float horiz) {
   1132         // TODO: use Paint.getOffsetForAdvance to avoid binary search
   1133         int max = getLineEnd(line) - 1;
   1134         int min = getLineStart(line);
   1135         Directions dirs = getLineDirections(line);
   1136 
   1137         if (line == getLineCount() - 1)
   1138             max++;
   1139 
   1140         int best = min;
   1141         float bestdist = Math.abs(getPrimaryHorizontal(best) - horiz);
   1142 
   1143         for (int i = 0; i < dirs.mDirections.length; i += 2) {
   1144             int here = min + dirs.mDirections[i];
   1145             int there = here + (dirs.mDirections[i+1] & RUN_LENGTH_MASK);
   1146             int swap = (dirs.mDirections[i+1] & RUN_RTL_FLAG) != 0 ? -1 : 1;
   1147 
   1148             if (there > max)
   1149                 there = max;
   1150             int high = there - 1 + 1, low = here + 1 - 1, guess;
   1151 
   1152             while (high - low > 1) {
   1153                 guess = (high + low) / 2;
   1154                 int adguess = getOffsetAtStartOf(guess);
   1155 
   1156                 if (getPrimaryHorizontal(adguess) * swap >= horiz * swap)
   1157                     high = guess;
   1158                 else
   1159                     low = guess;
   1160             }
   1161 
   1162             if (low < here + 1)
   1163                 low = here + 1;
   1164 
   1165             if (low < there) {
   1166                 low = getOffsetAtStartOf(low);
   1167 
   1168                 float dist = Math.abs(getPrimaryHorizontal(low) - horiz);
   1169 
   1170                 int aft = TextUtils.getOffsetAfter(mText, low);
   1171                 if (aft < there) {
   1172                     float other = Math.abs(getPrimaryHorizontal(aft) - horiz);
   1173 
   1174                     if (other < dist) {
   1175                         dist = other;
   1176                         low = aft;
   1177                     }
   1178                 }
   1179 
   1180                 if (dist < bestdist) {
   1181                     bestdist = dist;
   1182                     best = low;
   1183                 }
   1184             }
   1185 
   1186             float dist = Math.abs(getPrimaryHorizontal(here) - horiz);
   1187 
   1188             if (dist < bestdist) {
   1189                 bestdist = dist;
   1190                 best = here;
   1191             }
   1192         }
   1193 
   1194         float dist = Math.abs(getPrimaryHorizontal(max) - horiz);
   1195 
   1196         if (dist <= bestdist) {
   1197             bestdist = dist;
   1198             best = max;
   1199         }
   1200 
   1201         return best;
   1202     }
   1203 
   1204     /**
   1205      * Return the text offset after the last character on the specified line.
   1206      */
   1207     public final int getLineEnd(int line) {
   1208         return getLineStart(line + 1);
   1209     }
   1210 
   1211     /**
   1212      * Return the text offset after the last visible character (so whitespace
   1213      * is not counted) on the specified line.
   1214      */
   1215     public int getLineVisibleEnd(int line) {
   1216         return getLineVisibleEnd(line, getLineStart(line), getLineStart(line+1));
   1217     }
   1218 
   1219     private int getLineVisibleEnd(int line, int start, int end) {
   1220         CharSequence text = mText;
   1221         char ch;
   1222         if (line == getLineCount() - 1) {
   1223             return end;
   1224         }
   1225 
   1226         for (; end > start; end--) {
   1227             ch = text.charAt(end - 1);
   1228 
   1229             if (ch == '\n') {
   1230                 return end - 1;
   1231             }
   1232 
   1233             // Note: keep this in sync with Minikin LineBreaker::isLineEndSpace()
   1234             if (!(ch == ' ' || ch == '\t' || ch == 0x1680 ||
   1235                     (0x2000 <= ch && ch <= 0x200A && ch != 0x2007) ||
   1236                     ch == 0x205F || ch == 0x3000)) {
   1237                 break;
   1238             }
   1239 
   1240         }
   1241 
   1242         return end;
   1243     }
   1244 
   1245     /**
   1246      * Return the vertical position of the bottom of the specified line.
   1247      */
   1248     public final int getLineBottom(int line) {
   1249         return getLineTop(line + 1);
   1250     }
   1251 
   1252     /**
   1253      * Return the vertical position of the baseline of the specified line.
   1254      */
   1255     public final int getLineBaseline(int line) {
   1256         // getLineTop(line+1) == getLineTop(line)
   1257         return getLineTop(line+1) - getLineDescent(line);
   1258     }
   1259 
   1260     /**
   1261      * Get the ascent of the text on the specified line.
   1262      * The return value is negative to match the Paint.ascent() convention.
   1263      */
   1264     public final int getLineAscent(int line) {
   1265         // getLineTop(line+1) - getLineDescent(line) == getLineBaseLine(line)
   1266         return getLineTop(line) - (getLineTop(line+1) - getLineDescent(line));
   1267     }
   1268 
   1269     public int getOffsetToLeftOf(int offset) {
   1270         return getOffsetToLeftRightOf(offset, true);
   1271     }
   1272 
   1273     public int getOffsetToRightOf(int offset) {
   1274         return getOffsetToLeftRightOf(offset, false);
   1275     }
   1276 
   1277     private int getOffsetToLeftRightOf(int caret, boolean toLeft) {
   1278         int line = getLineForOffset(caret);
   1279         int lineStart = getLineStart(line);
   1280         int lineEnd = getLineEnd(line);
   1281         int lineDir = getParagraphDirection(line);
   1282 
   1283         boolean lineChanged = false;
   1284         boolean advance = toLeft == (lineDir == DIR_RIGHT_TO_LEFT);
   1285         // if walking off line, look at the line we're headed to
   1286         if (advance) {
   1287             if (caret == lineEnd) {
   1288                 if (line < getLineCount() - 1) {
   1289                     lineChanged = true;
   1290                     ++line;
   1291                 } else {
   1292                     return caret; // at very end, don't move
   1293                 }
   1294             }
   1295         } else {
   1296             if (caret == lineStart) {
   1297                 if (line > 0) {
   1298                     lineChanged = true;
   1299                     --line;
   1300                 } else {
   1301                     return caret; // at very start, don't move
   1302                 }
   1303             }
   1304         }
   1305 
   1306         if (lineChanged) {
   1307             lineStart = getLineStart(line);
   1308             lineEnd = getLineEnd(line);
   1309             int newDir = getParagraphDirection(line);
   1310             if (newDir != lineDir) {
   1311                 // unusual case.  we want to walk onto the line, but it runs
   1312                 // in a different direction than this one, so we fake movement
   1313                 // in the opposite direction.
   1314                 toLeft = !toLeft;
   1315                 lineDir = newDir;
   1316             }
   1317         }
   1318 
   1319         Directions directions = getLineDirections(line);
   1320 
   1321         TextLine tl = TextLine.obtain();
   1322         // XXX: we don't care about tabs
   1323         tl.set(mPaint, mText, lineStart, lineEnd, lineDir, directions, false, null);
   1324         caret = lineStart + tl.getOffsetToLeftRightOf(caret - lineStart, toLeft);
   1325         tl = TextLine.recycle(tl);
   1326         return caret;
   1327     }
   1328 
   1329     private int getOffsetAtStartOf(int offset) {
   1330         // XXX this probably should skip local reorderings and
   1331         // zero-width characters, look at callers
   1332         if (offset == 0)
   1333             return 0;
   1334 
   1335         CharSequence text = mText;
   1336         char c = text.charAt(offset);
   1337 
   1338         if (c >= '\uDC00' && c <= '\uDFFF') {
   1339             char c1 = text.charAt(offset - 1);
   1340 
   1341             if (c1 >= '\uD800' && c1 <= '\uDBFF')
   1342                 offset -= 1;
   1343         }
   1344 
   1345         if (mSpannedText) {
   1346             ReplacementSpan[] spans = ((Spanned) text).getSpans(offset, offset,
   1347                                                        ReplacementSpan.class);
   1348 
   1349             for (int i = 0; i < spans.length; i++) {
   1350                 int start = ((Spanned) text).getSpanStart(spans[i]);
   1351                 int end = ((Spanned) text).getSpanEnd(spans[i]);
   1352 
   1353                 if (start < offset && end > offset)
   1354                     offset = start;
   1355             }
   1356         }
   1357 
   1358         return offset;
   1359     }
   1360 
   1361     /**
   1362      * Determine whether we should clamp cursor position. Currently it's
   1363      * only robust for left-aligned displays.
   1364      * @hide
   1365      */
   1366     public boolean shouldClampCursor(int line) {
   1367         // Only clamp cursor position in left-aligned displays.
   1368         switch (getParagraphAlignment(line)) {
   1369             case ALIGN_LEFT:
   1370                 return true;
   1371             case ALIGN_NORMAL:
   1372                 return getParagraphDirection(line) > 0;
   1373             default:
   1374                 return false;
   1375         }
   1376 
   1377     }
   1378     /**
   1379      * Fills in the specified Path with a representation of a cursor
   1380      * at the specified offset.  This will often be a vertical line
   1381      * but can be multiple discontinuous lines in text with multiple
   1382      * directionalities.
   1383      */
   1384     public void getCursorPath(int point, Path dest,
   1385                               CharSequence editingBuffer) {
   1386         dest.reset();
   1387 
   1388         int line = getLineForOffset(point);
   1389         int top = getLineTop(line);
   1390         int bottom = getLineTop(line+1);
   1391 
   1392         boolean clamped = shouldClampCursor(line);
   1393         float h1 = getPrimaryHorizontal(point, clamped) - 0.5f;
   1394         float h2 = isLevelBoundary(point) ? getSecondaryHorizontal(point, clamped) - 0.5f : h1;
   1395 
   1396         int caps = TextKeyListener.getMetaState(editingBuffer, TextKeyListener.META_SHIFT_ON) |
   1397                    TextKeyListener.getMetaState(editingBuffer, TextKeyListener.META_SELECTING);
   1398         int fn = TextKeyListener.getMetaState(editingBuffer, TextKeyListener.META_ALT_ON);
   1399         int dist = 0;
   1400 
   1401         if (caps != 0 || fn != 0) {
   1402             dist = (bottom - top) >> 2;
   1403 
   1404             if (fn != 0)
   1405                 top += dist;
   1406             if (caps != 0)
   1407                 bottom -= dist;
   1408         }
   1409 
   1410         if (h1 < 0.5f)
   1411             h1 = 0.5f;
   1412         if (h2 < 0.5f)
   1413             h2 = 0.5f;
   1414 
   1415         if (Float.compare(h1, h2) == 0) {
   1416             dest.moveTo(h1, top);
   1417             dest.lineTo(h1, bottom);
   1418         } else {
   1419             dest.moveTo(h1, top);
   1420             dest.lineTo(h1, (top + bottom) >> 1);
   1421 
   1422             dest.moveTo(h2, (top + bottom) >> 1);
   1423             dest.lineTo(h2, bottom);
   1424         }
   1425 
   1426         if (caps == 2) {
   1427             dest.moveTo(h2, bottom);
   1428             dest.lineTo(h2 - dist, bottom + dist);
   1429             dest.lineTo(h2, bottom);
   1430             dest.lineTo(h2 + dist, bottom + dist);
   1431         } else if (caps == 1) {
   1432             dest.moveTo(h2, bottom);
   1433             dest.lineTo(h2 - dist, bottom + dist);
   1434 
   1435             dest.moveTo(h2 - dist, bottom + dist - 0.5f);
   1436             dest.lineTo(h2 + dist, bottom + dist - 0.5f);
   1437 
   1438             dest.moveTo(h2 + dist, bottom + dist);
   1439             dest.lineTo(h2, bottom);
   1440         }
   1441 
   1442         if (fn == 2) {
   1443             dest.moveTo(h1, top);
   1444             dest.lineTo(h1 - dist, top - dist);
   1445             dest.lineTo(h1, top);
   1446             dest.lineTo(h1 + dist, top - dist);
   1447         } else if (fn == 1) {
   1448             dest.moveTo(h1, top);
   1449             dest.lineTo(h1 - dist, top - dist);
   1450 
   1451             dest.moveTo(h1 - dist, top - dist + 0.5f);
   1452             dest.lineTo(h1 + dist, top - dist + 0.5f);
   1453 
   1454             dest.moveTo(h1 + dist, top - dist);
   1455             dest.lineTo(h1, top);
   1456         }
   1457     }
   1458 
   1459     private void addSelection(int line, int start, int end,
   1460                               int top, int bottom, Path dest) {
   1461         int linestart = getLineStart(line);
   1462         int lineend = getLineEnd(line);
   1463         Directions dirs = getLineDirections(line);
   1464 
   1465         if (lineend > linestart && mText.charAt(lineend - 1) == '\n')
   1466             lineend--;
   1467 
   1468         for (int i = 0; i < dirs.mDirections.length; i += 2) {
   1469             int here = linestart + dirs.mDirections[i];
   1470             int there = here + (dirs.mDirections[i+1] & RUN_LENGTH_MASK);
   1471 
   1472             if (there > lineend)
   1473                 there = lineend;
   1474 
   1475             if (start <= there && end >= here) {
   1476                 int st = Math.max(start, here);
   1477                 int en = Math.min(end, there);
   1478 
   1479                 if (st != en) {
   1480                     float h1 = getHorizontal(st, false, line, false /* not clamped */);
   1481                     float h2 = getHorizontal(en, true, line, false /* not clamped */);
   1482 
   1483                     float left = Math.min(h1, h2);
   1484                     float right = Math.max(h1, h2);
   1485 
   1486                     dest.addRect(left, top, right, bottom, Path.Direction.CW);
   1487                 }
   1488             }
   1489         }
   1490     }
   1491 
   1492     /**
   1493      * Fills in the specified Path with a representation of a highlight
   1494      * between the specified offsets.  This will often be a rectangle
   1495      * or a potentially discontinuous set of rectangles.  If the start
   1496      * and end are the same, the returned path is empty.
   1497      */
   1498     public void getSelectionPath(int start, int end, Path dest) {
   1499         dest.reset();
   1500 
   1501         if (start == end)
   1502             return;
   1503 
   1504         if (end < start) {
   1505             int temp = end;
   1506             end = start;
   1507             start = temp;
   1508         }
   1509 
   1510         int startline = getLineForOffset(start);
   1511         int endline = getLineForOffset(end);
   1512 
   1513         int top = getLineTop(startline);
   1514         int bottom = getLineBottom(endline);
   1515 
   1516         if (startline == endline) {
   1517             addSelection(startline, start, end, top, bottom, dest);
   1518         } else {
   1519             final float width = mWidth;
   1520 
   1521             addSelection(startline, start, getLineEnd(startline),
   1522                          top, getLineBottom(startline), dest);
   1523 
   1524             if (getParagraphDirection(startline) == DIR_RIGHT_TO_LEFT)
   1525                 dest.addRect(getLineLeft(startline), top,
   1526                               0, getLineBottom(startline), Path.Direction.CW);
   1527             else
   1528                 dest.addRect(getLineRight(startline), top,
   1529                               width, getLineBottom(startline), Path.Direction.CW);
   1530 
   1531             for (int i = startline + 1; i < endline; i++) {
   1532                 top = getLineTop(i);
   1533                 bottom = getLineBottom(i);
   1534                 dest.addRect(0, top, width, bottom, Path.Direction.CW);
   1535             }
   1536 
   1537             top = getLineTop(endline);
   1538             bottom = getLineBottom(endline);
   1539 
   1540             addSelection(endline, getLineStart(endline), end,
   1541                          top, bottom, dest);
   1542 
   1543             if (getParagraphDirection(endline) == DIR_RIGHT_TO_LEFT)
   1544                 dest.addRect(width, top, getLineRight(endline), bottom, Path.Direction.CW);
   1545             else
   1546                 dest.addRect(0, top, getLineLeft(endline), bottom, Path.Direction.CW);
   1547         }
   1548     }
   1549 
   1550     /**
   1551      * Get the alignment of the specified paragraph, taking into account
   1552      * markup attached to it.
   1553      */
   1554     public final Alignment getParagraphAlignment(int line) {
   1555         Alignment align = mAlignment;
   1556 
   1557         if (mSpannedText) {
   1558             Spanned sp = (Spanned) mText;
   1559             AlignmentSpan[] spans = getParagraphSpans(sp, getLineStart(line),
   1560                                                 getLineEnd(line),
   1561                                                 AlignmentSpan.class);
   1562 
   1563             int spanLength = spans.length;
   1564             if (spanLength > 0) {
   1565                 align = spans[spanLength-1].getAlignment();
   1566             }
   1567         }
   1568 
   1569         return align;
   1570     }
   1571 
   1572     /**
   1573      * Get the left edge of the specified paragraph, inset by left margins.
   1574      */
   1575     public final int getParagraphLeft(int line) {
   1576         int left = 0;
   1577         int dir = getParagraphDirection(line);
   1578         if (dir == DIR_RIGHT_TO_LEFT || !mSpannedText) {
   1579             return left; // leading margin has no impact, or no styles
   1580         }
   1581         return getParagraphLeadingMargin(line);
   1582     }
   1583 
   1584     /**
   1585      * Get the right edge of the specified paragraph, inset by right margins.
   1586      */
   1587     public final int getParagraphRight(int line) {
   1588         int right = mWidth;
   1589         int dir = getParagraphDirection(line);
   1590         if (dir == DIR_LEFT_TO_RIGHT || !mSpannedText) {
   1591             return right; // leading margin has no impact, or no styles
   1592         }
   1593         return right - getParagraphLeadingMargin(line);
   1594     }
   1595 
   1596     /**
   1597      * Returns the effective leading margin (unsigned) for this line,
   1598      * taking into account LeadingMarginSpan and LeadingMarginSpan2.
   1599      * @param line the line index
   1600      * @return the leading margin of this line
   1601      */
   1602     private int getParagraphLeadingMargin(int line) {
   1603         if (!mSpannedText) {
   1604             return 0;
   1605         }
   1606         Spanned spanned = (Spanned) mText;
   1607 
   1608         int lineStart = getLineStart(line);
   1609         int lineEnd = getLineEnd(line);
   1610         int spanEnd = spanned.nextSpanTransition(lineStart, lineEnd,
   1611                 LeadingMarginSpan.class);
   1612         LeadingMarginSpan[] spans = getParagraphSpans(spanned, lineStart, spanEnd,
   1613                                                 LeadingMarginSpan.class);
   1614         if (spans.length == 0) {
   1615             return 0; // no leading margin span;
   1616         }
   1617 
   1618         int margin = 0;
   1619 
   1620         boolean isFirstParaLine = lineStart == 0 ||
   1621             spanned.charAt(lineStart - 1) == '\n';
   1622 
   1623         boolean useFirstLineMargin = isFirstParaLine;
   1624         for (int i = 0; i < spans.length; i++) {
   1625             if (spans[i] instanceof LeadingMarginSpan2) {
   1626                 int spStart = spanned.getSpanStart(spans[i]);
   1627                 int spanLine = getLineForOffset(spStart);
   1628                 int count = ((LeadingMarginSpan2) spans[i]).getLeadingMarginLineCount();
   1629                 // if there is more than one LeadingMarginSpan2, use the count that is greatest
   1630                 useFirstLineMargin |= line < spanLine + count;
   1631             }
   1632         }
   1633         for (int i = 0; i < spans.length; i++) {
   1634             LeadingMarginSpan span = spans[i];
   1635             margin += span.getLeadingMargin(useFirstLineMargin);
   1636         }
   1637 
   1638         return margin;
   1639     }
   1640 
   1641     /* package */
   1642     static float measurePara(TextPaint paint, CharSequence text, int start, int end) {
   1643 
   1644         MeasuredText mt = MeasuredText.obtain();
   1645         TextLine tl = TextLine.obtain();
   1646         try {
   1647             mt.setPara(text, start, end, TextDirectionHeuristics.LTR, null);
   1648             Directions directions;
   1649             int dir;
   1650             if (mt.mEasy) {
   1651                 directions = DIRS_ALL_LEFT_TO_RIGHT;
   1652                 dir = Layout.DIR_LEFT_TO_RIGHT;
   1653             } else {
   1654                 directions = AndroidBidi.directions(mt.mDir, mt.mLevels,
   1655                     0, mt.mChars, 0, mt.mLen);
   1656                 dir = mt.mDir;
   1657             }
   1658             char[] chars = mt.mChars;
   1659             int len = mt.mLen;
   1660             boolean hasTabs = false;
   1661             TabStops tabStops = null;
   1662             // leading margins should be taken into account when measuring a paragraph
   1663             int margin = 0;
   1664             if (text instanceof Spanned) {
   1665                 Spanned spanned = (Spanned) text;
   1666                 LeadingMarginSpan[] spans = getParagraphSpans(spanned, start, end,
   1667                         LeadingMarginSpan.class);
   1668                 for (LeadingMarginSpan lms : spans) {
   1669                     margin += lms.getLeadingMargin(true);
   1670                 }
   1671             }
   1672             for (int i = 0; i < len; ++i) {
   1673                 if (chars[i] == '\t') {
   1674                     hasTabs = true;
   1675                     if (text instanceof Spanned) {
   1676                         Spanned spanned = (Spanned) text;
   1677                         int spanEnd = spanned.nextSpanTransition(start, end,
   1678                                 TabStopSpan.class);
   1679                         TabStopSpan[] spans = getParagraphSpans(spanned, start, spanEnd,
   1680                                 TabStopSpan.class);
   1681                         if (spans.length > 0) {
   1682                             tabStops = new TabStops(TAB_INCREMENT, spans);
   1683                         }
   1684                     }
   1685                     break;
   1686                 }
   1687             }
   1688             tl.set(paint, text, start, end, dir, directions, hasTabs, tabStops);
   1689             return margin + tl.metrics(null);
   1690         } finally {
   1691             TextLine.recycle(tl);
   1692             MeasuredText.recycle(mt);
   1693         }
   1694     }
   1695 
   1696     /**
   1697      * @hide
   1698      */
   1699     /* package */ static class TabStops {
   1700         private int[] mStops;
   1701         private int mNumStops;
   1702         private int mIncrement;
   1703 
   1704         TabStops(int increment, Object[] spans) {
   1705             reset(increment, spans);
   1706         }
   1707 
   1708         void reset(int increment, Object[] spans) {
   1709             this.mIncrement = increment;
   1710 
   1711             int ns = 0;
   1712             if (spans != null) {
   1713                 int[] stops = this.mStops;
   1714                 for (Object o : spans) {
   1715                     if (o instanceof TabStopSpan) {
   1716                         if (stops == null) {
   1717                             stops = new int[10];
   1718                         } else if (ns == stops.length) {
   1719                             int[] nstops = new int[ns * 2];
   1720                             for (int i = 0; i < ns; ++i) {
   1721                                 nstops[i] = stops[i];
   1722                             }
   1723                             stops = nstops;
   1724                         }
   1725                         stops[ns++] = ((TabStopSpan) o).getTabStop();
   1726                     }
   1727                 }
   1728                 if (ns > 1) {
   1729                     Arrays.sort(stops, 0, ns);
   1730                 }
   1731                 if (stops != this.mStops) {
   1732                     this.mStops = stops;
   1733                 }
   1734             }
   1735             this.mNumStops = ns;
   1736         }
   1737 
   1738         float nextTab(float h) {
   1739             int ns = this.mNumStops;
   1740             if (ns > 0) {
   1741                 int[] stops = this.mStops;
   1742                 for (int i = 0; i < ns; ++i) {
   1743                     int stop = stops[i];
   1744                     if (stop > h) {
   1745                         return stop;
   1746                     }
   1747                 }
   1748             }
   1749             return nextDefaultStop(h, mIncrement);
   1750         }
   1751 
   1752         public static float nextDefaultStop(float h, int inc) {
   1753             return ((int) ((h + inc) / inc)) * inc;
   1754         }
   1755     }
   1756 
   1757     /**
   1758      * Returns the position of the next tab stop after h on the line.
   1759      *
   1760      * @param text the text
   1761      * @param start start of the line
   1762      * @param end limit of the line
   1763      * @param h the current horizontal offset
   1764      * @param tabs the tabs, can be null.  If it is null, any tabs in effect
   1765      * on the line will be used.  If there are no tabs, a default offset
   1766      * will be used to compute the tab stop.
   1767      * @return the offset of the next tab stop.
   1768      */
   1769     /* package */ static float nextTab(CharSequence text, int start, int end,
   1770                                        float h, Object[] tabs) {
   1771         float nh = Float.MAX_VALUE;
   1772         boolean alltabs = false;
   1773 
   1774         if (text instanceof Spanned) {
   1775             if (tabs == null) {
   1776                 tabs = getParagraphSpans((Spanned) text, start, end, TabStopSpan.class);
   1777                 alltabs = true;
   1778             }
   1779 
   1780             for (int i = 0; i < tabs.length; i++) {
   1781                 if (!alltabs) {
   1782                     if (!(tabs[i] instanceof TabStopSpan))
   1783                         continue;
   1784                 }
   1785 
   1786                 int where = ((TabStopSpan) tabs[i]).getTabStop();
   1787 
   1788                 if (where < nh && where > h)
   1789                     nh = where;
   1790             }
   1791 
   1792             if (nh != Float.MAX_VALUE)
   1793                 return nh;
   1794         }
   1795 
   1796         return ((int) ((h + TAB_INCREMENT) / TAB_INCREMENT)) * TAB_INCREMENT;
   1797     }
   1798 
   1799     protected final boolean isSpanned() {
   1800         return mSpannedText;
   1801     }
   1802 
   1803     /**
   1804      * Returns the same as <code>text.getSpans()</code>, except where
   1805      * <code>start</code> and <code>end</code> are the same and are not
   1806      * at the very beginning of the text, in which case an empty array
   1807      * is returned instead.
   1808      * <p>
   1809      * This is needed because of the special case that <code>getSpans()</code>
   1810      * on an empty range returns the spans adjacent to that range, which is
   1811      * primarily for the sake of <code>TextWatchers</code> so they will get
   1812      * notifications when text goes from empty to non-empty.  But it also
   1813      * has the unfortunate side effect that if the text ends with an empty
   1814      * paragraph, that paragraph accidentally picks up the styles of the
   1815      * preceding paragraph (even though those styles will not be picked up
   1816      * by new text that is inserted into the empty paragraph).
   1817      * <p>
   1818      * The reason it just checks whether <code>start</code> and <code>end</code>
   1819      * is the same is that the only time a line can contain 0 characters
   1820      * is if it is the final paragraph of the Layout; otherwise any line will
   1821      * contain at least one printing or newline character.  The reason for the
   1822      * additional check if <code>start</code> is greater than 0 is that
   1823      * if the empty paragraph is the entire content of the buffer, paragraph
   1824      * styles that are already applied to the buffer will apply to text that
   1825      * is inserted into it.
   1826      */
   1827     /* package */static <T> T[] getParagraphSpans(Spanned text, int start, int end, Class<T> type) {
   1828         if (start == end && start > 0) {
   1829             return ArrayUtils.emptyArray(type);
   1830         }
   1831 
   1832         return text.getSpans(start, end, type);
   1833     }
   1834 
   1835     private char getEllipsisChar(TextUtils.TruncateAt method) {
   1836         return (method == TextUtils.TruncateAt.END_SMALL) ?
   1837                 TextUtils.ELLIPSIS_TWO_DOTS[0] :
   1838                 TextUtils.ELLIPSIS_NORMAL[0];
   1839     }
   1840 
   1841     private void ellipsize(int start, int end, int line,
   1842                            char[] dest, int destoff, TextUtils.TruncateAt method) {
   1843         int ellipsisCount = getEllipsisCount(line);
   1844 
   1845         if (ellipsisCount == 0) {
   1846             return;
   1847         }
   1848 
   1849         int ellipsisStart = getEllipsisStart(line);
   1850         int linestart = getLineStart(line);
   1851 
   1852         for (int i = ellipsisStart; i < ellipsisStart + ellipsisCount; i++) {
   1853             char c;
   1854 
   1855             if (i == ellipsisStart) {
   1856                 c = getEllipsisChar(method); // ellipsis
   1857             } else {
   1858                 c = '\uFEFF'; // 0-width space
   1859             }
   1860 
   1861             int a = i + linestart;
   1862 
   1863             if (a >= start && a < end) {
   1864                 dest[destoff + a - start] = c;
   1865             }
   1866         }
   1867     }
   1868 
   1869     /**
   1870      * Stores information about bidirectional (left-to-right or right-to-left)
   1871      * text within the layout of a line.
   1872      */
   1873     public static class Directions {
   1874         // Directions represents directional runs within a line of text.
   1875         // Runs are pairs of ints listed in visual order, starting from the
   1876         // leading margin.  The first int of each pair is the offset from
   1877         // the first character of the line to the start of the run.  The
   1878         // second int represents both the length and level of the run.
   1879         // The length is in the lower bits, accessed by masking with
   1880         // DIR_LENGTH_MASK.  The level is in the higher bits, accessed
   1881         // by shifting by DIR_LEVEL_SHIFT and masking by DIR_LEVEL_MASK.
   1882         // To simply test for an RTL direction, test the bit using
   1883         // DIR_RTL_FLAG, if set then the direction is rtl.
   1884 
   1885         /* package */ int[] mDirections;
   1886         /* package */ Directions(int[] dirs) {
   1887             mDirections = dirs;
   1888         }
   1889     }
   1890 
   1891     /**
   1892      * Return the offset of the first character to be ellipsized away,
   1893      * relative to the start of the line.  (So 0 if the beginning of the
   1894      * line is ellipsized, not getLineStart().)
   1895      */
   1896     public abstract int getEllipsisStart(int line);
   1897 
   1898     /**
   1899      * Returns the number of characters to be ellipsized away, or 0 if
   1900      * no ellipsis is to take place.
   1901      */
   1902     public abstract int getEllipsisCount(int line);
   1903 
   1904     /* package */ static class Ellipsizer implements CharSequence, GetChars {
   1905         /* package */ CharSequence mText;
   1906         /* package */ Layout mLayout;
   1907         /* package */ int mWidth;
   1908         /* package */ TextUtils.TruncateAt mMethod;
   1909 
   1910         public Ellipsizer(CharSequence s) {
   1911             mText = s;
   1912         }
   1913 
   1914         public char charAt(int off) {
   1915             char[] buf = TextUtils.obtain(1);
   1916             getChars(off, off + 1, buf, 0);
   1917             char ret = buf[0];
   1918 
   1919             TextUtils.recycle(buf);
   1920             return ret;
   1921         }
   1922 
   1923         public void getChars(int start, int end, char[] dest, int destoff) {
   1924             int line1 = mLayout.getLineForOffset(start);
   1925             int line2 = mLayout.getLineForOffset(end);
   1926 
   1927             TextUtils.getChars(mText, start, end, dest, destoff);
   1928 
   1929             for (int i = line1; i <= line2; i++) {
   1930                 mLayout.ellipsize(start, end, i, dest, destoff, mMethod);
   1931             }
   1932         }
   1933 
   1934         public int length() {
   1935             return mText.length();
   1936         }
   1937 
   1938         public CharSequence subSequence(int start, int end) {
   1939             char[] s = new char[end - start];
   1940             getChars(start, end, s, 0);
   1941             return new String(s);
   1942         }
   1943 
   1944         @Override
   1945         public String toString() {
   1946             char[] s = new char[length()];
   1947             getChars(0, length(), s, 0);
   1948             return new String(s);
   1949         }
   1950 
   1951     }
   1952 
   1953     /* package */ static class SpannedEllipsizer extends Ellipsizer implements Spanned {
   1954         private Spanned mSpanned;
   1955 
   1956         public SpannedEllipsizer(CharSequence display) {
   1957             super(display);
   1958             mSpanned = (Spanned) display;
   1959         }
   1960 
   1961         public <T> T[] getSpans(int start, int end, Class<T> type) {
   1962             return mSpanned.getSpans(start, end, type);
   1963         }
   1964 
   1965         public int getSpanStart(Object tag) {
   1966             return mSpanned.getSpanStart(tag);
   1967         }
   1968 
   1969         public int getSpanEnd(Object tag) {
   1970             return mSpanned.getSpanEnd(tag);
   1971         }
   1972 
   1973         public int getSpanFlags(Object tag) {
   1974             return mSpanned.getSpanFlags(tag);
   1975         }
   1976 
   1977         @SuppressWarnings("rawtypes")
   1978         public int nextSpanTransition(int start, int limit, Class type) {
   1979             return mSpanned.nextSpanTransition(start, limit, type);
   1980         }
   1981 
   1982         @Override
   1983         public CharSequence subSequence(int start, int end) {
   1984             char[] s = new char[end - start];
   1985             getChars(start, end, s, 0);
   1986 
   1987             SpannableString ss = new SpannableString(new String(s));
   1988             TextUtils.copySpansFrom(mSpanned, start, end, Object.class, ss, 0);
   1989             return ss;
   1990         }
   1991     }
   1992 
   1993     private CharSequence mText;
   1994     private TextPaint mPaint;
   1995     private int mWidth;
   1996     private Alignment mAlignment = Alignment.ALIGN_NORMAL;
   1997     private float mSpacingMult;
   1998     private float mSpacingAdd;
   1999     private static final Rect sTempRect = new Rect();
   2000     private boolean mSpannedText;
   2001     private TextDirectionHeuristic mTextDir;
   2002     private SpanSet<LineBackgroundSpan> mLineBackgroundSpans;
   2003 
   2004     public static final int DIR_LEFT_TO_RIGHT = 1;
   2005     public static final int DIR_RIGHT_TO_LEFT = -1;
   2006 
   2007     /* package */ static final int DIR_REQUEST_LTR = 1;
   2008     /* package */ static final int DIR_REQUEST_RTL = -1;
   2009     /* package */ static final int DIR_REQUEST_DEFAULT_LTR = 2;
   2010     /* package */ static final int DIR_REQUEST_DEFAULT_RTL = -2;
   2011 
   2012     /* package */ static final int RUN_LENGTH_MASK = 0x03ffffff;
   2013     /* package */ static final int RUN_LEVEL_SHIFT = 26;
   2014     /* package */ static final int RUN_LEVEL_MASK = 0x3f;
   2015     /* package */ static final int RUN_RTL_FLAG = 1 << RUN_LEVEL_SHIFT;
   2016 
   2017     public enum Alignment {
   2018         ALIGN_NORMAL,
   2019         ALIGN_OPPOSITE,
   2020         ALIGN_CENTER,
   2021         /** @hide */
   2022         ALIGN_LEFT,
   2023         /** @hide */
   2024         ALIGN_RIGHT,
   2025     }
   2026 
   2027     private static final int TAB_INCREMENT = 20;
   2028 
   2029     /* package */ static final Directions DIRS_ALL_LEFT_TO_RIGHT =
   2030         new Directions(new int[] { 0, RUN_LENGTH_MASK });
   2031     /* package */ static final Directions DIRS_ALL_RIGHT_TO_LEFT =
   2032         new Directions(new int[] { 0, RUN_LENGTH_MASK | RUN_RTL_FLAG });
   2033 
   2034 }
   2035