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