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.annotation.IntRange;
     21 import android.annotation.UnsupportedAppUsage;
     22 import android.graphics.Canvas;
     23 import android.graphics.Paint;
     24 import android.graphics.Path;
     25 import android.graphics.Rect;
     26 import android.graphics.text.LineBreaker;
     27 import android.text.method.TextKeyListener;
     28 import android.text.style.AlignmentSpan;
     29 import android.text.style.LeadingMarginSpan;
     30 import android.text.style.LeadingMarginSpan.LeadingMarginSpan2;
     31 import android.text.style.LineBackgroundSpan;
     32 import android.text.style.ParagraphStyle;
     33 import android.text.style.ReplacementSpan;
     34 import android.text.style.TabStopSpan;
     35 
     36 import com.android.internal.annotations.VisibleForTesting;
     37 import com.android.internal.util.ArrayUtils;
     38 import com.android.internal.util.GrowingArrayUtils;
     39 
     40 import java.lang.annotation.Retention;
     41 import java.lang.annotation.RetentionPolicy;
     42 import java.util.Arrays;
     43 
     44 /**
     45  * A base class that manages text layout in visual elements on
     46  * the screen.
     47  * <p>For text that will be edited, use a {@link DynamicLayout},
     48  * which will be updated as the text changes.
     49  * For text that will not change, use a {@link StaticLayout}.
     50  */
     51 public abstract class Layout {
     52     /** @hide */
     53     @IntDef(prefix = { "BREAK_STRATEGY_" }, value = {
     54             LineBreaker.BREAK_STRATEGY_SIMPLE,
     55             LineBreaker.BREAK_STRATEGY_HIGH_QUALITY,
     56             LineBreaker.BREAK_STRATEGY_BALANCED
     57     })
     58     @Retention(RetentionPolicy.SOURCE)
     59     public @interface BreakStrategy {}
     60 
     61     /**
     62      * Value for break strategy indicating simple line breaking. Automatic hyphens are not added
     63      * (though soft hyphens are respected), and modifying text generally doesn't affect the layout
     64      * before it (which yields a more consistent user experience when editing), but layout may not
     65      * be the highest quality.
     66      */
     67     public static final int BREAK_STRATEGY_SIMPLE = LineBreaker.BREAK_STRATEGY_SIMPLE;
     68 
     69     /**
     70      * Value for break strategy indicating high quality line breaking, including automatic
     71      * hyphenation and doing whole-paragraph optimization of line breaks.
     72      */
     73     public static final int BREAK_STRATEGY_HIGH_QUALITY = LineBreaker.BREAK_STRATEGY_HIGH_QUALITY;
     74 
     75     /**
     76      * Value for break strategy indicating balanced line breaking. The breaks are chosen to
     77      * make all lines as close to the same length as possible, including automatic hyphenation.
     78      */
     79     public static final int BREAK_STRATEGY_BALANCED = LineBreaker.BREAK_STRATEGY_BALANCED;
     80 
     81     /** @hide */
     82     @IntDef(prefix = { "HYPHENATION_FREQUENCY_" }, value = {
     83             HYPHENATION_FREQUENCY_NORMAL,
     84             HYPHENATION_FREQUENCY_FULL,
     85             HYPHENATION_FREQUENCY_NONE
     86     })
     87     @Retention(RetentionPolicy.SOURCE)
     88     public @interface HyphenationFrequency {}
     89 
     90     /**
     91      * Value for hyphenation frequency indicating no automatic hyphenation. Useful
     92      * for backward compatibility, and for cases where the automatic hyphenation algorithm results
     93      * in incorrect hyphenation. Mid-word breaks may still happen when a word is wider than the
     94      * layout and there is otherwise no valid break. Soft hyphens are ignored and will not be used
     95      * as suggestions for potential line breaks.
     96      */
     97     public static final int HYPHENATION_FREQUENCY_NONE = LineBreaker.HYPHENATION_FREQUENCY_NONE;
     98 
     99     /**
    100      * Value for hyphenation frequency indicating a light amount of automatic hyphenation, which
    101      * is a conservative default. Useful for informal cases, such as short sentences or chat
    102      * messages.
    103      */
    104     public static final int HYPHENATION_FREQUENCY_NORMAL = LineBreaker.HYPHENATION_FREQUENCY_NORMAL;
    105 
    106     /**
    107      * Value for hyphenation frequency indicating the full amount of automatic hyphenation, typical
    108      * in typography. Useful for running text and where it's important to put the maximum amount of
    109      * text in a screen with limited space.
    110      */
    111     public static final int HYPHENATION_FREQUENCY_FULL = LineBreaker.HYPHENATION_FREQUENCY_FULL;
    112 
    113     private static final ParagraphStyle[] NO_PARA_SPANS =
    114         ArrayUtils.emptyArray(ParagraphStyle.class);
    115 
    116     /** @hide */
    117     @IntDef(prefix = { "JUSTIFICATION_MODE_" }, value = {
    118             LineBreaker.JUSTIFICATION_MODE_NONE,
    119             LineBreaker.JUSTIFICATION_MODE_INTER_WORD
    120     })
    121     @Retention(RetentionPolicy.SOURCE)
    122     public @interface JustificationMode {}
    123 
    124     /**
    125      * Value for justification mode indicating no justification.
    126      */
    127     public static final int JUSTIFICATION_MODE_NONE = LineBreaker.JUSTIFICATION_MODE_NONE;
    128 
    129     /**
    130      * Value for justification mode indicating the text is justified by stretching word spacing.
    131      */
    132     public static final int JUSTIFICATION_MODE_INTER_WORD =
    133             LineBreaker.JUSTIFICATION_MODE_INTER_WORD;
    134 
    135     /*
    136      * Line spacing multiplier for default line spacing.
    137      */
    138     public static final float DEFAULT_LINESPACING_MULTIPLIER = 1.0f;
    139 
    140     /*
    141      * Line spacing addition for default line spacing.
    142      */
    143     public static final float DEFAULT_LINESPACING_ADDITION = 0.0f;
    144 
    145     /**
    146      * Return how wide a layout must be in order to display the specified text with one line per
    147      * paragraph.
    148      *
    149      * <p>As of O, Uses
    150      * {@link TextDirectionHeuristics#FIRSTSTRONG_LTR} as the default text direction heuristics. In
    151      * the earlier versions uses {@link TextDirectionHeuristics#LTR} as the default.</p>
    152      */
    153     public static float getDesiredWidth(CharSequence source,
    154                                         TextPaint paint) {
    155         return getDesiredWidth(source, 0, source.length(), paint);
    156     }
    157 
    158     /**
    159      * Return how wide a layout must be in order to display the specified text slice with one
    160      * line per paragraph.
    161      *
    162      * <p>As of O, Uses
    163      * {@link TextDirectionHeuristics#FIRSTSTRONG_LTR} as the default text direction heuristics. In
    164      * the earlier versions uses {@link TextDirectionHeuristics#LTR} as the default.</p>
    165      */
    166     public static float getDesiredWidth(CharSequence source, int start, int end, TextPaint paint) {
    167         return getDesiredWidth(source, start, end, paint, TextDirectionHeuristics.FIRSTSTRONG_LTR);
    168     }
    169 
    170     /**
    171      * Return how wide a layout must be in order to display the
    172      * specified text slice with one line per paragraph.
    173      *
    174      * @hide
    175      */
    176     public static float getDesiredWidth(CharSequence source, int start, int end, TextPaint paint,
    177             TextDirectionHeuristic textDir) {
    178         return getDesiredWidthWithLimit(source, start, end, paint, textDir, Float.MAX_VALUE);
    179     }
    180     /**
    181      * Return how wide a layout must be in order to display the
    182      * specified text slice with one line per paragraph.
    183      *
    184      * If the measured width exceeds given limit, returns limit value instead.
    185      * @hide
    186      */
    187     public static float getDesiredWidthWithLimit(CharSequence source, int start, int end,
    188             TextPaint paint, TextDirectionHeuristic textDir, float upperLimit) {
    189         float need = 0;
    190 
    191         int next;
    192         for (int i = start; i <= end; i = next) {
    193             next = TextUtils.indexOf(source, '\n', i, end);
    194 
    195             if (next < 0)
    196                 next = end;
    197 
    198             // note, omits trailing paragraph char
    199             float w = measurePara(paint, source, i, next, textDir);
    200             if (w > upperLimit) {
    201                 return upperLimit;
    202             }
    203 
    204             if (w > need)
    205                 need = w;
    206 
    207             next++;
    208         }
    209 
    210         return need;
    211     }
    212 
    213     /**
    214      * Subclasses of Layout use this constructor to set the display text,
    215      * width, and other standard properties.
    216      * @param text the text to render
    217      * @param paint the default paint for the layout.  Styles can override
    218      * various attributes of the paint.
    219      * @param width the wrapping width for the text.
    220      * @param align whether to left, right, or center the text.  Styles can
    221      * override the alignment.
    222      * @param spacingMult factor by which to scale the font size to get the
    223      * default line spacing
    224      * @param spacingAdd amount to add to the default line spacing
    225      */
    226     protected Layout(CharSequence text, TextPaint paint,
    227                      int width, Alignment align,
    228                      float spacingMult, float spacingAdd) {
    229         this(text, paint, width, align, TextDirectionHeuristics.FIRSTSTRONG_LTR,
    230                 spacingMult, spacingAdd);
    231     }
    232 
    233     /**
    234      * Subclasses of Layout use this constructor to set the display text,
    235      * width, and other standard properties.
    236      * @param text the text to render
    237      * @param paint the default paint for the layout.  Styles can override
    238      * various attributes of the paint.
    239      * @param width the wrapping width for the text.
    240      * @param align whether to left, right, or center the text.  Styles can
    241      * override the alignment.
    242      * @param spacingMult factor by which to scale the font size to get the
    243      * default line spacing
    244      * @param spacingAdd amount to add to the default line spacing
    245      *
    246      * @hide
    247      */
    248     protected Layout(CharSequence text, TextPaint paint,
    249                      int width, Alignment align, TextDirectionHeuristic textDir,
    250                      float spacingMult, float spacingAdd) {
    251 
    252         if (width < 0)
    253             throw new IllegalArgumentException("Layout: " + width + " < 0");
    254 
    255         // Ensure paint doesn't have baselineShift set.
    256         // While normally we don't modify the paint the user passed in,
    257         // we were already doing this in Styled.drawUniformRun with both
    258         // baselineShift and bgColor.  We probably should reevaluate bgColor.
    259         if (paint != null) {
    260             paint.bgColor = 0;
    261             paint.baselineShift = 0;
    262         }
    263 
    264         mText = text;
    265         mPaint = paint;
    266         mWidth = width;
    267         mAlignment = align;
    268         mSpacingMult = spacingMult;
    269         mSpacingAdd = spacingAdd;
    270         mSpannedText = text instanceof Spanned;
    271         mTextDir = textDir;
    272     }
    273 
    274     /** @hide */
    275     protected void setJustificationMode(@JustificationMode int justificationMode) {
    276         mJustificationMode = justificationMode;
    277     }
    278 
    279     /**
    280      * Replace constructor properties of this Layout with new ones.  Be careful.
    281      */
    282     /* package */ void replaceWith(CharSequence text, TextPaint paint,
    283                               int width, Alignment align,
    284                               float spacingmult, float spacingadd) {
    285         if (width < 0) {
    286             throw new IllegalArgumentException("Layout: " + width + " < 0");
    287         }
    288 
    289         mText = text;
    290         mPaint = paint;
    291         mWidth = width;
    292         mAlignment = align;
    293         mSpacingMult = spacingmult;
    294         mSpacingAdd = spacingadd;
    295         mSpannedText = text instanceof Spanned;
    296     }
    297 
    298     /**
    299      * Draw this Layout on the specified Canvas.
    300      */
    301     public void draw(Canvas c) {
    302         draw(c, null, null, 0);
    303     }
    304 
    305     /**
    306      * Draw this Layout on the specified canvas, with the highlight path drawn
    307      * between the background and the text.
    308      *
    309      * @param canvas the canvas
    310      * @param highlight the path of the highlight or cursor; can be null
    311      * @param highlightPaint the paint for the highlight
    312      * @param cursorOffsetVertical the amount to temporarily translate the
    313      *        canvas while rendering the highlight
    314      */
    315     public void draw(Canvas canvas, Path highlight, Paint highlightPaint,
    316             int cursorOffsetVertical) {
    317         final long lineRange = getLineRangeForDraw(canvas);
    318         int firstLine = TextUtils.unpackRangeStartFromLong(lineRange);
    319         int lastLine = TextUtils.unpackRangeEndFromLong(lineRange);
    320         if (lastLine < 0) return;
    321 
    322         drawBackground(canvas, highlight, highlightPaint, cursorOffsetVertical,
    323                 firstLine, lastLine);
    324         drawText(canvas, firstLine, lastLine);
    325     }
    326 
    327     private boolean isJustificationRequired(int lineNum) {
    328         if (mJustificationMode == JUSTIFICATION_MODE_NONE) return false;
    329         final int lineEnd = getLineEnd(lineNum);
    330         return lineEnd < mText.length() && mText.charAt(lineEnd - 1) != '\n';
    331     }
    332 
    333     private float getJustifyWidth(int lineNum) {
    334         Alignment paraAlign = mAlignment;
    335 
    336         int left = 0;
    337         int right = mWidth;
    338 
    339         final int dir = getParagraphDirection(lineNum);
    340 
    341         ParagraphStyle[] spans = NO_PARA_SPANS;
    342         if (mSpannedText) {
    343             Spanned sp = (Spanned) mText;
    344             final int start = getLineStart(lineNum);
    345 
    346             final boolean isFirstParaLine = (start == 0 || mText.charAt(start - 1) == '\n');
    347 
    348             if (isFirstParaLine) {
    349                 final int spanEnd = sp.nextSpanTransition(start, mText.length(),
    350                         ParagraphStyle.class);
    351                 spans = getParagraphSpans(sp, start, spanEnd, ParagraphStyle.class);
    352 
    353                 for (int n = spans.length - 1; n >= 0; n--) {
    354                     if (spans[n] instanceof AlignmentSpan) {
    355                         paraAlign = ((AlignmentSpan) spans[n]).getAlignment();
    356                         break;
    357                     }
    358                 }
    359             }
    360 
    361             final int length = spans.length;
    362             boolean useFirstLineMargin = isFirstParaLine;
    363             for (int n = 0; n < length; n++) {
    364                 if (spans[n] instanceof LeadingMarginSpan2) {
    365                     int count = ((LeadingMarginSpan2) spans[n]).getLeadingMarginLineCount();
    366                     int startLine = getLineForOffset(sp.getSpanStart(spans[n]));
    367                     if (lineNum < startLine + count) {
    368                         useFirstLineMargin = true;
    369                         break;
    370                     }
    371                 }
    372             }
    373             for (int n = 0; n < length; n++) {
    374                 if (spans[n] instanceof LeadingMarginSpan) {
    375                     LeadingMarginSpan margin = (LeadingMarginSpan) spans[n];
    376                     if (dir == DIR_RIGHT_TO_LEFT) {
    377                         right -= margin.getLeadingMargin(useFirstLineMargin);
    378                     } else {
    379                         left += margin.getLeadingMargin(useFirstLineMargin);
    380                     }
    381                 }
    382             }
    383         }
    384 
    385         final Alignment align;
    386         if (paraAlign == Alignment.ALIGN_LEFT) {
    387             align = (dir == DIR_LEFT_TO_RIGHT) ?  Alignment.ALIGN_NORMAL : Alignment.ALIGN_OPPOSITE;
    388         } else if (paraAlign == Alignment.ALIGN_RIGHT) {
    389             align = (dir == DIR_LEFT_TO_RIGHT) ?  Alignment.ALIGN_OPPOSITE : Alignment.ALIGN_NORMAL;
    390         } else {
    391             align = paraAlign;
    392         }
    393 
    394         final int indentWidth;
    395         if (align == Alignment.ALIGN_NORMAL) {
    396             if (dir == DIR_LEFT_TO_RIGHT) {
    397                 indentWidth = getIndentAdjust(lineNum, Alignment.ALIGN_LEFT);
    398             } else {
    399                 indentWidth = -getIndentAdjust(lineNum, Alignment.ALIGN_RIGHT);
    400             }
    401         } else if (align == Alignment.ALIGN_OPPOSITE) {
    402             if (dir == DIR_LEFT_TO_RIGHT) {
    403                 indentWidth = -getIndentAdjust(lineNum, Alignment.ALIGN_RIGHT);
    404             } else {
    405                 indentWidth = getIndentAdjust(lineNum, Alignment.ALIGN_LEFT);
    406             }
    407         } else { // Alignment.ALIGN_CENTER
    408             indentWidth = getIndentAdjust(lineNum, Alignment.ALIGN_CENTER);
    409         }
    410 
    411         return right - left - indentWidth;
    412     }
    413 
    414     /**
    415      * @hide
    416      */
    417     @UnsupportedAppUsage
    418     public void drawText(Canvas canvas, int firstLine, int lastLine) {
    419         int previousLineBottom = getLineTop(firstLine);
    420         int previousLineEnd = getLineStart(firstLine);
    421         ParagraphStyle[] spans = NO_PARA_SPANS;
    422         int spanEnd = 0;
    423         final TextPaint paint = mWorkPaint;
    424         paint.set(mPaint);
    425         CharSequence buf = mText;
    426 
    427         Alignment paraAlign = mAlignment;
    428         TabStops tabStops = null;
    429         boolean tabStopsIsInitialized = false;
    430 
    431         TextLine tl = TextLine.obtain();
    432 
    433         // Draw the lines, one at a time.
    434         // The baseline is the top of the following line minus the current line's descent.
    435         for (int lineNum = firstLine; lineNum <= lastLine; lineNum++) {
    436             int start = previousLineEnd;
    437             previousLineEnd = getLineStart(lineNum + 1);
    438             final boolean justify = isJustificationRequired(lineNum);
    439             int end = getLineVisibleEnd(lineNum, start, previousLineEnd);
    440             paint.setStartHyphenEdit(getStartHyphenEdit(lineNum));
    441             paint.setEndHyphenEdit(getEndHyphenEdit(lineNum));
    442 
    443             int ltop = previousLineBottom;
    444             int lbottom = getLineTop(lineNum + 1);
    445             previousLineBottom = lbottom;
    446             int lbaseline = lbottom - getLineDescent(lineNum);
    447 
    448             int dir = getParagraphDirection(lineNum);
    449             int left = 0;
    450             int right = mWidth;
    451 
    452             if (mSpannedText) {
    453                 Spanned sp = (Spanned) buf;
    454                 int textLength = buf.length();
    455                 boolean isFirstParaLine = (start == 0 || buf.charAt(start - 1) == '\n');
    456 
    457                 // New batch of paragraph styles, collect into spans array.
    458                 // Compute the alignment, last alignment style wins.
    459                 // Reset tabStops, we'll rebuild if we encounter a line with
    460                 // tabs.
    461                 // We expect paragraph spans to be relatively infrequent, use
    462                 // spanEnd so that we can check less frequently.  Since
    463                 // paragraph styles ought to apply to entire paragraphs, we can
    464                 // just collect the ones present at the start of the paragraph.
    465                 // If spanEnd is before the end of the paragraph, that's not
    466                 // our problem.
    467                 if (start >= spanEnd && (lineNum == firstLine || isFirstParaLine)) {
    468                     spanEnd = sp.nextSpanTransition(start, textLength,
    469                                                     ParagraphStyle.class);
    470                     spans = getParagraphSpans(sp, start, spanEnd, ParagraphStyle.class);
    471 
    472                     paraAlign = mAlignment;
    473                     for (int n = spans.length - 1; n >= 0; n--) {
    474                         if (spans[n] instanceof AlignmentSpan) {
    475                             paraAlign = ((AlignmentSpan) spans[n]).getAlignment();
    476                             break;
    477                         }
    478                     }
    479 
    480                     tabStopsIsInitialized = false;
    481                 }
    482 
    483                 // Draw all leading margin spans.  Adjust left or right according
    484                 // to the paragraph direction of the line.
    485                 final int length = spans.length;
    486                 boolean useFirstLineMargin = isFirstParaLine;
    487                 for (int n = 0; n < length; n++) {
    488                     if (spans[n] instanceof LeadingMarginSpan2) {
    489                         int count = ((LeadingMarginSpan2) spans[n]).getLeadingMarginLineCount();
    490                         int startLine = getLineForOffset(sp.getSpanStart(spans[n]));
    491                         // if there is more than one LeadingMarginSpan2, use
    492                         // the count that is greatest
    493                         if (lineNum < startLine + count) {
    494                             useFirstLineMargin = true;
    495                             break;
    496                         }
    497                     }
    498                 }
    499                 for (int n = 0; n < length; n++) {
    500                     if (spans[n] instanceof LeadingMarginSpan) {
    501                         LeadingMarginSpan margin = (LeadingMarginSpan) spans[n];
    502                         if (dir == DIR_RIGHT_TO_LEFT) {
    503                             margin.drawLeadingMargin(canvas, paint, right, dir, ltop,
    504                                                      lbaseline, lbottom, buf,
    505                                                      start, end, isFirstParaLine, this);
    506                             right -= margin.getLeadingMargin(useFirstLineMargin);
    507                         } else {
    508                             margin.drawLeadingMargin(canvas, paint, left, dir, ltop,
    509                                                      lbaseline, lbottom, buf,
    510                                                      start, end, isFirstParaLine, this);
    511                             left += margin.getLeadingMargin(useFirstLineMargin);
    512                         }
    513                     }
    514                 }
    515             }
    516 
    517             boolean hasTab = getLineContainsTab(lineNum);
    518             // Can't tell if we have tabs for sure, currently
    519             if (hasTab && !tabStopsIsInitialized) {
    520                 if (tabStops == null) {
    521                     tabStops = new TabStops(TAB_INCREMENT, spans);
    522                 } else {
    523                     tabStops.reset(TAB_INCREMENT, spans);
    524                 }
    525                 tabStopsIsInitialized = true;
    526             }
    527 
    528             // Determine whether the line aligns to normal, opposite, or center.
    529             Alignment align = paraAlign;
    530             if (align == Alignment.ALIGN_LEFT) {
    531                 align = (dir == DIR_LEFT_TO_RIGHT) ?
    532                     Alignment.ALIGN_NORMAL : Alignment.ALIGN_OPPOSITE;
    533             } else if (align == Alignment.ALIGN_RIGHT) {
    534                 align = (dir == DIR_LEFT_TO_RIGHT) ?
    535                     Alignment.ALIGN_OPPOSITE : Alignment.ALIGN_NORMAL;
    536             }
    537 
    538             int x;
    539             final int indentWidth;
    540             if (align == Alignment.ALIGN_NORMAL) {
    541                 if (dir == DIR_LEFT_TO_RIGHT) {
    542                     indentWidth = getIndentAdjust(lineNum, Alignment.ALIGN_LEFT);
    543                     x = left + indentWidth;
    544                 } else {
    545                     indentWidth = -getIndentAdjust(lineNum, Alignment.ALIGN_RIGHT);
    546                     x = right - indentWidth;
    547                 }
    548             } else {
    549                 int max = (int)getLineExtent(lineNum, tabStops, false);
    550                 if (align == Alignment.ALIGN_OPPOSITE) {
    551                     if (dir == DIR_LEFT_TO_RIGHT) {
    552                         indentWidth = -getIndentAdjust(lineNum, Alignment.ALIGN_RIGHT);
    553                         x = right - max - indentWidth;
    554                     } else {
    555                         indentWidth = getIndentAdjust(lineNum, Alignment.ALIGN_LEFT);
    556                         x = left - max + indentWidth;
    557                     }
    558                 } else { // Alignment.ALIGN_CENTER
    559                     indentWidth = getIndentAdjust(lineNum, Alignment.ALIGN_CENTER);
    560                     max = max & ~1;
    561                     x = ((right + left - max) >> 1) + indentWidth;
    562                 }
    563             }
    564 
    565             Directions directions = getLineDirections(lineNum);
    566             if (directions == DIRS_ALL_LEFT_TO_RIGHT && !mSpannedText && !hasTab && !justify) {
    567                 // XXX: assumes there's nothing additional to be done
    568                 canvas.drawText(buf, start, end, x, lbaseline, paint);
    569             } else {
    570                 tl.set(paint, buf, start, end, dir, directions, hasTab, tabStops,
    571                         getEllipsisStart(lineNum),
    572                         getEllipsisStart(lineNum) + getEllipsisCount(lineNum));
    573                 if (justify) {
    574                     tl.justify(right - left - indentWidth);
    575                 }
    576                 tl.draw(canvas, x, ltop, lbaseline, lbottom);
    577             }
    578         }
    579 
    580         TextLine.recycle(tl);
    581     }
    582 
    583     /**
    584      * @hide
    585      */
    586     @UnsupportedAppUsage
    587     public void drawBackground(Canvas canvas, Path highlight, Paint highlightPaint,
    588             int cursorOffsetVertical, int firstLine, int lastLine) {
    589         // First, draw LineBackgroundSpans.
    590         // LineBackgroundSpans know nothing about the alignment, margins, or
    591         // direction of the layout or line.  XXX: Should they?
    592         // They are evaluated at each line.
    593         if (mSpannedText) {
    594             if (mLineBackgroundSpans == null) {
    595                 mLineBackgroundSpans = new SpanSet<LineBackgroundSpan>(LineBackgroundSpan.class);
    596             }
    597 
    598             Spanned buffer = (Spanned) mText;
    599             int textLength = buffer.length();
    600             mLineBackgroundSpans.init(buffer, 0, textLength);
    601 
    602             if (mLineBackgroundSpans.numberOfSpans > 0) {
    603                 int previousLineBottom = getLineTop(firstLine);
    604                 int previousLineEnd = getLineStart(firstLine);
    605                 ParagraphStyle[] spans = NO_PARA_SPANS;
    606                 int spansLength = 0;
    607                 TextPaint paint = mPaint;
    608                 int spanEnd = 0;
    609                 final int width = mWidth;
    610                 for (int i = firstLine; i <= lastLine; i++) {
    611                     int start = previousLineEnd;
    612                     int end = getLineStart(i + 1);
    613                     previousLineEnd = end;
    614 
    615                     int ltop = previousLineBottom;
    616                     int lbottom = getLineTop(i + 1);
    617                     previousLineBottom = lbottom;
    618                     int lbaseline = lbottom - getLineDescent(i);
    619 
    620                     if (end >= spanEnd) {
    621                         // These should be infrequent, so we'll use this so that
    622                         // we don't have to check as often.
    623                         spanEnd = mLineBackgroundSpans.getNextTransition(start, textLength);
    624                         // All LineBackgroundSpans on a line contribute to its background.
    625                         spansLength = 0;
    626                         // Duplication of the logic of getParagraphSpans
    627                         if (start != end || start == 0) {
    628                             // Equivalent to a getSpans(start, end), but filling the 'spans' local
    629                             // array instead to reduce memory allocation
    630                             for (int j = 0; j < mLineBackgroundSpans.numberOfSpans; j++) {
    631                                 // equal test is valid since both intervals are not empty by
    632                                 // construction
    633                                 if (mLineBackgroundSpans.spanStarts[j] >= end ||
    634                                         mLineBackgroundSpans.spanEnds[j] <= start) continue;
    635                                 spans = GrowingArrayUtils.append(
    636                                         spans, spansLength, mLineBackgroundSpans.spans[j]);
    637                                 spansLength++;
    638                             }
    639                         }
    640                     }
    641 
    642                     for (int n = 0; n < spansLength; n++) {
    643                         LineBackgroundSpan lineBackgroundSpan = (LineBackgroundSpan) spans[n];
    644                         lineBackgroundSpan.drawBackground(canvas, paint, 0, width,
    645                                 ltop, lbaseline, lbottom,
    646                                 buffer, start, end, i);
    647                     }
    648                 }
    649             }
    650             mLineBackgroundSpans.recycle();
    651         }
    652 
    653         // There can be a highlight even without spans if we are drawing
    654         // a non-spanned transformation of a spanned editing buffer.
    655         if (highlight != null) {
    656             if (cursorOffsetVertical != 0) canvas.translate(0, cursorOffsetVertical);
    657             canvas.drawPath(highlight, highlightPaint);
    658             if (cursorOffsetVertical != 0) canvas.translate(0, -cursorOffsetVertical);
    659         }
    660     }
    661 
    662     /**
    663      * @param canvas
    664      * @return The range of lines that need to be drawn, possibly empty.
    665      * @hide
    666      */
    667     @UnsupportedAppUsage
    668     public long getLineRangeForDraw(Canvas canvas) {
    669         int dtop, dbottom;
    670 
    671         synchronized (sTempRect) {
    672             if (!canvas.getClipBounds(sTempRect)) {
    673                 // Negative range end used as a special flag
    674                 return TextUtils.packRangeInLong(0, -1);
    675             }
    676 
    677             dtop = sTempRect.top;
    678             dbottom = sTempRect.bottom;
    679         }
    680 
    681         final int top = Math.max(dtop, 0);
    682         final int bottom = Math.min(getLineTop(getLineCount()), dbottom);
    683 
    684         if (top >= bottom) return TextUtils.packRangeInLong(0, -1);
    685         return TextUtils.packRangeInLong(getLineForVertical(top), getLineForVertical(bottom));
    686     }
    687 
    688     /**
    689      * Return the start position of the line, given the left and right bounds
    690      * of the margins.
    691      *
    692      * @param line the line index
    693      * @param left the left bounds (0, or leading margin if ltr para)
    694      * @param right the right bounds (width, minus leading margin if rtl para)
    695      * @return the start position of the line (to right of line if rtl para)
    696      */
    697     private int getLineStartPos(int line, int left, int right) {
    698         // Adjust the point at which to start rendering depending on the
    699         // alignment of the paragraph.
    700         Alignment align = getParagraphAlignment(line);
    701         int dir = getParagraphDirection(line);
    702 
    703         if (align == Alignment.ALIGN_LEFT) {
    704             align = (dir == DIR_LEFT_TO_RIGHT) ? Alignment.ALIGN_NORMAL : Alignment.ALIGN_OPPOSITE;
    705         } else if (align == Alignment.ALIGN_RIGHT) {
    706             align = (dir == DIR_LEFT_TO_RIGHT) ? Alignment.ALIGN_OPPOSITE : Alignment.ALIGN_NORMAL;
    707         }
    708 
    709         int x;
    710         if (align == Alignment.ALIGN_NORMAL) {
    711             if (dir == DIR_LEFT_TO_RIGHT) {
    712                 x = left + getIndentAdjust(line, Alignment.ALIGN_LEFT);
    713             } else {
    714                 x = right + getIndentAdjust(line, Alignment.ALIGN_RIGHT);
    715             }
    716         } else {
    717             TabStops tabStops = null;
    718             if (mSpannedText && getLineContainsTab(line)) {
    719                 Spanned spanned = (Spanned) mText;
    720                 int start = getLineStart(line);
    721                 int spanEnd = spanned.nextSpanTransition(start, spanned.length(),
    722                         TabStopSpan.class);
    723                 TabStopSpan[] tabSpans = getParagraphSpans(spanned, start, spanEnd,
    724                         TabStopSpan.class);
    725                 if (tabSpans.length > 0) {
    726                     tabStops = new TabStops(TAB_INCREMENT, tabSpans);
    727                 }
    728             }
    729             int max = (int)getLineExtent(line, tabStops, false);
    730             if (align == Alignment.ALIGN_OPPOSITE) {
    731                 if (dir == DIR_LEFT_TO_RIGHT) {
    732                     x = right - max + getIndentAdjust(line, Alignment.ALIGN_RIGHT);
    733                 } else {
    734                     // max is negative here
    735                     x = left - max + getIndentAdjust(line, Alignment.ALIGN_LEFT);
    736                 }
    737             } else { // Alignment.ALIGN_CENTER
    738                 max = max & ~1;
    739                 x = (left + right - max) >> 1 + getIndentAdjust(line, Alignment.ALIGN_CENTER);
    740             }
    741         }
    742         return x;
    743     }
    744 
    745     /**
    746      * Return the text that is displayed by this Layout.
    747      */
    748     public final CharSequence getText() {
    749         return mText;
    750     }
    751 
    752     /**
    753      * Return the base Paint properties for this layout.
    754      * Do NOT change the paint, which may result in funny
    755      * drawing for this layout.
    756      */
    757     public final TextPaint getPaint() {
    758         return mPaint;
    759     }
    760 
    761     /**
    762      * Return the width of this layout.
    763      */
    764     public final int getWidth() {
    765         return mWidth;
    766     }
    767 
    768     /**
    769      * Return the width to which this Layout is ellipsizing, or
    770      * {@link #getWidth} if it is not doing anything special.
    771      */
    772     public int getEllipsizedWidth() {
    773         return mWidth;
    774     }
    775 
    776     /**
    777      * Increase the width of this layout to the specified width.
    778      * Be careful to use this only when you know it is appropriate&mdash;
    779      * it does not cause the text to reflow to use the full new width.
    780      */
    781     public final void increaseWidthTo(int wid) {
    782         if (wid < mWidth) {
    783             throw new RuntimeException("attempted to reduce Layout width");
    784         }
    785 
    786         mWidth = wid;
    787     }
    788 
    789     /**
    790      * Return the total height of this layout.
    791      */
    792     public int getHeight() {
    793         return getLineTop(getLineCount());
    794     }
    795 
    796     /**
    797      * Return the total height of this layout.
    798      *
    799      * @param cap if true and max lines is set, returns the height of the layout at the max lines.
    800      *
    801      * @hide
    802      */
    803     public int getHeight(boolean cap) {
    804         return getHeight();
    805     }
    806 
    807     /**
    808      * Return the base alignment of this layout.
    809      */
    810     public final Alignment getAlignment() {
    811         return mAlignment;
    812     }
    813 
    814     /**
    815      * Return what the text height is multiplied by to get the line height.
    816      */
    817     public final float getSpacingMultiplier() {
    818         return mSpacingMult;
    819     }
    820 
    821     /**
    822      * Return the number of units of leading that are added to each line.
    823      */
    824     public final float getSpacingAdd() {
    825         return mSpacingAdd;
    826     }
    827 
    828     /**
    829      * Return the heuristic used to determine paragraph text direction.
    830      * @hide
    831      */
    832     public final TextDirectionHeuristic getTextDirectionHeuristic() {
    833         return mTextDir;
    834     }
    835 
    836     /**
    837      * Return the number of lines of text in this layout.
    838      */
    839     public abstract int getLineCount();
    840 
    841     /**
    842      * Return the baseline for the specified line (0&hellip;getLineCount() - 1)
    843      * If bounds is not null, return the top, left, right, bottom extents
    844      * of the specified line in it.
    845      * @param line which line to examine (0..getLineCount() - 1)
    846      * @param bounds Optional. If not null, it returns the extent of the line
    847      * @return the Y-coordinate of the baseline
    848      */
    849     public int getLineBounds(int line, Rect bounds) {
    850         if (bounds != null) {
    851             bounds.left = 0;     // ???
    852             bounds.top = getLineTop(line);
    853             bounds.right = mWidth;   // ???
    854             bounds.bottom = getLineTop(line + 1);
    855         }
    856         return getLineBaseline(line);
    857     }
    858 
    859     /**
    860      * Return the vertical position of the top of the specified line
    861      * (0&hellip;getLineCount()).
    862      * If the specified line is equal to the line count, returns the
    863      * bottom of the last line.
    864      */
    865     public abstract int getLineTop(int line);
    866 
    867     /**
    868      * Return the descent of the specified line(0&hellip;getLineCount() - 1).
    869      */
    870     public abstract int getLineDescent(int line);
    871 
    872     /**
    873      * Return the text offset of the beginning of the specified line (
    874      * 0&hellip;getLineCount()). If the specified line is equal to the line
    875      * count, returns the length of the text.
    876      */
    877     public abstract int getLineStart(int line);
    878 
    879     /**
    880      * Returns the primary directionality of the paragraph containing the
    881      * specified line, either 1 for left-to-right lines, or -1 for right-to-left
    882      * lines (see {@link #DIR_LEFT_TO_RIGHT}, {@link #DIR_RIGHT_TO_LEFT}).
    883      */
    884     public abstract int getParagraphDirection(int line);
    885 
    886     /**
    887      * Returns whether the specified line contains one or more
    888      * characters that need to be handled specially, like tabs.
    889      */
    890     public abstract boolean getLineContainsTab(int line);
    891 
    892     /**
    893      * Returns the directional run information for the specified line.
    894      * The array alternates counts of characters in left-to-right
    895      * and right-to-left segments of the line.
    896      *
    897      * <p>NOTE: this is inadequate to support bidirectional text, and will change.
    898      */
    899     public abstract Directions getLineDirections(int line);
    900 
    901     /**
    902      * Returns the (negative) number of extra pixels of ascent padding in the
    903      * top line of the Layout.
    904      */
    905     public abstract int getTopPadding();
    906 
    907     /**
    908      * Returns the number of extra pixels of descent padding in the
    909      * bottom line of the Layout.
    910      */
    911     public abstract int getBottomPadding();
    912 
    913     /**
    914      * Returns the start hyphen edit for a line.
    915      *
    916      * @hide
    917      */
    918     public @Paint.StartHyphenEdit int getStartHyphenEdit(int line) {
    919         return Paint.START_HYPHEN_EDIT_NO_EDIT;
    920     }
    921 
    922     /**
    923      * Returns the end hyphen edit for a line.
    924      *
    925      * @hide
    926      */
    927     public @Paint.EndHyphenEdit int getEndHyphenEdit(int line) {
    928         return Paint.END_HYPHEN_EDIT_NO_EDIT;
    929     }
    930 
    931     /**
    932      * Returns the left indent for a line.
    933      *
    934      * @hide
    935      */
    936     public int getIndentAdjust(int line, Alignment alignment) {
    937         return 0;
    938     }
    939 
    940     /**
    941      * Returns true if the character at offset and the preceding character
    942      * are at different run levels (and thus there's a split caret).
    943      * @param offset the offset
    944      * @return true if at a level boundary
    945      * @hide
    946      */
    947     @UnsupportedAppUsage
    948     public boolean isLevelBoundary(int offset) {
    949         int line = getLineForOffset(offset);
    950         Directions dirs = getLineDirections(line);
    951         if (dirs == DIRS_ALL_LEFT_TO_RIGHT || dirs == DIRS_ALL_RIGHT_TO_LEFT) {
    952             return false;
    953         }
    954 
    955         int[] runs = dirs.mDirections;
    956         int lineStart = getLineStart(line);
    957         int lineEnd = getLineEnd(line);
    958         if (offset == lineStart || offset == lineEnd) {
    959             int paraLevel = getParagraphDirection(line) == 1 ? 0 : 1;
    960             int runIndex = offset == lineStart ? 0 : runs.length - 2;
    961             return ((runs[runIndex + 1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK) != paraLevel;
    962         }
    963 
    964         offset -= lineStart;
    965         for (int i = 0; i < runs.length; i += 2) {
    966             if (offset == runs[i]) {
    967                 return true;
    968             }
    969         }
    970         return false;
    971     }
    972 
    973     /**
    974      * Returns true if the character at offset is right to left (RTL).
    975      * @param offset the offset
    976      * @return true if the character is RTL, false if it is LTR
    977      */
    978     public boolean isRtlCharAt(int offset) {
    979         int line = getLineForOffset(offset);
    980         Directions dirs = getLineDirections(line);
    981         if (dirs == DIRS_ALL_LEFT_TO_RIGHT) {
    982             return false;
    983         }
    984         if (dirs == DIRS_ALL_RIGHT_TO_LEFT) {
    985             return  true;
    986         }
    987         int[] runs = dirs.mDirections;
    988         int lineStart = getLineStart(line);
    989         for (int i = 0; i < runs.length; i += 2) {
    990             int start = lineStart + runs[i];
    991             int limit = start + (runs[i+1] & RUN_LENGTH_MASK);
    992             if (offset >= start && offset < limit) {
    993                 int level = (runs[i+1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK;
    994                 return ((level & 1) != 0);
    995             }
    996         }
    997         // Should happen only if the offset is "out of bounds"
    998         return false;
    999     }
   1000 
   1001     /**
   1002      * Returns the range of the run that the character at offset belongs to.
   1003      * @param offset the offset
   1004      * @return The range of the run
   1005      * @hide
   1006      */
   1007     public long getRunRange(int offset) {
   1008         int line = getLineForOffset(offset);
   1009         Directions dirs = getLineDirections(line);
   1010         if (dirs == DIRS_ALL_LEFT_TO_RIGHT || dirs == DIRS_ALL_RIGHT_TO_LEFT) {
   1011             return TextUtils.packRangeInLong(0, getLineEnd(line));
   1012         }
   1013         int[] runs = dirs.mDirections;
   1014         int lineStart = getLineStart(line);
   1015         for (int i = 0; i < runs.length; i += 2) {
   1016             int start = lineStart + runs[i];
   1017             int limit = start + (runs[i+1] & RUN_LENGTH_MASK);
   1018             if (offset >= start && offset < limit) {
   1019                 return TextUtils.packRangeInLong(start, limit);
   1020             }
   1021         }
   1022         // Should happen only if the offset is "out of bounds"
   1023         return TextUtils.packRangeInLong(0, getLineEnd(line));
   1024     }
   1025 
   1026     /**
   1027      * Checks if the trailing BiDi level should be used for an offset
   1028      *
   1029      * This method is useful when the offset is at the BiDi level transition point and determine
   1030      * which run need to be used. For example, let's think about following input: (L* denotes
   1031      * Left-to-Right characters, R* denotes Right-to-Left characters.)
   1032      * Input (Logical Order): L1 L2 L3 R1 R2 R3 L4 L5 L6
   1033      * Input (Display Order): L1 L2 L3 R3 R2 R1 L4 L5 L6
   1034      *
   1035      * Then, think about selecting the range (3, 6). The offset=3 and offset=6 are ambiguous here
   1036      * since they are at the BiDi transition point.  In Android, the offset is considered to be
   1037      * associated with the trailing run if the BiDi level of the trailing run is higher than of the
   1038      * previous run.  In this case, the BiDi level of the input text is as follows:
   1039      *
   1040      * Input (Logical Order): L1 L2 L3 R1 R2 R3 L4 L5 L6
   1041      *              BiDi Run: [ Run 0 ][ Run 1 ][ Run 2 ]
   1042      *            BiDi Level:  0  0  0  1  1  1  0  0  0
   1043      *
   1044      * Thus, offset = 3 is part of Run 1 and this method returns true for offset = 3, since the BiDi
   1045      * level of Run 1 is higher than the level of Run 0.  Similarly, the offset = 6 is a part of Run
   1046      * 1 and this method returns false for the offset = 6 since the BiDi level of Run 1 is higher
   1047      * than the level of Run 2.
   1048      *
   1049      * @returns true if offset is at the BiDi level transition point and trailing BiDi level is
   1050      *          higher than previous BiDi level. See above for the detail.
   1051      * @hide
   1052      */
   1053     @VisibleForTesting
   1054     public boolean primaryIsTrailingPrevious(int offset) {
   1055         int line = getLineForOffset(offset);
   1056         int lineStart = getLineStart(line);
   1057         int lineEnd = getLineEnd(line);
   1058         int[] runs = getLineDirections(line).mDirections;
   1059 
   1060         int levelAt = -1;
   1061         for (int i = 0; i < runs.length; i += 2) {
   1062             int start = lineStart + runs[i];
   1063             int limit = start + (runs[i+1] & RUN_LENGTH_MASK);
   1064             if (limit > lineEnd) {
   1065                 limit = lineEnd;
   1066             }
   1067             if (offset >= start && offset < limit) {
   1068                 if (offset > start) {
   1069                     // Previous character is at same level, so don't use trailing.
   1070                     return false;
   1071                 }
   1072                 levelAt = (runs[i+1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK;
   1073                 break;
   1074             }
   1075         }
   1076         if (levelAt == -1) {
   1077             // Offset was limit of line.
   1078             levelAt = getParagraphDirection(line) == 1 ? 0 : 1;
   1079         }
   1080 
   1081         // At level boundary, check previous level.
   1082         int levelBefore = -1;
   1083         if (offset == lineStart) {
   1084             levelBefore = getParagraphDirection(line) == 1 ? 0 : 1;
   1085         } else {
   1086             offset -= 1;
   1087             for (int i = 0; i < runs.length; i += 2) {
   1088                 int start = lineStart + runs[i];
   1089                 int limit = start + (runs[i+1] & RUN_LENGTH_MASK);
   1090                 if (limit > lineEnd) {
   1091                     limit = lineEnd;
   1092                 }
   1093                 if (offset >= start && offset < limit) {
   1094                     levelBefore = (runs[i+1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK;
   1095                     break;
   1096                 }
   1097             }
   1098         }
   1099 
   1100         return levelBefore < levelAt;
   1101     }
   1102 
   1103     /**
   1104      * Computes in linear time the results of calling
   1105      * #primaryIsTrailingPrevious for all offsets on a line.
   1106      * @param line The line giving the offsets we compute the information for
   1107      * @return The array of results, indexed from 0, where 0 corresponds to the line start offset
   1108      * @hide
   1109      */
   1110     @VisibleForTesting
   1111     public boolean[] primaryIsTrailingPreviousAllLineOffsets(int line) {
   1112         int lineStart = getLineStart(line);
   1113         int lineEnd = getLineEnd(line);
   1114         int[] runs = getLineDirections(line).mDirections;
   1115 
   1116         boolean[] trailing = new boolean[lineEnd - lineStart + 1];
   1117 
   1118         byte[] level = new byte[lineEnd - lineStart + 1];
   1119         for (int i = 0; i < runs.length; i += 2) {
   1120             int start = lineStart + runs[i];
   1121             int limit = start + (runs[i + 1] & RUN_LENGTH_MASK);
   1122             if (limit > lineEnd) {
   1123                 limit = lineEnd;
   1124             }
   1125             if (limit == start) {
   1126                 continue;
   1127             }
   1128             level[limit - lineStart - 1] =
   1129                     (byte) ((runs[i + 1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK);
   1130         }
   1131 
   1132         for (int i = 0; i < runs.length; i += 2) {
   1133             int start = lineStart + runs[i];
   1134             byte currentLevel = (byte) ((runs[i + 1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK);
   1135             trailing[start - lineStart] = currentLevel > (start == lineStart
   1136                     ? (getParagraphDirection(line) == 1 ? 0 : 1)
   1137                     : level[start - lineStart - 1]);
   1138         }
   1139 
   1140         return trailing;
   1141     }
   1142 
   1143     /**
   1144      * Get the primary horizontal position for the specified text offset.
   1145      * This is the location where a new character would be inserted in
   1146      * the paragraph's primary direction.
   1147      */
   1148     public float getPrimaryHorizontal(int offset) {
   1149         return getPrimaryHorizontal(offset, false /* not clamped */);
   1150     }
   1151 
   1152     /**
   1153      * Get the primary horizontal position for the specified text offset, but
   1154      * optionally clamp it so that it doesn't exceed the width of the layout.
   1155      * @hide
   1156      */
   1157     @UnsupportedAppUsage
   1158     public float getPrimaryHorizontal(int offset, boolean clamped) {
   1159         boolean trailing = primaryIsTrailingPrevious(offset);
   1160         return getHorizontal(offset, trailing, clamped);
   1161     }
   1162 
   1163     /**
   1164      * Get the secondary horizontal position for the specified text offset.
   1165      * This is the location where a new character would be inserted in
   1166      * the direction other than the paragraph's primary direction.
   1167      */
   1168     public float getSecondaryHorizontal(int offset) {
   1169         return getSecondaryHorizontal(offset, false /* not clamped */);
   1170     }
   1171 
   1172     /**
   1173      * Get the secondary horizontal position for the specified text offset, but
   1174      * optionally clamp it so that it doesn't exceed the width of the layout.
   1175      * @hide
   1176      */
   1177     @UnsupportedAppUsage
   1178     public float getSecondaryHorizontal(int offset, boolean clamped) {
   1179         boolean trailing = primaryIsTrailingPrevious(offset);
   1180         return getHorizontal(offset, !trailing, clamped);
   1181     }
   1182 
   1183     private float getHorizontal(int offset, boolean primary) {
   1184         return primary ? getPrimaryHorizontal(offset) : getSecondaryHorizontal(offset);
   1185     }
   1186 
   1187     private float getHorizontal(int offset, boolean trailing, boolean clamped) {
   1188         int line = getLineForOffset(offset);
   1189 
   1190         return getHorizontal(offset, trailing, line, clamped);
   1191     }
   1192 
   1193     private float getHorizontal(int offset, boolean trailing, int line, boolean clamped) {
   1194         int start = getLineStart(line);
   1195         int end = getLineEnd(line);
   1196         int dir = getParagraphDirection(line);
   1197         boolean hasTab = getLineContainsTab(line);
   1198         Directions directions = getLineDirections(line);
   1199 
   1200         TabStops tabStops = null;
   1201         if (hasTab && mText instanceof Spanned) {
   1202             // Just checking this line should be good enough, tabs should be
   1203             // consistent across all lines in a paragraph.
   1204             TabStopSpan[] tabs = getParagraphSpans((Spanned) mText, start, end, TabStopSpan.class);
   1205             if (tabs.length > 0) {
   1206                 tabStops = new TabStops(TAB_INCREMENT, tabs); // XXX should reuse
   1207             }
   1208         }
   1209 
   1210         TextLine tl = TextLine.obtain();
   1211         tl.set(mPaint, mText, start, end, dir, directions, hasTab, tabStops,
   1212                 getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line));
   1213         float wid = tl.measure(offset - start, trailing, null);
   1214         TextLine.recycle(tl);
   1215 
   1216         if (clamped && wid > mWidth) {
   1217             wid = mWidth;
   1218         }
   1219         int left = getParagraphLeft(line);
   1220         int right = getParagraphRight(line);
   1221 
   1222         return getLineStartPos(line, left, right) + wid;
   1223     }
   1224 
   1225     /**
   1226      * Computes in linear time the results of calling #getHorizontal for all offsets on a line.
   1227      *
   1228      * @param line The line giving the offsets we compute information for
   1229      * @param clamped Whether to clamp the results to the width of the layout
   1230      * @param primary Whether the results should be the primary or the secondary horizontal
   1231      * @return The array of results, indexed from 0, where 0 corresponds to the line start offset
   1232      */
   1233     private float[] getLineHorizontals(int line, boolean clamped, boolean primary) {
   1234         int start = getLineStart(line);
   1235         int end = getLineEnd(line);
   1236         int dir = getParagraphDirection(line);
   1237         boolean hasTab = getLineContainsTab(line);
   1238         Directions directions = getLineDirections(line);
   1239 
   1240         TabStops tabStops = null;
   1241         if (hasTab && mText instanceof Spanned) {
   1242             // Just checking this line should be good enough, tabs should be
   1243             // consistent across all lines in a paragraph.
   1244             TabStopSpan[] tabs = getParagraphSpans((Spanned) mText, start, end, TabStopSpan.class);
   1245             if (tabs.length > 0) {
   1246                 tabStops = new TabStops(TAB_INCREMENT, tabs); // XXX should reuse
   1247             }
   1248         }
   1249 
   1250         TextLine tl = TextLine.obtain();
   1251         tl.set(mPaint, mText, start, end, dir, directions, hasTab, tabStops,
   1252                 getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line));
   1253         boolean[] trailings = primaryIsTrailingPreviousAllLineOffsets(line);
   1254         if (!primary) {
   1255             for (int offset = 0; offset < trailings.length; ++offset) {
   1256                 trailings[offset] = !trailings[offset];
   1257             }
   1258         }
   1259         float[] wid = tl.measureAllOffsets(trailings, null);
   1260         TextLine.recycle(tl);
   1261 
   1262         if (clamped) {
   1263             for (int offset = 0; offset < wid.length; ++offset) {
   1264                 if (wid[offset] > mWidth) {
   1265                     wid[offset] = mWidth;
   1266                 }
   1267             }
   1268         }
   1269         int left = getParagraphLeft(line);
   1270         int right = getParagraphRight(line);
   1271 
   1272         int lineStartPos = getLineStartPos(line, left, right);
   1273         float[] horizontal = new float[end - start + 1];
   1274         for (int offset = 0; offset < horizontal.length; ++offset) {
   1275             horizontal[offset] = lineStartPos + wid[offset];
   1276         }
   1277         return horizontal;
   1278     }
   1279 
   1280     /**
   1281      * Get the leftmost position that should be exposed for horizontal
   1282      * scrolling on the specified line.
   1283      */
   1284     public float getLineLeft(int line) {
   1285         final int dir = getParagraphDirection(line);
   1286         Alignment align = getParagraphAlignment(line);
   1287         // Before Q, StaticLayout.Builder.setAlignment didn't check whether the input alignment
   1288         // is null. And when it is null, the old behavior is the same as ALIGN_CENTER.
   1289         // To keep consistency, we convert a null alignment to ALIGN_CENTER.
   1290         if (align == null) {
   1291             align = Alignment.ALIGN_CENTER;
   1292         }
   1293 
   1294         // First convert combinations of alignment and direction settings to
   1295         // three basic cases: ALIGN_LEFT, ALIGN_RIGHT and ALIGN_CENTER.
   1296         // For unexpected cases, it will fallback to ALIGN_LEFT.
   1297         final Alignment resultAlign;
   1298         switch(align) {
   1299             case ALIGN_NORMAL:
   1300                 resultAlign =
   1301                         dir == DIR_RIGHT_TO_LEFT ? Alignment.ALIGN_RIGHT : Alignment.ALIGN_LEFT;
   1302                 break;
   1303             case ALIGN_OPPOSITE:
   1304                 resultAlign =
   1305                         dir == DIR_RIGHT_TO_LEFT ? Alignment.ALIGN_LEFT : Alignment.ALIGN_RIGHT;
   1306                 break;
   1307             case ALIGN_CENTER:
   1308                 resultAlign = Alignment.ALIGN_CENTER;
   1309                 break;
   1310             case ALIGN_RIGHT:
   1311                 resultAlign = Alignment.ALIGN_RIGHT;
   1312                 break;
   1313             default: /* align == Alignment.ALIGN_LEFT */
   1314                 resultAlign = Alignment.ALIGN_LEFT;
   1315         }
   1316 
   1317         // Here we must use getLineMax() to do the computation, because it maybe overridden by
   1318         // derived class. And also note that line max equals the width of the text in that line
   1319         // plus the leading margin.
   1320         switch (resultAlign) {
   1321             case ALIGN_CENTER:
   1322                 final int left = getParagraphLeft(line);
   1323                 final float max = getLineMax(line);
   1324                 // This computation only works when mWidth equals leadingMargin plus
   1325                 // the width of text in this line. If this condition doesn't meet anymore,
   1326                 // please change here too.
   1327                 return (float) Math.floor(left + (mWidth - max) / 2);
   1328             case ALIGN_RIGHT:
   1329                 return mWidth - getLineMax(line);
   1330             default: /* resultAlign == Alignment.ALIGN_LEFT */
   1331                 return 0;
   1332         }
   1333     }
   1334 
   1335     /**
   1336      * Get the rightmost position that should be exposed for horizontal
   1337      * scrolling on the specified line.
   1338      */
   1339     public float getLineRight(int line) {
   1340         final int dir = getParagraphDirection(line);
   1341         Alignment align = getParagraphAlignment(line);
   1342         // Before Q, StaticLayout.Builder.setAlignment didn't check whether the input alignment
   1343         // is null. And when it is null, the old behavior is the same as ALIGN_CENTER.
   1344         // To keep consistency, we convert a null alignment to ALIGN_CENTER.
   1345         if (align == null) {
   1346             align = Alignment.ALIGN_CENTER;
   1347         }
   1348 
   1349         final Alignment resultAlign;
   1350         switch(align) {
   1351             case ALIGN_NORMAL:
   1352                 resultAlign =
   1353                         dir == DIR_RIGHT_TO_LEFT ? Alignment.ALIGN_RIGHT : Alignment.ALIGN_LEFT;
   1354                 break;
   1355             case ALIGN_OPPOSITE:
   1356                 resultAlign =
   1357                         dir == DIR_RIGHT_TO_LEFT ? Alignment.ALIGN_LEFT : Alignment.ALIGN_RIGHT;
   1358                 break;
   1359             case ALIGN_CENTER:
   1360                 resultAlign = Alignment.ALIGN_CENTER;
   1361                 break;
   1362             case ALIGN_RIGHT:
   1363                 resultAlign = Alignment.ALIGN_RIGHT;
   1364                 break;
   1365             default: /* align == Alignment.ALIGN_LEFT */
   1366                 resultAlign = Alignment.ALIGN_LEFT;
   1367         }
   1368 
   1369         switch (resultAlign) {
   1370             case ALIGN_CENTER:
   1371                 final int right = getParagraphRight(line);
   1372                 final float max = getLineMax(line);
   1373                 // This computation only works when mWidth equals leadingMargin plus width of the
   1374                 // text in this line. If this condition doesn't meet anymore, please change here.
   1375                 return (float) Math.ceil(right - (mWidth - max) / 2);
   1376             case ALIGN_RIGHT:
   1377                 return mWidth;
   1378             default: /* resultAlign == Alignment.ALIGN_LEFT */
   1379                 return getLineMax(line);
   1380         }
   1381     }
   1382 
   1383     /**
   1384      * Gets the unsigned horizontal extent of the specified line, including
   1385      * leading margin indent, but excluding trailing whitespace.
   1386      */
   1387     public float getLineMax(int line) {
   1388         float margin = getParagraphLeadingMargin(line);
   1389         float signedExtent = getLineExtent(line, false);
   1390         return margin + (signedExtent >= 0 ? signedExtent : -signedExtent);
   1391     }
   1392 
   1393     /**
   1394      * Gets the unsigned horizontal extent of the specified line, including
   1395      * leading margin indent and trailing whitespace.
   1396      */
   1397     public float getLineWidth(int line) {
   1398         float margin = getParagraphLeadingMargin(line);
   1399         float signedExtent = getLineExtent(line, true);
   1400         return margin + (signedExtent >= 0 ? signedExtent : -signedExtent);
   1401     }
   1402 
   1403     /**
   1404      * Like {@link #getLineExtent(int,TabStops,boolean)} but determines the
   1405      * tab stops instead of using the ones passed in.
   1406      * @param line the index of the line
   1407      * @param full whether to include trailing whitespace
   1408      * @return the extent of the line
   1409      */
   1410     private float getLineExtent(int line, boolean full) {
   1411         final int start = getLineStart(line);
   1412         final int end = full ? getLineEnd(line) : getLineVisibleEnd(line);
   1413 
   1414         final boolean hasTabs = getLineContainsTab(line);
   1415         TabStops tabStops = null;
   1416         if (hasTabs && mText instanceof Spanned) {
   1417             // Just checking this line should be good enough, tabs should be
   1418             // consistent across all lines in a paragraph.
   1419             TabStopSpan[] tabs = getParagraphSpans((Spanned) mText, start, end, TabStopSpan.class);
   1420             if (tabs.length > 0) {
   1421                 tabStops = new TabStops(TAB_INCREMENT, tabs); // XXX should reuse
   1422             }
   1423         }
   1424         final Directions directions = getLineDirections(line);
   1425         // Returned directions can actually be null
   1426         if (directions == null) {
   1427             return 0f;
   1428         }
   1429         final int dir = getParagraphDirection(line);
   1430 
   1431         final TextLine tl = TextLine.obtain();
   1432         final TextPaint paint = mWorkPaint;
   1433         paint.set(mPaint);
   1434         paint.setStartHyphenEdit(getStartHyphenEdit(line));
   1435         paint.setEndHyphenEdit(getEndHyphenEdit(line));
   1436         tl.set(paint, mText, start, end, dir, directions, hasTabs, tabStops,
   1437                 getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line));
   1438         if (isJustificationRequired(line)) {
   1439             tl.justify(getJustifyWidth(line));
   1440         }
   1441         final float width = tl.metrics(null);
   1442         TextLine.recycle(tl);
   1443         return width;
   1444     }
   1445 
   1446     /**
   1447      * Returns the signed horizontal extent of the specified line, excluding
   1448      * leading margin.  If full is false, excludes trailing whitespace.
   1449      * @param line the index of the line
   1450      * @param tabStops the tab stops, can be null if we know they're not used.
   1451      * @param full whether to include trailing whitespace
   1452      * @return the extent of the text on this line
   1453      */
   1454     private float getLineExtent(int line, TabStops tabStops, boolean full) {
   1455         final int start = getLineStart(line);
   1456         final int end = full ? getLineEnd(line) : getLineVisibleEnd(line);
   1457         final boolean hasTabs = getLineContainsTab(line);
   1458         final Directions directions = getLineDirections(line);
   1459         final int dir = getParagraphDirection(line);
   1460 
   1461         final TextLine tl = TextLine.obtain();
   1462         final TextPaint paint = mWorkPaint;
   1463         paint.set(mPaint);
   1464         paint.setStartHyphenEdit(getStartHyphenEdit(line));
   1465         paint.setEndHyphenEdit(getEndHyphenEdit(line));
   1466         tl.set(paint, mText, start, end, dir, directions, hasTabs, tabStops,
   1467                 getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line));
   1468         if (isJustificationRequired(line)) {
   1469             tl.justify(getJustifyWidth(line));
   1470         }
   1471         final float width = tl.metrics(null);
   1472         TextLine.recycle(tl);
   1473         return width;
   1474     }
   1475 
   1476     /**
   1477      * Get the line number corresponding to the specified vertical position.
   1478      * If you ask for a position above 0, you get 0; if you ask for a position
   1479      * below the bottom of the text, you get the last line.
   1480      */
   1481     // FIXME: It may be faster to do a linear search for layouts without many lines.
   1482     public int getLineForVertical(int vertical) {
   1483         int high = getLineCount(), low = -1, guess;
   1484 
   1485         while (high - low > 1) {
   1486             guess = (high + low) / 2;
   1487 
   1488             if (getLineTop(guess) > vertical)
   1489                 high = guess;
   1490             else
   1491                 low = guess;
   1492         }
   1493 
   1494         if (low < 0)
   1495             return 0;
   1496         else
   1497             return low;
   1498     }
   1499 
   1500     /**
   1501      * Get the line number on which the specified text offset appears.
   1502      * If you ask for a position before 0, you get 0; if you ask for a position
   1503      * beyond the end of the text, you get the last line.
   1504      */
   1505     public int getLineForOffset(int offset) {
   1506         int high = getLineCount(), low = -1, guess;
   1507 
   1508         while (high - low > 1) {
   1509             guess = (high + low) / 2;
   1510 
   1511             if (getLineStart(guess) > offset)
   1512                 high = guess;
   1513             else
   1514                 low = guess;
   1515         }
   1516 
   1517         if (low < 0) {
   1518             return 0;
   1519         } else {
   1520             return low;
   1521         }
   1522     }
   1523 
   1524     /**
   1525      * Get the character offset on the specified line whose position is
   1526      * closest to the specified horizontal position.
   1527      */
   1528     public int getOffsetForHorizontal(int line, float horiz) {
   1529         return getOffsetForHorizontal(line, horiz, true);
   1530     }
   1531 
   1532     /**
   1533      * Get the character offset on the specified line whose position is
   1534      * closest to the specified horizontal position.
   1535      *
   1536      * @param line the line used to find the closest offset
   1537      * @param horiz the horizontal position used to find the closest offset
   1538      * @param primary whether to use the primary position or secondary position to find the offset
   1539      *
   1540      * @hide
   1541      */
   1542     public int getOffsetForHorizontal(int line, float horiz, boolean primary) {
   1543         // TODO: use Paint.getOffsetForAdvance to avoid binary search
   1544         final int lineEndOffset = getLineEnd(line);
   1545         final int lineStartOffset = getLineStart(line);
   1546 
   1547         Directions dirs = getLineDirections(line);
   1548 
   1549         TextLine tl = TextLine.obtain();
   1550         // XXX: we don't care about tabs as we just use TextLine#getOffsetToLeftRightOf here.
   1551         tl.set(mPaint, mText, lineStartOffset, lineEndOffset, getParagraphDirection(line), dirs,
   1552                 false, null,
   1553                 getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line));
   1554         final HorizontalMeasurementProvider horizontal =
   1555                 new HorizontalMeasurementProvider(line, primary);
   1556 
   1557         final int max;
   1558         if (line == getLineCount() - 1) {
   1559             max = lineEndOffset;
   1560         } else {
   1561             max = tl.getOffsetToLeftRightOf(lineEndOffset - lineStartOffset,
   1562                     !isRtlCharAt(lineEndOffset - 1)) + lineStartOffset;
   1563         }
   1564         int best = lineStartOffset;
   1565         float bestdist = Math.abs(horizontal.get(lineStartOffset) - horiz);
   1566 
   1567         for (int i = 0; i < dirs.mDirections.length; i += 2) {
   1568             int here = lineStartOffset + dirs.mDirections[i];
   1569             int there = here + (dirs.mDirections[i+1] & RUN_LENGTH_MASK);
   1570             boolean isRtl = (dirs.mDirections[i+1] & RUN_RTL_FLAG) != 0;
   1571             int swap = isRtl ? -1 : 1;
   1572 
   1573             if (there > max)
   1574                 there = max;
   1575             int high = there - 1 + 1, low = here + 1 - 1, guess;
   1576 
   1577             while (high - low > 1) {
   1578                 guess = (high + low) / 2;
   1579                 int adguess = getOffsetAtStartOf(guess);
   1580 
   1581                 if (horizontal.get(adguess) * swap >= horiz * swap) {
   1582                     high = guess;
   1583                 } else {
   1584                     low = guess;
   1585                 }
   1586             }
   1587 
   1588             if (low < here + 1)
   1589                 low = here + 1;
   1590 
   1591             if (low < there) {
   1592                 int aft = tl.getOffsetToLeftRightOf(low - lineStartOffset, isRtl) + lineStartOffset;
   1593                 low = tl.getOffsetToLeftRightOf(aft - lineStartOffset, !isRtl) + lineStartOffset;
   1594                 if (low >= here && low < there) {
   1595                     float dist = Math.abs(horizontal.get(low) - horiz);
   1596                     if (aft < there) {
   1597                         float other = Math.abs(horizontal.get(aft) - horiz);
   1598 
   1599                         if (other < dist) {
   1600                             dist = other;
   1601                             low = aft;
   1602                         }
   1603                     }
   1604 
   1605                     if (dist < bestdist) {
   1606                         bestdist = dist;
   1607                         best = low;
   1608                     }
   1609                 }
   1610             }
   1611 
   1612             float dist = Math.abs(horizontal.get(here) - horiz);
   1613 
   1614             if (dist < bestdist) {
   1615                 bestdist = dist;
   1616                 best = here;
   1617             }
   1618         }
   1619 
   1620         float dist = Math.abs(horizontal.get(max) - horiz);
   1621 
   1622         if (dist <= bestdist) {
   1623             best = max;
   1624         }
   1625 
   1626         TextLine.recycle(tl);
   1627         return best;
   1628     }
   1629 
   1630     /**
   1631      * Responds to #getHorizontal queries, by selecting the better strategy between:
   1632      * - calling #getHorizontal explicitly for each query
   1633      * - precomputing all #getHorizontal measurements, and responding to any query in constant time
   1634      * The first strategy is used for LTR-only text, while the second is used for all other cases.
   1635      * The class is currently only used in #getOffsetForHorizontal, so reuse with care in other
   1636      * contexts.
   1637      */
   1638     private class HorizontalMeasurementProvider {
   1639         private final int mLine;
   1640         private final boolean mPrimary;
   1641 
   1642         private float[] mHorizontals;
   1643         private int mLineStartOffset;
   1644 
   1645         HorizontalMeasurementProvider(final int line, final boolean primary) {
   1646             mLine = line;
   1647             mPrimary = primary;
   1648             init();
   1649         }
   1650 
   1651         private void init() {
   1652             final Directions dirs = getLineDirections(mLine);
   1653             if (dirs == DIRS_ALL_LEFT_TO_RIGHT) {
   1654                 return;
   1655             }
   1656 
   1657             mHorizontals = getLineHorizontals(mLine, false, mPrimary);
   1658             mLineStartOffset = getLineStart(mLine);
   1659         }
   1660 
   1661         float get(final int offset) {
   1662             final int index = offset - mLineStartOffset;
   1663             if (mHorizontals == null || index < 0 || index >= mHorizontals.length) {
   1664                 return getHorizontal(offset, mPrimary);
   1665             } else {
   1666                 return mHorizontals[index];
   1667             }
   1668         }
   1669     }
   1670 
   1671     /**
   1672      * Return the text offset after the last character on the specified line.
   1673      */
   1674     public final int getLineEnd(int line) {
   1675         return getLineStart(line + 1);
   1676     }
   1677 
   1678     /**
   1679      * Return the text offset after the last visible character (so whitespace
   1680      * is not counted) on the specified line.
   1681      */
   1682     public int getLineVisibleEnd(int line) {
   1683         return getLineVisibleEnd(line, getLineStart(line), getLineStart(line+1));
   1684     }
   1685 
   1686     private int getLineVisibleEnd(int line, int start, int end) {
   1687         CharSequence text = mText;
   1688         char ch;
   1689         if (line == getLineCount() - 1) {
   1690             return end;
   1691         }
   1692 
   1693         for (; end > start; end--) {
   1694             ch = text.charAt(end - 1);
   1695 
   1696             if (ch == '\n') {
   1697                 return end - 1;
   1698             }
   1699 
   1700             if (!TextLine.isLineEndSpace(ch)) {
   1701                 break;
   1702             }
   1703 
   1704         }
   1705 
   1706         return end;
   1707     }
   1708 
   1709     /**
   1710      * Return the vertical position of the bottom of the specified line.
   1711      */
   1712     public final int getLineBottom(int line) {
   1713         return getLineTop(line + 1);
   1714     }
   1715 
   1716     /**
   1717      * Return the vertical position of the bottom of the specified line without the line spacing
   1718      * added.
   1719      *
   1720      * @hide
   1721      */
   1722     public final int getLineBottomWithoutSpacing(int line) {
   1723         return getLineTop(line + 1) - getLineExtra(line);
   1724     }
   1725 
   1726     /**
   1727      * Return the vertical position of the baseline of the specified line.
   1728      */
   1729     public final int getLineBaseline(int line) {
   1730         // getLineTop(line+1) == getLineBottom(line)
   1731         return getLineTop(line+1) - getLineDescent(line);
   1732     }
   1733 
   1734     /**
   1735      * Get the ascent of the text on the specified line.
   1736      * The return value is negative to match the Paint.ascent() convention.
   1737      */
   1738     public final int getLineAscent(int line) {
   1739         // getLineTop(line+1) - getLineDescent(line) == getLineBaseLine(line)
   1740         return getLineTop(line) - (getLineTop(line+1) - getLineDescent(line));
   1741     }
   1742 
   1743     /**
   1744      * Return the extra space added as a result of line spacing attributes
   1745      * {@link #getSpacingAdd()} and {@link #getSpacingMultiplier()}. Default value is {@code zero}.
   1746      *
   1747      * @param line the index of the line, the value should be equal or greater than {@code zero}
   1748      * @hide
   1749      */
   1750     public int getLineExtra(@IntRange(from = 0) int line) {
   1751         return 0;
   1752     }
   1753 
   1754     public int getOffsetToLeftOf(int offset) {
   1755         return getOffsetToLeftRightOf(offset, true);
   1756     }
   1757 
   1758     public int getOffsetToRightOf(int offset) {
   1759         return getOffsetToLeftRightOf(offset, false);
   1760     }
   1761 
   1762     private int getOffsetToLeftRightOf(int caret, boolean toLeft) {
   1763         int line = getLineForOffset(caret);
   1764         int lineStart = getLineStart(line);
   1765         int lineEnd = getLineEnd(line);
   1766         int lineDir = getParagraphDirection(line);
   1767 
   1768         boolean lineChanged = false;
   1769         boolean advance = toLeft == (lineDir == DIR_RIGHT_TO_LEFT);
   1770         // if walking off line, look at the line we're headed to
   1771         if (advance) {
   1772             if (caret == lineEnd) {
   1773                 if (line < getLineCount() - 1) {
   1774                     lineChanged = true;
   1775                     ++line;
   1776                 } else {
   1777                     return caret; // at very end, don't move
   1778                 }
   1779             }
   1780         } else {
   1781             if (caret == lineStart) {
   1782                 if (line > 0) {
   1783                     lineChanged = true;
   1784                     --line;
   1785                 } else {
   1786                     return caret; // at very start, don't move
   1787                 }
   1788             }
   1789         }
   1790 
   1791         if (lineChanged) {
   1792             lineStart = getLineStart(line);
   1793             lineEnd = getLineEnd(line);
   1794             int newDir = getParagraphDirection(line);
   1795             if (newDir != lineDir) {
   1796                 // unusual case.  we want to walk onto the line, but it runs
   1797                 // in a different direction than this one, so we fake movement
   1798                 // in the opposite direction.
   1799                 toLeft = !toLeft;
   1800                 lineDir = newDir;
   1801             }
   1802         }
   1803 
   1804         Directions directions = getLineDirections(line);
   1805 
   1806         TextLine tl = TextLine.obtain();
   1807         // XXX: we don't care about tabs
   1808         tl.set(mPaint, mText, lineStart, lineEnd, lineDir, directions, false, null,
   1809                 getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line));
   1810         caret = lineStart + tl.getOffsetToLeftRightOf(caret - lineStart, toLeft);
   1811         TextLine.recycle(tl);
   1812         return caret;
   1813     }
   1814 
   1815     private int getOffsetAtStartOf(int offset) {
   1816         // XXX this probably should skip local reorderings and
   1817         // zero-width characters, look at callers
   1818         if (offset == 0)
   1819             return 0;
   1820 
   1821         CharSequence text = mText;
   1822         char c = text.charAt(offset);
   1823 
   1824         if (c >= '\uDC00' && c <= '\uDFFF') {
   1825             char c1 = text.charAt(offset - 1);
   1826 
   1827             if (c1 >= '\uD800' && c1 <= '\uDBFF')
   1828                 offset -= 1;
   1829         }
   1830 
   1831         if (mSpannedText) {
   1832             ReplacementSpan[] spans = ((Spanned) text).getSpans(offset, offset,
   1833                                                        ReplacementSpan.class);
   1834 
   1835             for (int i = 0; i < spans.length; i++) {
   1836                 int start = ((Spanned) text).getSpanStart(spans[i]);
   1837                 int end = ((Spanned) text).getSpanEnd(spans[i]);
   1838 
   1839                 if (start < offset && end > offset)
   1840                     offset = start;
   1841             }
   1842         }
   1843 
   1844         return offset;
   1845     }
   1846 
   1847     /**
   1848      * Determine whether we should clamp cursor position. Currently it's
   1849      * only robust for left-aligned displays.
   1850      * @hide
   1851      */
   1852     @UnsupportedAppUsage
   1853     public boolean shouldClampCursor(int line) {
   1854         // Only clamp cursor position in left-aligned displays.
   1855         switch (getParagraphAlignment(line)) {
   1856             case ALIGN_LEFT:
   1857                 return true;
   1858             case ALIGN_NORMAL:
   1859                 return getParagraphDirection(line) > 0;
   1860             default:
   1861                 return false;
   1862         }
   1863 
   1864     }
   1865 
   1866     /**
   1867      * Fills in the specified Path with a representation of a cursor
   1868      * at the specified offset.  This will often be a vertical line
   1869      * but can be multiple discontinuous lines in text with multiple
   1870      * directionalities.
   1871      */
   1872     public void getCursorPath(final int point, final Path dest, final CharSequence editingBuffer) {
   1873         dest.reset();
   1874 
   1875         int line = getLineForOffset(point);
   1876         int top = getLineTop(line);
   1877         int bottom = getLineBottomWithoutSpacing(line);
   1878 
   1879         boolean clamped = shouldClampCursor(line);
   1880         float h1 = getPrimaryHorizontal(point, clamped) - 0.5f;
   1881 
   1882         int caps = TextKeyListener.getMetaState(editingBuffer, TextKeyListener.META_SHIFT_ON) |
   1883                    TextKeyListener.getMetaState(editingBuffer, TextKeyListener.META_SELECTING);
   1884         int fn = TextKeyListener.getMetaState(editingBuffer, TextKeyListener.META_ALT_ON);
   1885         int dist = 0;
   1886 
   1887         if (caps != 0 || fn != 0) {
   1888             dist = (bottom - top) >> 2;
   1889 
   1890             if (fn != 0)
   1891                 top += dist;
   1892             if (caps != 0)
   1893                 bottom -= dist;
   1894         }
   1895 
   1896         if (h1 < 0.5f)
   1897             h1 = 0.5f;
   1898 
   1899         dest.moveTo(h1, top);
   1900         dest.lineTo(h1, bottom);
   1901 
   1902         if (caps == 2) {
   1903             dest.moveTo(h1, bottom);
   1904             dest.lineTo(h1 - dist, bottom + dist);
   1905             dest.lineTo(h1, bottom);
   1906             dest.lineTo(h1 + dist, bottom + dist);
   1907         } else if (caps == 1) {
   1908             dest.moveTo(h1, bottom);
   1909             dest.lineTo(h1 - dist, bottom + dist);
   1910 
   1911             dest.moveTo(h1 - dist, bottom + dist - 0.5f);
   1912             dest.lineTo(h1 + dist, bottom + dist - 0.5f);
   1913 
   1914             dest.moveTo(h1 + dist, bottom + dist);
   1915             dest.lineTo(h1, bottom);
   1916         }
   1917 
   1918         if (fn == 2) {
   1919             dest.moveTo(h1, top);
   1920             dest.lineTo(h1 - dist, top - dist);
   1921             dest.lineTo(h1, top);
   1922             dest.lineTo(h1 + dist, top - dist);
   1923         } else if (fn == 1) {
   1924             dest.moveTo(h1, top);
   1925             dest.lineTo(h1 - dist, top - dist);
   1926 
   1927             dest.moveTo(h1 - dist, top - dist + 0.5f);
   1928             dest.lineTo(h1 + dist, top - dist + 0.5f);
   1929 
   1930             dest.moveTo(h1 + dist, top - dist);
   1931             dest.lineTo(h1, top);
   1932         }
   1933     }
   1934 
   1935     private void addSelection(int line, int start, int end,
   1936             int top, int bottom, SelectionRectangleConsumer consumer) {
   1937         int linestart = getLineStart(line);
   1938         int lineend = getLineEnd(line);
   1939         Directions dirs = getLineDirections(line);
   1940 
   1941         if (lineend > linestart && mText.charAt(lineend - 1) == '\n') {
   1942             lineend--;
   1943         }
   1944 
   1945         for (int i = 0; i < dirs.mDirections.length; i += 2) {
   1946             int here = linestart + dirs.mDirections[i];
   1947             int there = here + (dirs.mDirections[i + 1] & RUN_LENGTH_MASK);
   1948 
   1949             if (there > lineend) {
   1950                 there = lineend;
   1951             }
   1952 
   1953             if (start <= there && end >= here) {
   1954                 int st = Math.max(start, here);
   1955                 int en = Math.min(end, there);
   1956 
   1957                 if (st != en) {
   1958                     float h1 = getHorizontal(st, false, line, false /* not clamped */);
   1959                     float h2 = getHorizontal(en, true, line, false /* not clamped */);
   1960 
   1961                     float left = Math.min(h1, h2);
   1962                     float right = Math.max(h1, h2);
   1963 
   1964                     final @TextSelectionLayout int layout =
   1965                             ((dirs.mDirections[i + 1] & RUN_RTL_FLAG) != 0)
   1966                                     ? TEXT_SELECTION_LAYOUT_RIGHT_TO_LEFT
   1967                                     : TEXT_SELECTION_LAYOUT_LEFT_TO_RIGHT;
   1968 
   1969                     consumer.accept(left, top, right, bottom, layout);
   1970                 }
   1971             }
   1972         }
   1973     }
   1974 
   1975     /**
   1976      * Fills in the specified Path with a representation of a highlight
   1977      * between the specified offsets.  This will often be a rectangle
   1978      * or a potentially discontinuous set of rectangles.  If the start
   1979      * and end are the same, the returned path is empty.
   1980      */
   1981     public void getSelectionPath(int start, int end, Path dest) {
   1982         dest.reset();
   1983         getSelection(start, end, (left, top, right, bottom, textSelectionLayout) ->
   1984                 dest.addRect(left, top, right, bottom, Path.Direction.CW));
   1985     }
   1986 
   1987     /**
   1988      * Calculates the rectangles which should be highlighted to indicate a selection between start
   1989      * and end and feeds them into the given {@link SelectionRectangleConsumer}.
   1990      *
   1991      * @param start    the starting index of the selection
   1992      * @param end      the ending index of the selection
   1993      * @param consumer the {@link SelectionRectangleConsumer} which will receive the generated
   1994      *                 rectangles. It will be called every time a rectangle is generated.
   1995      * @hide
   1996      * @see #getSelectionPath(int, int, Path)
   1997      */
   1998     public final void getSelection(int start, int end, final SelectionRectangleConsumer consumer) {
   1999         if (start == end) {
   2000             return;
   2001         }
   2002 
   2003         if (end < start) {
   2004             int temp = end;
   2005             end = start;
   2006             start = temp;
   2007         }
   2008 
   2009         final int startline = getLineForOffset(start);
   2010         final int endline = getLineForOffset(end);
   2011 
   2012         int top = getLineTop(startline);
   2013         int bottom = getLineBottomWithoutSpacing(endline);
   2014 
   2015         if (startline == endline) {
   2016             addSelection(startline, start, end, top, bottom, consumer);
   2017         } else {
   2018             final float width = mWidth;
   2019 
   2020             addSelection(startline, start, getLineEnd(startline),
   2021                     top, getLineBottom(startline), consumer);
   2022 
   2023             if (getParagraphDirection(startline) == DIR_RIGHT_TO_LEFT) {
   2024                 consumer.accept(getLineLeft(startline), top, 0, getLineBottom(startline),
   2025                         TEXT_SELECTION_LAYOUT_RIGHT_TO_LEFT);
   2026             } else {
   2027                 consumer.accept(getLineRight(startline), top, width, getLineBottom(startline),
   2028                         TEXT_SELECTION_LAYOUT_LEFT_TO_RIGHT);
   2029             }
   2030 
   2031             for (int i = startline + 1; i < endline; i++) {
   2032                 top = getLineTop(i);
   2033                 bottom = getLineBottom(i);
   2034                 if (getParagraphDirection(i) == DIR_RIGHT_TO_LEFT) {
   2035                     consumer.accept(0, top, width, bottom, TEXT_SELECTION_LAYOUT_RIGHT_TO_LEFT);
   2036                 } else {
   2037                     consumer.accept(0, top, width, bottom, TEXT_SELECTION_LAYOUT_LEFT_TO_RIGHT);
   2038                 }
   2039             }
   2040 
   2041             top = getLineTop(endline);
   2042             bottom = getLineBottomWithoutSpacing(endline);
   2043 
   2044             addSelection(endline, getLineStart(endline), end, top, bottom, consumer);
   2045 
   2046             if (getParagraphDirection(endline) == DIR_RIGHT_TO_LEFT) {
   2047                 consumer.accept(width, top, getLineRight(endline), bottom,
   2048                         TEXT_SELECTION_LAYOUT_RIGHT_TO_LEFT);
   2049             } else {
   2050                 consumer.accept(0, top, getLineLeft(endline), bottom,
   2051                         TEXT_SELECTION_LAYOUT_LEFT_TO_RIGHT);
   2052             }
   2053         }
   2054     }
   2055 
   2056     /**
   2057      * Get the alignment of the specified paragraph, taking into account
   2058      * markup attached to it.
   2059      */
   2060     public final Alignment getParagraphAlignment(int line) {
   2061         Alignment align = mAlignment;
   2062 
   2063         if (mSpannedText) {
   2064             Spanned sp = (Spanned) mText;
   2065             AlignmentSpan[] spans = getParagraphSpans(sp, getLineStart(line),
   2066                                                 getLineEnd(line),
   2067                                                 AlignmentSpan.class);
   2068 
   2069             int spanLength = spans.length;
   2070             if (spanLength > 0) {
   2071                 align = spans[spanLength-1].getAlignment();
   2072             }
   2073         }
   2074 
   2075         return align;
   2076     }
   2077 
   2078     /**
   2079      * Get the left edge of the specified paragraph, inset by left margins.
   2080      */
   2081     public final int getParagraphLeft(int line) {
   2082         int left = 0;
   2083         int dir = getParagraphDirection(line);
   2084         if (dir == DIR_RIGHT_TO_LEFT || !mSpannedText) {
   2085             return left; // leading margin has no impact, or no styles
   2086         }
   2087         return getParagraphLeadingMargin(line);
   2088     }
   2089 
   2090     /**
   2091      * Get the right edge of the specified paragraph, inset by right margins.
   2092      */
   2093     public final int getParagraphRight(int line) {
   2094         int right = mWidth;
   2095         int dir = getParagraphDirection(line);
   2096         if (dir == DIR_LEFT_TO_RIGHT || !mSpannedText) {
   2097             return right; // leading margin has no impact, or no styles
   2098         }
   2099         return right - getParagraphLeadingMargin(line);
   2100     }
   2101 
   2102     /**
   2103      * Returns the effective leading margin (unsigned) for this line,
   2104      * taking into account LeadingMarginSpan and LeadingMarginSpan2.
   2105      * @param line the line index
   2106      * @return the leading margin of this line
   2107      */
   2108     private int getParagraphLeadingMargin(int line) {
   2109         if (!mSpannedText) {
   2110             return 0;
   2111         }
   2112         Spanned spanned = (Spanned) mText;
   2113 
   2114         int lineStart = getLineStart(line);
   2115         int lineEnd = getLineEnd(line);
   2116         int spanEnd = spanned.nextSpanTransition(lineStart, lineEnd,
   2117                 LeadingMarginSpan.class);
   2118         LeadingMarginSpan[] spans = getParagraphSpans(spanned, lineStart, spanEnd,
   2119                                                 LeadingMarginSpan.class);
   2120         if (spans.length == 0) {
   2121             return 0; // no leading margin span;
   2122         }
   2123 
   2124         int margin = 0;
   2125 
   2126         boolean useFirstLineMargin = lineStart == 0 || spanned.charAt(lineStart - 1) == '\n';
   2127         for (int i = 0; i < spans.length; i++) {
   2128             if (spans[i] instanceof LeadingMarginSpan2) {
   2129                 int spStart = spanned.getSpanStart(spans[i]);
   2130                 int spanLine = getLineForOffset(spStart);
   2131                 int count = ((LeadingMarginSpan2) spans[i]).getLeadingMarginLineCount();
   2132                 // if there is more than one LeadingMarginSpan2, use the count that is greatest
   2133                 useFirstLineMargin |= line < spanLine + count;
   2134             }
   2135         }
   2136         for (int i = 0; i < spans.length; i++) {
   2137             LeadingMarginSpan span = spans[i];
   2138             margin += span.getLeadingMargin(useFirstLineMargin);
   2139         }
   2140 
   2141         return margin;
   2142     }
   2143 
   2144     private static float measurePara(TextPaint paint, CharSequence text, int start, int end,
   2145             TextDirectionHeuristic textDir) {
   2146         MeasuredParagraph mt = null;
   2147         TextLine tl = TextLine.obtain();
   2148         try {
   2149             mt = MeasuredParagraph.buildForBidi(text, start, end, textDir, mt);
   2150             final char[] chars = mt.getChars();
   2151             final int len = chars.length;
   2152             final Directions directions = mt.getDirections(0, len);
   2153             final int dir = mt.getParagraphDir();
   2154             boolean hasTabs = false;
   2155             TabStops tabStops = null;
   2156             // leading margins should be taken into account when measuring a paragraph
   2157             int margin = 0;
   2158             if (text instanceof Spanned) {
   2159                 Spanned spanned = (Spanned) text;
   2160                 LeadingMarginSpan[] spans = getParagraphSpans(spanned, start, end,
   2161                         LeadingMarginSpan.class);
   2162                 for (LeadingMarginSpan lms : spans) {
   2163                     margin += lms.getLeadingMargin(true);
   2164                 }
   2165             }
   2166             for (int i = 0; i < len; ++i) {
   2167                 if (chars[i] == '\t') {
   2168                     hasTabs = true;
   2169                     if (text instanceof Spanned) {
   2170                         Spanned spanned = (Spanned) text;
   2171                         int spanEnd = spanned.nextSpanTransition(start, end,
   2172                                 TabStopSpan.class);
   2173                         TabStopSpan[] spans = getParagraphSpans(spanned, start, spanEnd,
   2174                                 TabStopSpan.class);
   2175                         if (spans.length > 0) {
   2176                             tabStops = new TabStops(TAB_INCREMENT, spans);
   2177                         }
   2178                     }
   2179                     break;
   2180                 }
   2181             }
   2182             tl.set(paint, text, start, end, dir, directions, hasTabs, tabStops,
   2183                     0 /* ellipsisStart */, 0 /* ellipsisEnd */);
   2184             return margin + Math.abs(tl.metrics(null));
   2185         } finally {
   2186             TextLine.recycle(tl);
   2187             if (mt != null) {
   2188                 mt.recycle();
   2189             }
   2190         }
   2191     }
   2192 
   2193     /**
   2194      * @hide
   2195      */
   2196     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
   2197     public static class TabStops {
   2198         private float[] mStops;
   2199         private int mNumStops;
   2200         private float mIncrement;
   2201 
   2202         public TabStops(float increment, Object[] spans) {
   2203             reset(increment, spans);
   2204         }
   2205 
   2206         void reset(float increment, Object[] spans) {
   2207             this.mIncrement = increment;
   2208 
   2209             int ns = 0;
   2210             if (spans != null) {
   2211                 float[] stops = this.mStops;
   2212                 for (Object o : spans) {
   2213                     if (o instanceof TabStopSpan) {
   2214                         if (stops == null) {
   2215                             stops = new float[10];
   2216                         } else if (ns == stops.length) {
   2217                             float[] nstops = new float[ns * 2];
   2218                             for (int i = 0; i < ns; ++i) {
   2219                                 nstops[i] = stops[i];
   2220                             }
   2221                             stops = nstops;
   2222                         }
   2223                         stops[ns++] = ((TabStopSpan) o).getTabStop();
   2224                     }
   2225                 }
   2226                 if (ns > 1) {
   2227                     Arrays.sort(stops, 0, ns);
   2228                 }
   2229                 if (stops != this.mStops) {
   2230                     this.mStops = stops;
   2231                 }
   2232             }
   2233             this.mNumStops = ns;
   2234         }
   2235 
   2236         float nextTab(float h) {
   2237             int ns = this.mNumStops;
   2238             if (ns > 0) {
   2239                 float[] stops = this.mStops;
   2240                 for (int i = 0; i < ns; ++i) {
   2241                     float stop = stops[i];
   2242                     if (stop > h) {
   2243                         return stop;
   2244                     }
   2245                 }
   2246             }
   2247             return nextDefaultStop(h, mIncrement);
   2248         }
   2249 
   2250         /**
   2251          * Returns the position of next tab stop.
   2252          */
   2253         public static float nextDefaultStop(float h, float inc) {
   2254             return ((int) ((h + inc) / inc)) * inc;
   2255         }
   2256     }
   2257 
   2258     /**
   2259      * Returns the position of the next tab stop after h on the line.
   2260      *
   2261      * @param text the text
   2262      * @param start start of the line
   2263      * @param end limit of the line
   2264      * @param h the current horizontal offset
   2265      * @param tabs the tabs, can be null.  If it is null, any tabs in effect
   2266      * on the line will be used.  If there are no tabs, a default offset
   2267      * will be used to compute the tab stop.
   2268      * @return the offset of the next tab stop.
   2269      */
   2270     /* package */ static float nextTab(CharSequence text, int start, int end,
   2271                                        float h, Object[] tabs) {
   2272         float nh = Float.MAX_VALUE;
   2273         boolean alltabs = false;
   2274 
   2275         if (text instanceof Spanned) {
   2276             if (tabs == null) {
   2277                 tabs = getParagraphSpans((Spanned) text, start, end, TabStopSpan.class);
   2278                 alltabs = true;
   2279             }
   2280 
   2281             for (int i = 0; i < tabs.length; i++) {
   2282                 if (!alltabs) {
   2283                     if (!(tabs[i] instanceof TabStopSpan))
   2284                         continue;
   2285                 }
   2286 
   2287                 int where = ((TabStopSpan) tabs[i]).getTabStop();
   2288 
   2289                 if (where < nh && where > h)
   2290                     nh = where;
   2291             }
   2292 
   2293             if (nh != Float.MAX_VALUE)
   2294                 return nh;
   2295         }
   2296 
   2297         return ((int) ((h + TAB_INCREMENT) / TAB_INCREMENT)) * TAB_INCREMENT;
   2298     }
   2299 
   2300     protected final boolean isSpanned() {
   2301         return mSpannedText;
   2302     }
   2303 
   2304     /**
   2305      * Returns the same as <code>text.getSpans()</code>, except where
   2306      * <code>start</code> and <code>end</code> are the same and are not
   2307      * at the very beginning of the text, in which case an empty array
   2308      * is returned instead.
   2309      * <p>
   2310      * This is needed because of the special case that <code>getSpans()</code>
   2311      * on an empty range returns the spans adjacent to that range, which is
   2312      * primarily for the sake of <code>TextWatchers</code> so they will get
   2313      * notifications when text goes from empty to non-empty.  But it also
   2314      * has the unfortunate side effect that if the text ends with an empty
   2315      * paragraph, that paragraph accidentally picks up the styles of the
   2316      * preceding paragraph (even though those styles will not be picked up
   2317      * by new text that is inserted into the empty paragraph).
   2318      * <p>
   2319      * The reason it just checks whether <code>start</code> and <code>end</code>
   2320      * is the same is that the only time a line can contain 0 characters
   2321      * is if it is the final paragraph of the Layout; otherwise any line will
   2322      * contain at least one printing or newline character.  The reason for the
   2323      * additional check if <code>start</code> is greater than 0 is that
   2324      * if the empty paragraph is the entire content of the buffer, paragraph
   2325      * styles that are already applied to the buffer will apply to text that
   2326      * is inserted into it.
   2327      */
   2328     /* package */static <T> T[] getParagraphSpans(Spanned text, int start, int end, Class<T> type) {
   2329         if (start == end && start > 0) {
   2330             return ArrayUtils.emptyArray(type);
   2331         }
   2332 
   2333         if(text instanceof SpannableStringBuilder) {
   2334             return ((SpannableStringBuilder) text).getSpans(start, end, type, false);
   2335         } else {
   2336             return text.getSpans(start, end, type);
   2337         }
   2338     }
   2339 
   2340     private void ellipsize(int start, int end, int line,
   2341                            char[] dest, int destoff, TextUtils.TruncateAt method) {
   2342         final int ellipsisCount = getEllipsisCount(line);
   2343         if (ellipsisCount == 0) {
   2344             return;
   2345         }
   2346         final int ellipsisStart = getEllipsisStart(line);
   2347         final int lineStart = getLineStart(line);
   2348 
   2349         final String ellipsisString = TextUtils.getEllipsisString(method);
   2350         final int ellipsisStringLen = ellipsisString.length();
   2351         // Use the ellipsis string only if there are that at least as many characters to replace.
   2352         final boolean useEllipsisString = ellipsisCount >= ellipsisStringLen;
   2353         for (int i = 0; i < ellipsisCount; i++) {
   2354             final char c;
   2355             if (useEllipsisString && i < ellipsisStringLen) {
   2356                 c = ellipsisString.charAt(i);
   2357             } else {
   2358                 c = TextUtils.ELLIPSIS_FILLER;
   2359             }
   2360 
   2361             final int a = i + ellipsisStart + lineStart;
   2362             if (start <= a && a < end) {
   2363                 dest[destoff + a - start] = c;
   2364             }
   2365         }
   2366     }
   2367 
   2368     /**
   2369      * Stores information about bidirectional (left-to-right or right-to-left)
   2370      * text within the layout of a line.
   2371      */
   2372     public static class Directions {
   2373         /**
   2374          * Directions represents directional runs within a line of text. Runs are pairs of ints
   2375          * listed in visual order, starting from the leading margin.  The first int of each pair is
   2376          * the offset from the first character of the line to the start of the run.  The second int
   2377          * represents both the length and level of the run. The length is in the lower bits,
   2378          * accessed by masking with RUN_LENGTH_MASK.  The level is in the higher bits, accessed by
   2379          * shifting by RUN_LEVEL_SHIFT and masking by RUN_LEVEL_MASK. To simply test for an RTL
   2380          * direction, test the bit using RUN_RTL_FLAG, if set then the direction is rtl.
   2381          * @hide
   2382          */
   2383         @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
   2384         public int[] mDirections;
   2385 
   2386         /**
   2387          * @hide
   2388          */
   2389         @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
   2390         public Directions(int[] dirs) {
   2391             mDirections = dirs;
   2392         }
   2393 
   2394         /**
   2395          * Returns number of BiDi runs.
   2396          *
   2397          * @hide
   2398          */
   2399         public @IntRange(from = 0) int getRunCount() {
   2400             return mDirections.length / 2;
   2401         }
   2402 
   2403         /**
   2404          * Returns the start offset of the BiDi run.
   2405          *
   2406          * @param runIndex the index of the BiDi run
   2407          * @return the start offset of the BiDi run.
   2408          * @hide
   2409          */
   2410         public @IntRange(from = 0) int getRunStart(@IntRange(from = 0) int runIndex) {
   2411             return mDirections[runIndex * 2];
   2412         }
   2413 
   2414         /**
   2415          * Returns the length of the BiDi run.
   2416          *
   2417          * Note that this method may return too large number due to reducing the number of object
   2418          * allocations. The too large number means the remaining part is assigned to this run. The
   2419          * caller must clamp the returned value.
   2420          *
   2421          * @param runIndex the index of the BiDi run
   2422          * @return the length of the BiDi run.
   2423          * @hide
   2424          */
   2425         public @IntRange(from = 0) int getRunLength(@IntRange(from = 0) int runIndex) {
   2426             return mDirections[runIndex * 2 + 1] & RUN_LENGTH_MASK;
   2427         }
   2428 
   2429         /**
   2430          * Returns true if the BiDi run is RTL.
   2431          *
   2432          * @param runIndex the index of the BiDi run
   2433          * @return true if the BiDi run is RTL.
   2434          * @hide
   2435          */
   2436         public boolean isRunRtl(int runIndex) {
   2437             return (mDirections[runIndex * 2 + 1] & RUN_RTL_FLAG) != 0;
   2438         }
   2439     }
   2440 
   2441     /**
   2442      * Return the offset of the first character to be ellipsized away,
   2443      * relative to the start of the line.  (So 0 if the beginning of the
   2444      * line is ellipsized, not getLineStart().)
   2445      */
   2446     public abstract int getEllipsisStart(int line);
   2447 
   2448     /**
   2449      * Returns the number of characters to be ellipsized away, or 0 if
   2450      * no ellipsis is to take place.
   2451      */
   2452     public abstract int getEllipsisCount(int line);
   2453 
   2454     /* package */ static class Ellipsizer implements CharSequence, GetChars {
   2455         /* package */ CharSequence mText;
   2456         /* package */ Layout mLayout;
   2457         /* package */ int mWidth;
   2458         /* package */ TextUtils.TruncateAt mMethod;
   2459 
   2460         public Ellipsizer(CharSequence s) {
   2461             mText = s;
   2462         }
   2463 
   2464         public char charAt(int off) {
   2465             char[] buf = TextUtils.obtain(1);
   2466             getChars(off, off + 1, buf, 0);
   2467             char ret = buf[0];
   2468 
   2469             TextUtils.recycle(buf);
   2470             return ret;
   2471         }
   2472 
   2473         public void getChars(int start, int end, char[] dest, int destoff) {
   2474             int line1 = mLayout.getLineForOffset(start);
   2475             int line2 = mLayout.getLineForOffset(end);
   2476 
   2477             TextUtils.getChars(mText, start, end, dest, destoff);
   2478 
   2479             for (int i = line1; i <= line2; i++) {
   2480                 mLayout.ellipsize(start, end, i, dest, destoff, mMethod);
   2481             }
   2482         }
   2483 
   2484         public int length() {
   2485             return mText.length();
   2486         }
   2487 
   2488         public CharSequence subSequence(int start, int end) {
   2489             char[] s = new char[end - start];
   2490             getChars(start, end, s, 0);
   2491             return new String(s);
   2492         }
   2493 
   2494         @Override
   2495         public String toString() {
   2496             char[] s = new char[length()];
   2497             getChars(0, length(), s, 0);
   2498             return new String(s);
   2499         }
   2500 
   2501     }
   2502 
   2503     /* package */ static class SpannedEllipsizer extends Ellipsizer implements Spanned {
   2504         private Spanned mSpanned;
   2505 
   2506         public SpannedEllipsizer(CharSequence display) {
   2507             super(display);
   2508             mSpanned = (Spanned) display;
   2509         }
   2510 
   2511         public <T> T[] getSpans(int start, int end, Class<T> type) {
   2512             return mSpanned.getSpans(start, end, type);
   2513         }
   2514 
   2515         public int getSpanStart(Object tag) {
   2516             return mSpanned.getSpanStart(tag);
   2517         }
   2518 
   2519         public int getSpanEnd(Object tag) {
   2520             return mSpanned.getSpanEnd(tag);
   2521         }
   2522 
   2523         public int getSpanFlags(Object tag) {
   2524             return mSpanned.getSpanFlags(tag);
   2525         }
   2526 
   2527         @SuppressWarnings("rawtypes")
   2528         public int nextSpanTransition(int start, int limit, Class type) {
   2529             return mSpanned.nextSpanTransition(start, limit, type);
   2530         }
   2531 
   2532         @Override
   2533         public CharSequence subSequence(int start, int end) {
   2534             char[] s = new char[end - start];
   2535             getChars(start, end, s, 0);
   2536 
   2537             SpannableString ss = new SpannableString(new String(s));
   2538             TextUtils.copySpansFrom(mSpanned, start, end, Object.class, ss, 0);
   2539             return ss;
   2540         }
   2541     }
   2542 
   2543     private CharSequence mText;
   2544     @UnsupportedAppUsage
   2545     private TextPaint mPaint;
   2546     private TextPaint mWorkPaint = new TextPaint();
   2547     private int mWidth;
   2548     private Alignment mAlignment = Alignment.ALIGN_NORMAL;
   2549     private float mSpacingMult;
   2550     private float mSpacingAdd;
   2551     private static final Rect sTempRect = new Rect();
   2552     private boolean mSpannedText;
   2553     private TextDirectionHeuristic mTextDir;
   2554     private SpanSet<LineBackgroundSpan> mLineBackgroundSpans;
   2555     private int mJustificationMode;
   2556 
   2557     /** @hide */
   2558     @IntDef(prefix = { "DIR_" }, value = {
   2559             DIR_LEFT_TO_RIGHT,
   2560             DIR_RIGHT_TO_LEFT
   2561     })
   2562     @Retention(RetentionPolicy.SOURCE)
   2563     public @interface Direction {}
   2564 
   2565     public static final int DIR_LEFT_TO_RIGHT = 1;
   2566     public static final int DIR_RIGHT_TO_LEFT = -1;
   2567 
   2568     /* package */ static final int DIR_REQUEST_LTR = 1;
   2569     /* package */ static final int DIR_REQUEST_RTL = -1;
   2570     @UnsupportedAppUsage
   2571     /* package */ static final int DIR_REQUEST_DEFAULT_LTR = 2;
   2572     /* package */ static final int DIR_REQUEST_DEFAULT_RTL = -2;
   2573 
   2574     /* package */ static final int RUN_LENGTH_MASK = 0x03ffffff;
   2575     /* package */ static final int RUN_LEVEL_SHIFT = 26;
   2576     /* package */ static final int RUN_LEVEL_MASK = 0x3f;
   2577     /* package */ static final int RUN_RTL_FLAG = 1 << RUN_LEVEL_SHIFT;
   2578 
   2579     public enum Alignment {
   2580         ALIGN_NORMAL,
   2581         ALIGN_OPPOSITE,
   2582         ALIGN_CENTER,
   2583         /** @hide */
   2584         @UnsupportedAppUsage
   2585         ALIGN_LEFT,
   2586         /** @hide */
   2587         @UnsupportedAppUsage
   2588         ALIGN_RIGHT,
   2589     }
   2590 
   2591     private static final float TAB_INCREMENT = 20;
   2592 
   2593     /** @hide */
   2594     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
   2595     @UnsupportedAppUsage
   2596     public static final Directions DIRS_ALL_LEFT_TO_RIGHT =
   2597         new Directions(new int[] { 0, RUN_LENGTH_MASK });
   2598 
   2599     /** @hide */
   2600     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
   2601     @UnsupportedAppUsage
   2602     public static final Directions DIRS_ALL_RIGHT_TO_LEFT =
   2603         new Directions(new int[] { 0, RUN_LENGTH_MASK | RUN_RTL_FLAG });
   2604 
   2605     /** @hide */
   2606     @Retention(RetentionPolicy.SOURCE)
   2607     @IntDef(prefix = { "TEXT_SELECTION_LAYOUT_" }, value = {
   2608             TEXT_SELECTION_LAYOUT_RIGHT_TO_LEFT,
   2609             TEXT_SELECTION_LAYOUT_LEFT_TO_RIGHT
   2610     })
   2611     public @interface TextSelectionLayout {}
   2612 
   2613     /** @hide */
   2614     public static final int TEXT_SELECTION_LAYOUT_RIGHT_TO_LEFT = 0;
   2615     /** @hide */
   2616     public static final int TEXT_SELECTION_LAYOUT_LEFT_TO_RIGHT = 1;
   2617 
   2618     /** @hide */
   2619     @FunctionalInterface
   2620     public interface SelectionRectangleConsumer {
   2621         /**
   2622          * Performs this operation on the given rectangle.
   2623          *
   2624          * @param left   the left edge of the rectangle
   2625          * @param top    the top edge of the rectangle
   2626          * @param right  the right edge of the rectangle
   2627          * @param bottom the bottom edge of the rectangle
   2628          * @param textSelectionLayout the layout (RTL or LTR) of the text covered by this
   2629          *                            selection rectangle
   2630          */
   2631         void accept(float left, float top, float right, float bottom,
   2632                 @TextSelectionLayout int textSelectionLayout);
   2633     }
   2634 
   2635 }
   2636