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.FloatRange;
     20 import android.annotation.IntRange;
     21 import android.annotation.NonNull;
     22 import android.annotation.Nullable;
     23 import android.graphics.Paint;
     24 import android.text.AutoGrowArray.FloatArray;
     25 import android.text.style.LeadingMarginSpan;
     26 import android.text.style.LeadingMarginSpan.LeadingMarginSpan2;
     27 import android.text.style.LineHeightSpan;
     28 import android.text.style.TabStopSpan;
     29 import android.util.Log;
     30 import android.util.Pools.SynchronizedPool;
     31 
     32 import com.android.internal.util.ArrayUtils;
     33 import com.android.internal.util.GrowingArrayUtils;
     34 
     35 import dalvik.annotation.optimization.CriticalNative;
     36 import dalvik.annotation.optimization.FastNative;
     37 
     38 import java.util.Arrays;
     39 
     40 /**
     41  * StaticLayout is a Layout for text that will not be edited after it
     42  * is laid out.  Use {@link DynamicLayout} for text that may change.
     43  * <p>This is used by widgets to control text layout. You should not need
     44  * to use this class directly unless you are implementing your own widget
     45  * or custom display object, or would be tempted to call
     46  * {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int,
     47  * float, float, android.graphics.Paint)
     48  * Canvas.drawText()} directly.</p>
     49  */
     50 public class StaticLayout extends Layout {
     51     /*
     52      * The break iteration is done in native code. The protocol for using the native code is as
     53      * follows.
     54      *
     55      * First, call nInit to setup native line breaker object. Then, for each paragraph, do the
     56      * following:
     57      *
     58      *   - Create MeasuredParagraph by MeasuredParagraph.buildForStaticLayout which measures in
     59      *     native.
     60      *   - Run nComputeLineBreaks() to obtain line breaks for the paragraph.
     61      *
     62      * After all paragraphs, call finish() to release expensive buffers.
     63      */
     64 
     65     static final String TAG = "StaticLayout";
     66 
     67     /**
     68      * Builder for static layouts. The builder is the preferred pattern for constructing
     69      * StaticLayout objects and should be preferred over the constructors, particularly to access
     70      * newer features. To build a static layout, first call {@link #obtain} with the required
     71      * arguments (text, paint, and width), then call setters for optional parameters, and finally
     72      * {@link #build} to build the StaticLayout object. Parameters not explicitly set will get
     73      * default values.
     74      */
     75     public final static class Builder {
     76         private Builder() {}
     77 
     78         /**
     79          * Obtain a builder for constructing StaticLayout objects.
     80          *
     81          * @param source The text to be laid out, optionally with spans
     82          * @param start The index of the start of the text
     83          * @param end The index + 1 of the end of the text
     84          * @param paint The base paint used for layout
     85          * @param width The width in pixels
     86          * @return a builder object used for constructing the StaticLayout
     87          */
     88         @NonNull
     89         public static Builder obtain(@NonNull CharSequence source, @IntRange(from = 0) int start,
     90                 @IntRange(from = 0) int end, @NonNull TextPaint paint,
     91                 @IntRange(from = 0) int width) {
     92             Builder b = sPool.acquire();
     93             if (b == null) {
     94                 b = new Builder();
     95             }
     96 
     97             // set default initial values
     98             b.mText = source;
     99             b.mStart = start;
    100             b.mEnd = end;
    101             b.mPaint = paint;
    102             b.mWidth = width;
    103             b.mAlignment = Alignment.ALIGN_NORMAL;
    104             b.mTextDir = TextDirectionHeuristics.FIRSTSTRONG_LTR;
    105             b.mSpacingMult = DEFAULT_LINESPACING_MULTIPLIER;
    106             b.mSpacingAdd = DEFAULT_LINESPACING_ADDITION;
    107             b.mIncludePad = true;
    108             b.mFallbackLineSpacing = false;
    109             b.mEllipsizedWidth = width;
    110             b.mEllipsize = null;
    111             b.mMaxLines = Integer.MAX_VALUE;
    112             b.mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
    113             b.mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
    114             b.mJustificationMode = Layout.JUSTIFICATION_MODE_NONE;
    115             return b;
    116         }
    117 
    118         /**
    119          * This method should be called after the layout is finished getting constructed and the
    120          * builder needs to be cleaned up and returned to the pool.
    121          */
    122         private static void recycle(@NonNull Builder b) {
    123             b.mPaint = null;
    124             b.mText = null;
    125             b.mLeftIndents = null;
    126             b.mRightIndents = null;
    127             b.mLeftPaddings = null;
    128             b.mRightPaddings = null;
    129             sPool.release(b);
    130         }
    131 
    132         // release any expensive state
    133         /* package */ void finish() {
    134             mText = null;
    135             mPaint = null;
    136             mLeftIndents = null;
    137             mRightIndents = null;
    138             mLeftPaddings = null;
    139             mRightPaddings = null;
    140         }
    141 
    142         public Builder setText(CharSequence source) {
    143             return setText(source, 0, source.length());
    144         }
    145 
    146         /**
    147          * Set the text. Only useful when re-using the builder, which is done for
    148          * the internal implementation of {@link DynamicLayout} but not as part
    149          * of normal {@link StaticLayout} usage.
    150          *
    151          * @param source The text to be laid out, optionally with spans
    152          * @param start The index of the start of the text
    153          * @param end The index + 1 of the end of the text
    154          * @return this builder, useful for chaining
    155          *
    156          * @hide
    157          */
    158         @NonNull
    159         public Builder setText(@NonNull CharSequence source, int start, int end) {
    160             mText = source;
    161             mStart = start;
    162             mEnd = end;
    163             return this;
    164         }
    165 
    166         /**
    167          * Set the paint. Internal for reuse cases only.
    168          *
    169          * @param paint The base paint used for layout
    170          * @return this builder, useful for chaining
    171          *
    172          * @hide
    173          */
    174         @NonNull
    175         public Builder setPaint(@NonNull TextPaint paint) {
    176             mPaint = paint;
    177             return this;
    178         }
    179 
    180         /**
    181          * Set the width. Internal for reuse cases only.
    182          *
    183          * @param width The width in pixels
    184          * @return this builder, useful for chaining
    185          *
    186          * @hide
    187          */
    188         @NonNull
    189         public Builder setWidth(@IntRange(from = 0) int width) {
    190             mWidth = width;
    191             if (mEllipsize == null) {
    192                 mEllipsizedWidth = width;
    193             }
    194             return this;
    195         }
    196 
    197         /**
    198          * Set the alignment. The default is {@link Layout.Alignment#ALIGN_NORMAL}.
    199          *
    200          * @param alignment Alignment for the resulting {@link StaticLayout}
    201          * @return this builder, useful for chaining
    202          */
    203         @NonNull
    204         public Builder setAlignment(@NonNull Alignment alignment) {
    205             mAlignment = alignment;
    206             return this;
    207         }
    208 
    209         /**
    210          * Set the text direction heuristic. The text direction heuristic is used to
    211          * resolve text direction per-paragraph based on the input text. The default is
    212          * {@link TextDirectionHeuristics#FIRSTSTRONG_LTR}.
    213          *
    214          * @param textDir text direction heuristic for resolving bidi behavior.
    215          * @return this builder, useful for chaining
    216          */
    217         @NonNull
    218         public Builder setTextDirection(@NonNull TextDirectionHeuristic textDir) {
    219             mTextDir = textDir;
    220             return this;
    221         }
    222 
    223         /**
    224          * Set line spacing parameters. Each line will have its line spacing multiplied by
    225          * {@code spacingMult} and then increased by {@code spacingAdd}. The default is 0.0 for
    226          * {@code spacingAdd} and 1.0 for {@code spacingMult}.
    227          *
    228          * @param spacingAdd the amount of line spacing addition
    229          * @param spacingMult the line spacing multiplier
    230          * @return this builder, useful for chaining
    231          * @see android.widget.TextView#setLineSpacing
    232          */
    233         @NonNull
    234         public Builder setLineSpacing(float spacingAdd, @FloatRange(from = 0.0) float spacingMult) {
    235             mSpacingAdd = spacingAdd;
    236             mSpacingMult = spacingMult;
    237             return this;
    238         }
    239 
    240         /**
    241          * Set whether to include extra space beyond font ascent and descent (which is
    242          * needed to avoid clipping in some languages, such as Arabic and Kannada). The
    243          * default is {@code true}.
    244          *
    245          * @param includePad whether to include padding
    246          * @return this builder, useful for chaining
    247          * @see android.widget.TextView#setIncludeFontPadding
    248          */
    249         @NonNull
    250         public Builder setIncludePad(boolean includePad) {
    251             mIncludePad = includePad;
    252             return this;
    253         }
    254 
    255         /**
    256          * Set whether to respect the ascent and descent of the fallback fonts that are used in
    257          * displaying the text (which is needed to avoid text from consecutive lines running into
    258          * each other). If set, fallback fonts that end up getting used can increase the ascent
    259          * and descent of the lines that they are used on.
    260          *
    261          * <p>For backward compatibility reasons, the default is {@code false}, but setting this to
    262          * true is strongly recommended. It is required to be true if text could be in languages
    263          * like Burmese or Tibetan where text is typically much taller or deeper than Latin text.
    264          *
    265          * @param useLineSpacingFromFallbacks whether to expand linespacing based on fallback fonts
    266          * @return this builder, useful for chaining
    267          */
    268         @NonNull
    269         public Builder setUseLineSpacingFromFallbacks(boolean useLineSpacingFromFallbacks) {
    270             mFallbackLineSpacing = useLineSpacingFromFallbacks;
    271             return this;
    272         }
    273 
    274         /**
    275          * Set the width as used for ellipsizing purposes, if it differs from the
    276          * normal layout width. The default is the {@code width}
    277          * passed to {@link #obtain}.
    278          *
    279          * @param ellipsizedWidth width used for ellipsizing, in pixels
    280          * @return this builder, useful for chaining
    281          * @see android.widget.TextView#setEllipsize
    282          */
    283         @NonNull
    284         public Builder setEllipsizedWidth(@IntRange(from = 0) int ellipsizedWidth) {
    285             mEllipsizedWidth = ellipsizedWidth;
    286             return this;
    287         }
    288 
    289         /**
    290          * Set ellipsizing on the layout. Causes words that are longer than the view
    291          * is wide, or exceeding the number of lines (see #setMaxLines) in the case
    292          * of {@link android.text.TextUtils.TruncateAt#END} or
    293          * {@link android.text.TextUtils.TruncateAt#MARQUEE}, to be ellipsized instead
    294          * of broken. The default is {@code null}, indicating no ellipsis is to be applied.
    295          *
    296          * @param ellipsize type of ellipsis behavior
    297          * @return this builder, useful for chaining
    298          * @see android.widget.TextView#setEllipsize
    299          */
    300         @NonNull
    301         public Builder setEllipsize(@Nullable TextUtils.TruncateAt ellipsize) {
    302             mEllipsize = ellipsize;
    303             return this;
    304         }
    305 
    306         /**
    307          * Set maximum number of lines. This is particularly useful in the case of
    308          * ellipsizing, where it changes the layout of the last line. The default is
    309          * unlimited.
    310          *
    311          * @param maxLines maximum number of lines in the layout
    312          * @return this builder, useful for chaining
    313          * @see android.widget.TextView#setMaxLines
    314          */
    315         @NonNull
    316         public Builder setMaxLines(@IntRange(from = 0) int maxLines) {
    317             mMaxLines = maxLines;
    318             return this;
    319         }
    320 
    321         /**
    322          * Set break strategy, useful for selecting high quality or balanced paragraph
    323          * layout options. The default is {@link Layout#BREAK_STRATEGY_SIMPLE}.
    324          *
    325          * @param breakStrategy break strategy for paragraph layout
    326          * @return this builder, useful for chaining
    327          * @see android.widget.TextView#setBreakStrategy
    328          */
    329         @NonNull
    330         public Builder setBreakStrategy(@BreakStrategy int breakStrategy) {
    331             mBreakStrategy = breakStrategy;
    332             return this;
    333         }
    334 
    335         /**
    336          * Set hyphenation frequency, to control the amount of automatic hyphenation used. The
    337          * possible values are defined in {@link Layout}, by constants named with the pattern
    338          * {@code HYPHENATION_FREQUENCY_*}. The default is
    339          * {@link Layout#HYPHENATION_FREQUENCY_NONE}.
    340          *
    341          * @param hyphenationFrequency hyphenation frequency for the paragraph
    342          * @return this builder, useful for chaining
    343          * @see android.widget.TextView#setHyphenationFrequency
    344          */
    345         @NonNull
    346         public Builder setHyphenationFrequency(@HyphenationFrequency int hyphenationFrequency) {
    347             mHyphenationFrequency = hyphenationFrequency;
    348             return this;
    349         }
    350 
    351         /**
    352          * Set indents. Arguments are arrays holding an indent amount, one per line, measured in
    353          * pixels. For lines past the last element in the array, the last element repeats.
    354          *
    355          * @param leftIndents array of indent values for left margin, in pixels
    356          * @param rightIndents array of indent values for right margin, in pixels
    357          * @return this builder, useful for chaining
    358          */
    359         @NonNull
    360         public Builder setIndents(@Nullable int[] leftIndents, @Nullable int[] rightIndents) {
    361             mLeftIndents = leftIndents;
    362             mRightIndents = rightIndents;
    363             return this;
    364         }
    365 
    366         /**
    367          * Set available paddings to draw overhanging text on. Arguments are arrays holding the
    368          * amount of padding available, one per line, measured in pixels. For lines past the last
    369          * element in the array, the last element repeats.
    370          *
    371          * The individual padding amounts should be non-negative. The result of passing negative
    372          * paddings is undefined.
    373          *
    374          * @param leftPaddings array of amounts of available padding for left margin, in pixels
    375          * @param rightPaddings array of amounts of available padding for right margin, in pixels
    376          * @return this builder, useful for chaining
    377          *
    378          * @hide
    379          */
    380         @NonNull
    381         public Builder setAvailablePaddings(@Nullable int[] leftPaddings,
    382                 @Nullable int[] rightPaddings) {
    383             mLeftPaddings = leftPaddings;
    384             mRightPaddings = rightPaddings;
    385             return this;
    386         }
    387 
    388         /**
    389          * Set paragraph justification mode. The default value is
    390          * {@link Layout#JUSTIFICATION_MODE_NONE}. If the last line is too short for justification,
    391          * the last line will be displayed with the alignment set by {@link #setAlignment}.
    392          *
    393          * @param justificationMode justification mode for the paragraph.
    394          * @return this builder, useful for chaining.
    395          */
    396         @NonNull
    397         public Builder setJustificationMode(@JustificationMode int justificationMode) {
    398             mJustificationMode = justificationMode;
    399             return this;
    400         }
    401 
    402         /**
    403          * Sets whether the line spacing should be applied for the last line. Default value is
    404          * {@code false}.
    405          *
    406          * @hide
    407          */
    408         @NonNull
    409         /* package */ Builder setAddLastLineLineSpacing(boolean value) {
    410             mAddLastLineLineSpacing = value;
    411             return this;
    412         }
    413 
    414         /**
    415          * Build the {@link StaticLayout} after options have been set.
    416          *
    417          * <p>Note: the builder object must not be reused in any way after calling this
    418          * method. Setting parameters after calling this method, or calling it a second
    419          * time on the same builder object, will likely lead to unexpected results.
    420          *
    421          * @return the newly constructed {@link StaticLayout} object
    422          */
    423         @NonNull
    424         public StaticLayout build() {
    425             StaticLayout result = new StaticLayout(this);
    426             Builder.recycle(this);
    427             return result;
    428         }
    429 
    430         private CharSequence mText;
    431         private int mStart;
    432         private int mEnd;
    433         private TextPaint mPaint;
    434         private int mWidth;
    435         private Alignment mAlignment;
    436         private TextDirectionHeuristic mTextDir;
    437         private float mSpacingMult;
    438         private float mSpacingAdd;
    439         private boolean mIncludePad;
    440         private boolean mFallbackLineSpacing;
    441         private int mEllipsizedWidth;
    442         private TextUtils.TruncateAt mEllipsize;
    443         private int mMaxLines;
    444         private int mBreakStrategy;
    445         private int mHyphenationFrequency;
    446         @Nullable private int[] mLeftIndents;
    447         @Nullable private int[] mRightIndents;
    448         @Nullable private int[] mLeftPaddings;
    449         @Nullable private int[] mRightPaddings;
    450         private int mJustificationMode;
    451         private boolean mAddLastLineLineSpacing;
    452 
    453         private final Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
    454 
    455         private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<>(3);
    456     }
    457 
    458     /**
    459      * @deprecated Use {@link Builder} instead.
    460      */
    461     @Deprecated
    462     public StaticLayout(CharSequence source, TextPaint paint,
    463                         int width,
    464                         Alignment align, float spacingmult, float spacingadd,
    465                         boolean includepad) {
    466         this(source, 0, source.length(), paint, width, align,
    467              spacingmult, spacingadd, includepad);
    468     }
    469 
    470     /**
    471      * @deprecated Use {@link Builder} instead.
    472      */
    473     @Deprecated
    474     public StaticLayout(CharSequence source, int bufstart, int bufend,
    475                         TextPaint paint, int outerwidth,
    476                         Alignment align,
    477                         float spacingmult, float spacingadd,
    478                         boolean includepad) {
    479         this(source, bufstart, bufend, paint, outerwidth, align,
    480              spacingmult, spacingadd, includepad, null, 0);
    481     }
    482 
    483     /**
    484      * @deprecated Use {@link Builder} instead.
    485      */
    486     @Deprecated
    487     public StaticLayout(CharSequence source, int bufstart, int bufend,
    488             TextPaint paint, int outerwidth,
    489             Alignment align,
    490             float spacingmult, float spacingadd,
    491             boolean includepad,
    492             TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
    493         this(source, bufstart, bufend, paint, outerwidth, align,
    494                 TextDirectionHeuristics.FIRSTSTRONG_LTR,
    495                 spacingmult, spacingadd, includepad, ellipsize, ellipsizedWidth, Integer.MAX_VALUE);
    496     }
    497 
    498     /**
    499      * @hide
    500      * @deprecated Use {@link Builder} instead.
    501      */
    502     @Deprecated
    503     public StaticLayout(CharSequence source, int bufstart, int bufend,
    504                         TextPaint paint, int outerwidth,
    505                         Alignment align, TextDirectionHeuristic textDir,
    506                         float spacingmult, float spacingadd,
    507                         boolean includepad,
    508                         TextUtils.TruncateAt ellipsize, int ellipsizedWidth, int maxLines) {
    509         super((ellipsize == null)
    510                 ? source
    511                 : (source instanceof Spanned)
    512                     ? new SpannedEllipsizer(source)
    513                     : new Ellipsizer(source),
    514               paint, outerwidth, align, textDir, spacingmult, spacingadd);
    515 
    516         Builder b = Builder.obtain(source, bufstart, bufend, paint, outerwidth)
    517             .setAlignment(align)
    518             .setTextDirection(textDir)
    519             .setLineSpacing(spacingadd, spacingmult)
    520             .setIncludePad(includepad)
    521             .setEllipsizedWidth(ellipsizedWidth)
    522             .setEllipsize(ellipsize)
    523             .setMaxLines(maxLines);
    524         /*
    525          * This is annoying, but we can't refer to the layout until superclass construction is
    526          * finished, and the superclass constructor wants the reference to the display text.
    527          *
    528          * In other words, the two Ellipsizer classes in Layout.java need a (Dynamic|Static)Layout
    529          * as a parameter to do their calculations, but the Ellipsizers also need to be the input
    530          * to the superclass's constructor (Layout). In order to go around the circular
    531          * dependency, we construct the Ellipsizer with only one of the parameters, the text. And
    532          * we fill in the rest of the needed information (layout, width, and method) later, here.
    533          *
    534          * This will break if the superclass constructor ever actually cares about the content
    535          * instead of just holding the reference.
    536          */
    537         if (ellipsize != null) {
    538             Ellipsizer e = (Ellipsizer) getText();
    539 
    540             e.mLayout = this;
    541             e.mWidth = ellipsizedWidth;
    542             e.mMethod = ellipsize;
    543             mEllipsizedWidth = ellipsizedWidth;
    544 
    545             mColumns = COLUMNS_ELLIPSIZE;
    546         } else {
    547             mColumns = COLUMNS_NORMAL;
    548             mEllipsizedWidth = outerwidth;
    549         }
    550 
    551         mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2);
    552         mLines  = ArrayUtils.newUnpaddedIntArray(2 * mColumns);
    553         mMaximumVisibleLineCount = maxLines;
    554 
    555         generate(b, b.mIncludePad, b.mIncludePad);
    556 
    557         Builder.recycle(b);
    558     }
    559 
    560     /**
    561      * Used by DynamicLayout.
    562      */
    563     /* package */ StaticLayout(@Nullable CharSequence text) {
    564         super(text, null, 0, null, 0, 0);
    565 
    566         mColumns = COLUMNS_ELLIPSIZE;
    567         mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2);
    568         mLines  = ArrayUtils.newUnpaddedIntArray(2 * mColumns);
    569     }
    570 
    571     private StaticLayout(Builder b) {
    572         super((b.mEllipsize == null)
    573                 ? b.mText
    574                 : (b.mText instanceof Spanned)
    575                     ? new SpannedEllipsizer(b.mText)
    576                     : new Ellipsizer(b.mText),
    577                 b.mPaint, b.mWidth, b.mAlignment, b.mTextDir, b.mSpacingMult, b.mSpacingAdd);
    578 
    579         if (b.mEllipsize != null) {
    580             Ellipsizer e = (Ellipsizer) getText();
    581 
    582             e.mLayout = this;
    583             e.mWidth = b.mEllipsizedWidth;
    584             e.mMethod = b.mEllipsize;
    585             mEllipsizedWidth = b.mEllipsizedWidth;
    586 
    587             mColumns = COLUMNS_ELLIPSIZE;
    588         } else {
    589             mColumns = COLUMNS_NORMAL;
    590             mEllipsizedWidth = b.mWidth;
    591         }
    592 
    593         mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2);
    594         mLines  = ArrayUtils.newUnpaddedIntArray(2 * mColumns);
    595         mMaximumVisibleLineCount = b.mMaxLines;
    596 
    597         mLeftIndents = b.mLeftIndents;
    598         mRightIndents = b.mRightIndents;
    599         mLeftPaddings = b.mLeftPaddings;
    600         mRightPaddings = b.mRightPaddings;
    601         setJustificationMode(b.mJustificationMode);
    602 
    603         generate(b, b.mIncludePad, b.mIncludePad);
    604     }
    605 
    606     /* package */ void generate(Builder b, boolean includepad, boolean trackpad) {
    607         final CharSequence source = b.mText;
    608         final int bufStart = b.mStart;
    609         final int bufEnd = b.mEnd;
    610         TextPaint paint = b.mPaint;
    611         int outerWidth = b.mWidth;
    612         TextDirectionHeuristic textDir = b.mTextDir;
    613         final boolean fallbackLineSpacing = b.mFallbackLineSpacing;
    614         float spacingmult = b.mSpacingMult;
    615         float spacingadd = b.mSpacingAdd;
    616         float ellipsizedWidth = b.mEllipsizedWidth;
    617         TextUtils.TruncateAt ellipsize = b.mEllipsize;
    618         final boolean addLastLineSpacing = b.mAddLastLineLineSpacing;
    619         LineBreaks lineBreaks = new LineBreaks();  // TODO: move to builder to avoid allocation costs
    620         FloatArray widths = new FloatArray();
    621 
    622         mLineCount = 0;
    623         mEllipsized = false;
    624         mMaxLineHeight = mMaximumVisibleLineCount < 1 ? 0 : DEFAULT_MAX_LINE_HEIGHT;
    625 
    626         int v = 0;
    627         boolean needMultiply = (spacingmult != 1 || spacingadd != 0);
    628 
    629         Paint.FontMetricsInt fm = b.mFontMetricsInt;
    630         int[] chooseHtv = null;
    631 
    632         final int[] indents;
    633         if (mLeftIndents != null || mRightIndents != null) {
    634             final int leftLen = mLeftIndents == null ? 0 : mLeftIndents.length;
    635             final int rightLen = mRightIndents == null ? 0 : mRightIndents.length;
    636             final int indentsLen = Math.max(leftLen, rightLen);
    637             indents = new int[indentsLen];
    638             for (int i = 0; i < leftLen; i++) {
    639                 indents[i] = mLeftIndents[i];
    640             }
    641             for (int i = 0; i < rightLen; i++) {
    642                 indents[i] += mRightIndents[i];
    643             }
    644         } else {
    645             indents = null;
    646         }
    647 
    648         final long nativePtr = nInit(
    649                 b.mBreakStrategy, b.mHyphenationFrequency,
    650                 // TODO: Support more justification mode, e.g. letter spacing, stretching.
    651                 b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE,
    652                 indents, mLeftPaddings, mRightPaddings);
    653 
    654         PrecomputedText.ParagraphInfo[] paragraphInfo = null;
    655         final Spanned spanned = (source instanceof Spanned) ? (Spanned) source : null;
    656         if (source instanceof PrecomputedText) {
    657             PrecomputedText precomputed = (PrecomputedText) source;
    658             if (precomputed.canUseMeasuredResult(bufStart, bufEnd, textDir, paint,
    659                       b.mBreakStrategy, b.mHyphenationFrequency)) {
    660                 // Some parameters are different from the ones when measured text is created.
    661                 paragraphInfo = precomputed.getParagraphInfo();
    662             }
    663         }
    664 
    665         if (paragraphInfo == null) {
    666             final PrecomputedText.Params param = new PrecomputedText.Params(paint, textDir,
    667                     b.mBreakStrategy, b.mHyphenationFrequency);
    668             paragraphInfo = PrecomputedText.createMeasuredParagraphs(source, param, bufStart,
    669                     bufEnd, false /* computeLayout */);
    670         }
    671 
    672         try {
    673             for (int paraIndex = 0; paraIndex < paragraphInfo.length; paraIndex++) {
    674                 final int paraStart = paraIndex == 0
    675                         ? bufStart : paragraphInfo[paraIndex - 1].paragraphEnd;
    676                 final int paraEnd = paragraphInfo[paraIndex].paragraphEnd;
    677 
    678                 int firstWidthLineCount = 1;
    679                 int firstWidth = outerWidth;
    680                 int restWidth = outerWidth;
    681 
    682                 LineHeightSpan[] chooseHt = null;
    683 
    684                 if (spanned != null) {
    685                     LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
    686                             LeadingMarginSpan.class);
    687                     for (int i = 0; i < sp.length; i++) {
    688                         LeadingMarginSpan lms = sp[i];
    689                         firstWidth -= sp[i].getLeadingMargin(true);
    690                         restWidth -= sp[i].getLeadingMargin(false);
    691 
    692                         // LeadingMarginSpan2 is odd.  The count affects all
    693                         // leading margin spans, not just this particular one
    694                         if (lms instanceof LeadingMarginSpan2) {
    695                             LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
    696                             firstWidthLineCount = Math.max(firstWidthLineCount,
    697                                     lms2.getLeadingMarginLineCount());
    698                         }
    699                     }
    700 
    701                     chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
    702 
    703                     if (chooseHt.length == 0) {
    704                         chooseHt = null; // So that out() would not assume it has any contents
    705                     } else {
    706                         if (chooseHtv == null || chooseHtv.length < chooseHt.length) {
    707                             chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
    708                         }
    709 
    710                         for (int i = 0; i < chooseHt.length; i++) {
    711                             int o = spanned.getSpanStart(chooseHt[i]);
    712 
    713                             if (o < paraStart) {
    714                                 // starts in this layout, before the
    715                                 // current paragraph
    716 
    717                                 chooseHtv[i] = getLineTop(getLineForOffset(o));
    718                             } else {
    719                                 // starts in this paragraph
    720 
    721                                 chooseHtv[i] = v;
    722                             }
    723                         }
    724                     }
    725                 }
    726 
    727                 // tab stop locations
    728                 int[] variableTabStops = null;
    729                 if (spanned != null) {
    730                     TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
    731                             paraEnd, TabStopSpan.class);
    732                     if (spans.length > 0) {
    733                         int[] stops = new int[spans.length];
    734                         for (int i = 0; i < spans.length; i++) {
    735                             stops[i] = spans[i].getTabStop();
    736                         }
    737                         Arrays.sort(stops, 0, stops.length);
    738                         variableTabStops = stops;
    739                     }
    740                 }
    741 
    742                 final MeasuredParagraph measuredPara = paragraphInfo[paraIndex].measured;
    743                 final char[] chs = measuredPara.getChars();
    744                 final int[] spanEndCache = measuredPara.getSpanEndCache().getRawArray();
    745                 final int[] fmCache = measuredPara.getFontMetrics().getRawArray();
    746                 // TODO: Stop keeping duplicated width copy in native and Java.
    747                 widths.resize(chs.length);
    748 
    749                 // measurement has to be done before performing line breaking
    750                 // but we don't want to recompute fontmetrics or span ranges the
    751                 // second time, so we cache those and then use those stored values
    752 
    753                 int breakCount = nComputeLineBreaks(
    754                         nativePtr,
    755 
    756                         // Inputs
    757                         chs,
    758                         measuredPara.getNativePtr(),
    759                         paraEnd - paraStart,
    760                         firstWidth,
    761                         firstWidthLineCount,
    762                         restWidth,
    763                         variableTabStops,
    764                         TAB_INCREMENT,
    765                         mLineCount,
    766 
    767                         // Outputs
    768                         lineBreaks,
    769                         lineBreaks.breaks.length,
    770                         lineBreaks.breaks,
    771                         lineBreaks.widths,
    772                         lineBreaks.ascents,
    773                         lineBreaks.descents,
    774                         lineBreaks.flags,
    775                         widths.getRawArray());
    776 
    777                 final int[] breaks = lineBreaks.breaks;
    778                 final float[] lineWidths = lineBreaks.widths;
    779                 final float[] ascents = lineBreaks.ascents;
    780                 final float[] descents = lineBreaks.descents;
    781                 final int[] flags = lineBreaks.flags;
    782 
    783                 final int remainingLineCount = mMaximumVisibleLineCount - mLineCount;
    784                 final boolean ellipsisMayBeApplied = ellipsize != null
    785                         && (ellipsize == TextUtils.TruncateAt.END
    786                             || (mMaximumVisibleLineCount == 1
    787                                     && ellipsize != TextUtils.TruncateAt.MARQUEE));
    788                 if (0 < remainingLineCount && remainingLineCount < breakCount
    789                         && ellipsisMayBeApplied) {
    790                     // Calculate width and flag.
    791                     float width = 0;
    792                     int flag = 0; // XXX May need to also have starting hyphen edit
    793                     for (int i = remainingLineCount - 1; i < breakCount; i++) {
    794                         if (i == breakCount - 1) {
    795                             width += lineWidths[i];
    796                         } else {
    797                             for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) {
    798                                 width += widths.get(j);
    799                             }
    800                         }
    801                         flag |= flags[i] & TAB_MASK;
    802                     }
    803                     // Treat the last line and overflowed lines as a single line.
    804                     breaks[remainingLineCount - 1] = breaks[breakCount - 1];
    805                     lineWidths[remainingLineCount - 1] = width;
    806                     flags[remainingLineCount - 1] = flag;
    807 
    808                     breakCount = remainingLineCount;
    809                 }
    810 
    811                 // here is the offset of the starting character of the line we are currently
    812                 // measuring
    813                 int here = paraStart;
    814 
    815                 int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
    816                 int fmCacheIndex = 0;
    817                 int spanEndCacheIndex = 0;
    818                 int breakIndex = 0;
    819                 for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
    820                     // retrieve end of span
    821                     spanEnd = spanEndCache[spanEndCacheIndex++];
    822 
    823                     // retrieve cached metrics, order matches above
    824                     fm.top = fmCache[fmCacheIndex * 4 + 0];
    825                     fm.bottom = fmCache[fmCacheIndex * 4 + 1];
    826                     fm.ascent = fmCache[fmCacheIndex * 4 + 2];
    827                     fm.descent = fmCache[fmCacheIndex * 4 + 3];
    828                     fmCacheIndex++;
    829 
    830                     if (fm.top < fmTop) {
    831                         fmTop = fm.top;
    832                     }
    833                     if (fm.ascent < fmAscent) {
    834                         fmAscent = fm.ascent;
    835                     }
    836                     if (fm.descent > fmDescent) {
    837                         fmDescent = fm.descent;
    838                     }
    839                     if (fm.bottom > fmBottom) {
    840                         fmBottom = fm.bottom;
    841                     }
    842 
    843                     // skip breaks ending before current span range
    844                     while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
    845                         breakIndex++;
    846                     }
    847 
    848                     while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
    849                         int endPos = paraStart + breaks[breakIndex];
    850 
    851                         boolean moreChars = (endPos < bufEnd);
    852 
    853                         final int ascent = fallbackLineSpacing
    854                                 ? Math.min(fmAscent, Math.round(ascents[breakIndex]))
    855                                 : fmAscent;
    856                         final int descent = fallbackLineSpacing
    857                                 ? Math.max(fmDescent, Math.round(descents[breakIndex]))
    858                                 : fmDescent;
    859                         v = out(source, here, endPos,
    860                                 ascent, descent, fmTop, fmBottom,
    861                                 v, spacingmult, spacingadd, chooseHt, chooseHtv, fm,
    862                                 flags[breakIndex], needMultiply, measuredPara, bufEnd,
    863                                 includepad, trackpad, addLastLineSpacing, chs, widths.getRawArray(),
    864                                 paraStart, ellipsize, ellipsizedWidth, lineWidths[breakIndex],
    865                                 paint, moreChars);
    866 
    867                         if (endPos < spanEnd) {
    868                             // preserve metrics for current span
    869                             fmTop = fm.top;
    870                             fmBottom = fm.bottom;
    871                             fmAscent = fm.ascent;
    872                             fmDescent = fm.descent;
    873                         } else {
    874                             fmTop = fmBottom = fmAscent = fmDescent = 0;
    875                         }
    876 
    877                         here = endPos;
    878                         breakIndex++;
    879 
    880                         if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) {
    881                             return;
    882                         }
    883                     }
    884                 }
    885 
    886                 if (paraEnd == bufEnd) {
    887                     break;
    888                 }
    889             }
    890 
    891             if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE)
    892                     && mLineCount < mMaximumVisibleLineCount) {
    893                 final MeasuredParagraph measuredPara =
    894                         MeasuredParagraph.buildForBidi(source, bufEnd, bufEnd, textDir, null);
    895                 paint.getFontMetricsInt(fm);
    896                 v = out(source,
    897                         bufEnd, bufEnd, fm.ascent, fm.descent,
    898                         fm.top, fm.bottom,
    899                         v,
    900                         spacingmult, spacingadd, null,
    901                         null, fm, 0,
    902                         needMultiply, measuredPara, bufEnd,
    903                         includepad, trackpad, addLastLineSpacing, null,
    904                         null, bufStart, ellipsize,
    905                         ellipsizedWidth, 0, paint, false);
    906             }
    907         } finally {
    908             nFinish(nativePtr);
    909         }
    910     }
    911 
    912     private int out(final CharSequence text, final int start, final int end, int above, int below,
    913             int top, int bottom, int v, final float spacingmult, final float spacingadd,
    914             final LineHeightSpan[] chooseHt, final int[] chooseHtv, final Paint.FontMetricsInt fm,
    915             final int flags, final boolean needMultiply, @NonNull final MeasuredParagraph measured,
    916             final int bufEnd, final boolean includePad, final boolean trackPad,
    917             final boolean addLastLineLineSpacing, final char[] chs, final float[] widths,
    918             final int widthStart, final TextUtils.TruncateAt ellipsize, final float ellipsisWidth,
    919             final float textWidth, final TextPaint paint, final boolean moreChars) {
    920         final int j = mLineCount;
    921         final int off = j * mColumns;
    922         final int want = off + mColumns + TOP;
    923         int[] lines = mLines;
    924         final int dir = measured.getParagraphDir();
    925 
    926         if (want >= lines.length) {
    927             final int[] grow = ArrayUtils.newUnpaddedIntArray(GrowingArrayUtils.growSize(want));
    928             System.arraycopy(lines, 0, grow, 0, lines.length);
    929             mLines = grow;
    930             lines = grow;
    931         }
    932 
    933         if (j >= mLineDirections.length) {
    934             final Directions[] grow = ArrayUtils.newUnpaddedArray(Directions.class,
    935                     GrowingArrayUtils.growSize(j));
    936             System.arraycopy(mLineDirections, 0, grow, 0, mLineDirections.length);
    937             mLineDirections = grow;
    938         }
    939 
    940         if (chooseHt != null) {
    941             fm.ascent = above;
    942             fm.descent = below;
    943             fm.top = top;
    944             fm.bottom = bottom;
    945 
    946             for (int i = 0; i < chooseHt.length; i++) {
    947                 if (chooseHt[i] instanceof LineHeightSpan.WithDensity) {
    948                     ((LineHeightSpan.WithDensity) chooseHt[i])
    949                             .chooseHeight(text, start, end, chooseHtv[i], v, fm, paint);
    950                 } else {
    951                     chooseHt[i].chooseHeight(text, start, end, chooseHtv[i], v, fm);
    952                 }
    953             }
    954 
    955             above = fm.ascent;
    956             below = fm.descent;
    957             top = fm.top;
    958             bottom = fm.bottom;
    959         }
    960 
    961         boolean firstLine = (j == 0);
    962         boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);
    963 
    964         if (ellipsize != null) {
    965             // If there is only one line, then do any type of ellipsis except when it is MARQUEE
    966             // if there are multiple lines, just allow END ellipsis on the last line
    967             boolean forceEllipsis = moreChars && (mLineCount + 1 == mMaximumVisibleLineCount);
    968 
    969             boolean doEllipsis =
    970                     (((mMaximumVisibleLineCount == 1 && moreChars) || (firstLine && !moreChars)) &&
    971                             ellipsize != TextUtils.TruncateAt.MARQUEE) ||
    972                     (!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
    973                             ellipsize == TextUtils.TruncateAt.END);
    974             if (doEllipsis) {
    975                 calculateEllipsis(start, end, widths, widthStart,
    976                         ellipsisWidth, ellipsize, j,
    977                         textWidth, paint, forceEllipsis);
    978             }
    979         }
    980 
    981         final boolean lastLine;
    982         if (mEllipsized) {
    983             lastLine = true;
    984         } else {
    985             final boolean lastCharIsNewLine = widthStart != bufEnd && bufEnd > 0
    986                     && text.charAt(bufEnd - 1) == CHAR_NEW_LINE;
    987             if (end == bufEnd && !lastCharIsNewLine) {
    988                 lastLine = true;
    989             } else if (start == bufEnd && lastCharIsNewLine) {
    990                 lastLine = true;
    991             } else {
    992                 lastLine = false;
    993             }
    994         }
    995 
    996         if (firstLine) {
    997             if (trackPad) {
    998                 mTopPadding = top - above;
    999             }
   1000 
   1001             if (includePad) {
   1002                 above = top;
   1003             }
   1004         }
   1005 
   1006         int extra;
   1007 
   1008         if (lastLine) {
   1009             if (trackPad) {
   1010                 mBottomPadding = bottom - below;
   1011             }
   1012 
   1013             if (includePad) {
   1014                 below = bottom;
   1015             }
   1016         }
   1017 
   1018         if (needMultiply && (addLastLineLineSpacing || !lastLine)) {
   1019             double ex = (below - above) * (spacingmult - 1) + spacingadd;
   1020             if (ex >= 0) {
   1021                 extra = (int)(ex + EXTRA_ROUNDING);
   1022             } else {
   1023                 extra = -(int)(-ex + EXTRA_ROUNDING);
   1024             }
   1025         } else {
   1026             extra = 0;
   1027         }
   1028 
   1029         lines[off + START] = start;
   1030         lines[off + TOP] = v;
   1031         lines[off + DESCENT] = below + extra;
   1032         lines[off + EXTRA] = extra;
   1033 
   1034         // special case for non-ellipsized last visible line when maxLines is set
   1035         // store the height as if it was ellipsized
   1036         if (!mEllipsized && currentLineIsTheLastVisibleOne) {
   1037             // below calculation as if it was the last line
   1038             int maxLineBelow = includePad ? bottom : below;
   1039             // similar to the calculation of v below, without the extra.
   1040             mMaxLineHeight = v + (maxLineBelow - above);
   1041         }
   1042 
   1043         v += (below - above) + extra;
   1044         lines[off + mColumns + START] = end;
   1045         lines[off + mColumns + TOP] = v;
   1046 
   1047         // TODO: could move TAB to share same column as HYPHEN, simplifying this code and gaining
   1048         // one bit for start field
   1049         lines[off + TAB] |= flags & TAB_MASK;
   1050         lines[off + HYPHEN] = flags;
   1051         lines[off + DIR] |= dir << DIR_SHIFT;
   1052         mLineDirections[j] = measured.getDirections(start - widthStart, end - widthStart);
   1053 
   1054         mLineCount++;
   1055         return v;
   1056     }
   1057 
   1058     private void calculateEllipsis(int lineStart, int lineEnd,
   1059                                    float[] widths, int widthStart,
   1060                                    float avail, TextUtils.TruncateAt where,
   1061                                    int line, float textWidth, TextPaint paint,
   1062                                    boolean forceEllipsis) {
   1063         avail -= getTotalInsets(line);
   1064         if (textWidth <= avail && !forceEllipsis) {
   1065             // Everything fits!
   1066             mLines[mColumns * line + ELLIPSIS_START] = 0;
   1067             mLines[mColumns * line + ELLIPSIS_COUNT] = 0;
   1068             return;
   1069         }
   1070 
   1071         float ellipsisWidth = paint.measureText(TextUtils.getEllipsisString(where));
   1072         int ellipsisStart = 0;
   1073         int ellipsisCount = 0;
   1074         int len = lineEnd - lineStart;
   1075 
   1076         // We only support start ellipsis on a single line
   1077         if (where == TextUtils.TruncateAt.START) {
   1078             if (mMaximumVisibleLineCount == 1) {
   1079                 float sum = 0;
   1080                 int i;
   1081 
   1082                 for (i = len; i > 0; i--) {
   1083                     float w = widths[i - 1 + lineStart - widthStart];
   1084                     if (w + sum + ellipsisWidth > avail) {
   1085                         while (i < len && widths[i + lineStart - widthStart] == 0.0f) {
   1086                             i++;
   1087                         }
   1088                         break;
   1089                     }
   1090 
   1091                     sum += w;
   1092                 }
   1093 
   1094                 ellipsisStart = 0;
   1095                 ellipsisCount = i;
   1096             } else {
   1097                 if (Log.isLoggable(TAG, Log.WARN)) {
   1098                     Log.w(TAG, "Start Ellipsis only supported with one line");
   1099                 }
   1100             }
   1101         } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE ||
   1102                 where == TextUtils.TruncateAt.END_SMALL) {
   1103             float sum = 0;
   1104             int i;
   1105 
   1106             for (i = 0; i < len; i++) {
   1107                 float w = widths[i + lineStart - widthStart];
   1108 
   1109                 if (w + sum + ellipsisWidth > avail) {
   1110                     break;
   1111                 }
   1112 
   1113                 sum += w;
   1114             }
   1115 
   1116             ellipsisStart = i;
   1117             ellipsisCount = len - i;
   1118             if (forceEllipsis && ellipsisCount == 0 && len > 0) {
   1119                 ellipsisStart = len - 1;
   1120                 ellipsisCount = 1;
   1121             }
   1122         } else {
   1123             // where = TextUtils.TruncateAt.MIDDLE We only support middle ellipsis on a single line
   1124             if (mMaximumVisibleLineCount == 1) {
   1125                 float lsum = 0, rsum = 0;
   1126                 int left = 0, right = len;
   1127 
   1128                 float ravail = (avail - ellipsisWidth) / 2;
   1129                 for (right = len; right > 0; right--) {
   1130                     float w = widths[right - 1 + lineStart - widthStart];
   1131 
   1132                     if (w + rsum > ravail) {
   1133                         while (right < len && widths[right + lineStart - widthStart] == 0.0f) {
   1134                             right++;
   1135                         }
   1136                         break;
   1137                     }
   1138                     rsum += w;
   1139                 }
   1140 
   1141                 float lavail = avail - ellipsisWidth - rsum;
   1142                 for (left = 0; left < right; left++) {
   1143                     float w = widths[left + lineStart - widthStart];
   1144 
   1145                     if (w + lsum > lavail) {
   1146                         break;
   1147                     }
   1148 
   1149                     lsum += w;
   1150                 }
   1151 
   1152                 ellipsisStart = left;
   1153                 ellipsisCount = right - left;
   1154             } else {
   1155                 if (Log.isLoggable(TAG, Log.WARN)) {
   1156                     Log.w(TAG, "Middle Ellipsis only supported with one line");
   1157                 }
   1158             }
   1159         }
   1160         mEllipsized = true;
   1161         mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
   1162         mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
   1163     }
   1164 
   1165     private float getTotalInsets(int line) {
   1166         int totalIndent = 0;
   1167         if (mLeftIndents != null) {
   1168             totalIndent = mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
   1169         }
   1170         if (mRightIndents != null) {
   1171             totalIndent += mRightIndents[Math.min(line, mRightIndents.length - 1)];
   1172         }
   1173         return totalIndent;
   1174     }
   1175 
   1176     // Override the base class so we can directly access our members,
   1177     // rather than relying on member functions.
   1178     // The logic mirrors that of Layout.getLineForVertical
   1179     // FIXME: It may be faster to do a linear search for layouts without many lines.
   1180     @Override
   1181     public int getLineForVertical(int vertical) {
   1182         int high = mLineCount;
   1183         int low = -1;
   1184         int guess;
   1185         int[] lines = mLines;
   1186         while (high - low > 1) {
   1187             guess = (high + low) >> 1;
   1188             if (lines[mColumns * guess + TOP] > vertical){
   1189                 high = guess;
   1190             } else {
   1191                 low = guess;
   1192             }
   1193         }
   1194         if (low < 0) {
   1195             return 0;
   1196         } else {
   1197             return low;
   1198         }
   1199     }
   1200 
   1201     @Override
   1202     public int getLineCount() {
   1203         return mLineCount;
   1204     }
   1205 
   1206     @Override
   1207     public int getLineTop(int line) {
   1208         return mLines[mColumns * line + TOP];
   1209     }
   1210 
   1211     /**
   1212      * @hide
   1213      */
   1214     @Override
   1215     public int getLineExtra(int line) {
   1216         return mLines[mColumns * line + EXTRA];
   1217     }
   1218 
   1219     @Override
   1220     public int getLineDescent(int line) {
   1221         return mLines[mColumns * line + DESCENT];
   1222     }
   1223 
   1224     @Override
   1225     public int getLineStart(int line) {
   1226         return mLines[mColumns * line + START] & START_MASK;
   1227     }
   1228 
   1229     @Override
   1230     public int getParagraphDirection(int line) {
   1231         return mLines[mColumns * line + DIR] >> DIR_SHIFT;
   1232     }
   1233 
   1234     @Override
   1235     public boolean getLineContainsTab(int line) {
   1236         return (mLines[mColumns * line + TAB] & TAB_MASK) != 0;
   1237     }
   1238 
   1239     @Override
   1240     public final Directions getLineDirections(int line) {
   1241         if (line > getLineCount()) {
   1242             throw new ArrayIndexOutOfBoundsException();
   1243         }
   1244         return mLineDirections[line];
   1245     }
   1246 
   1247     @Override
   1248     public int getTopPadding() {
   1249         return mTopPadding;
   1250     }
   1251 
   1252     @Override
   1253     public int getBottomPadding() {
   1254         return mBottomPadding;
   1255     }
   1256 
   1257     /**
   1258      * @hide
   1259      */
   1260     @Override
   1261     public int getHyphen(int line) {
   1262         return mLines[mColumns * line + HYPHEN] & HYPHEN_MASK;
   1263     }
   1264 
   1265     /**
   1266      * @hide
   1267      */
   1268     @Override
   1269     public int getIndentAdjust(int line, Alignment align) {
   1270         if (align == Alignment.ALIGN_LEFT) {
   1271             if (mLeftIndents == null) {
   1272                 return 0;
   1273             } else {
   1274                 return mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
   1275             }
   1276         } else if (align == Alignment.ALIGN_RIGHT) {
   1277             if (mRightIndents == null) {
   1278                 return 0;
   1279             } else {
   1280                 return -mRightIndents[Math.min(line, mRightIndents.length - 1)];
   1281             }
   1282         } else if (align == Alignment.ALIGN_CENTER) {
   1283             int left = 0;
   1284             if (mLeftIndents != null) {
   1285                 left = mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
   1286             }
   1287             int right = 0;
   1288             if (mRightIndents != null) {
   1289                 right = mRightIndents[Math.min(line, mRightIndents.length - 1)];
   1290             }
   1291             return (left - right) >> 1;
   1292         } else {
   1293             throw new AssertionError("unhandled alignment " + align);
   1294         }
   1295     }
   1296 
   1297     @Override
   1298     public int getEllipsisCount(int line) {
   1299         if (mColumns < COLUMNS_ELLIPSIZE) {
   1300             return 0;
   1301         }
   1302 
   1303         return mLines[mColumns * line + ELLIPSIS_COUNT];
   1304     }
   1305 
   1306     @Override
   1307     public int getEllipsisStart(int line) {
   1308         if (mColumns < COLUMNS_ELLIPSIZE) {
   1309             return 0;
   1310         }
   1311 
   1312         return mLines[mColumns * line + ELLIPSIS_START];
   1313     }
   1314 
   1315     @Override
   1316     public int getEllipsizedWidth() {
   1317         return mEllipsizedWidth;
   1318     }
   1319 
   1320     /**
   1321      * Return the total height of this layout.
   1322      *
   1323      * @param cap if true and max lines is set, returns the height of the layout at the max lines.
   1324      *
   1325      * @hide
   1326      */
   1327     public int getHeight(boolean cap) {
   1328         if (cap && mLineCount >= mMaximumVisibleLineCount && mMaxLineHeight == -1 &&
   1329                 Log.isLoggable(TAG, Log.WARN)) {
   1330             Log.w(TAG, "maxLineHeight should not be -1. "
   1331                     + " maxLines:" + mMaximumVisibleLineCount
   1332                     + " lineCount:" + mLineCount);
   1333         }
   1334 
   1335         return cap && mLineCount >= mMaximumVisibleLineCount && mMaxLineHeight != -1 ?
   1336                 mMaxLineHeight : super.getHeight();
   1337     }
   1338 
   1339     @FastNative
   1340     private static native long nInit(
   1341             @BreakStrategy int breakStrategy,
   1342             @HyphenationFrequency int hyphenationFrequency,
   1343             boolean isJustified,
   1344             @Nullable int[] indents,
   1345             @Nullable int[] leftPaddings,
   1346             @Nullable int[] rightPaddings);
   1347 
   1348     @CriticalNative
   1349     private static native void nFinish(long nativePtr);
   1350 
   1351     // populates LineBreaks and returns the number of breaks found
   1352     //
   1353     // the arrays inside the LineBreaks objects are passed in as well
   1354     // to reduce the number of JNI calls in the common case where the
   1355     // arrays do not have to be resized
   1356     // The individual character widths will be returned in charWidths. The length of charWidths must
   1357     // be at least the length of the text.
   1358     private static native int nComputeLineBreaks(
   1359             /* non zero */ long nativePtr,
   1360 
   1361             // Inputs
   1362             @NonNull char[] text,
   1363             /* Non Zero */ long measuredTextPtr,
   1364             @IntRange(from = 0) int length,
   1365             @FloatRange(from = 0.0f) float firstWidth,
   1366             @IntRange(from = 0) int firstWidthLineCount,
   1367             @FloatRange(from = 0.0f) float restWidth,
   1368             @Nullable int[] variableTabStops,
   1369             int defaultTabStop,
   1370             @IntRange(from = 0) int indentsOffset,
   1371 
   1372             // Outputs
   1373             @NonNull LineBreaks recycle,
   1374             @IntRange(from  = 0) int recycleLength,
   1375             @NonNull int[] recycleBreaks,
   1376             @NonNull float[] recycleWidths,
   1377             @NonNull float[] recycleAscents,
   1378             @NonNull float[] recycleDescents,
   1379             @NonNull int[] recycleFlags,
   1380             @NonNull float[] charWidths);
   1381 
   1382     private int mLineCount;
   1383     private int mTopPadding, mBottomPadding;
   1384     private int mColumns;
   1385     private int mEllipsizedWidth;
   1386 
   1387     /**
   1388      * Keeps track if ellipsize is applied to the text.
   1389      */
   1390     private boolean mEllipsized;
   1391 
   1392     /**
   1393      * If maxLines is set, ellipsize is not set, and the actual line count of text is greater than
   1394      * or equal to maxLine, this variable holds the ideal visual height of the maxLine'th line
   1395      * starting from the top of the layout. If maxLines is not set its value will be -1.
   1396      *
   1397      * The value is the same as getLineTop(maxLines) for ellipsized version where structurally no
   1398      * more than maxLines is contained.
   1399      */
   1400     private int mMaxLineHeight = DEFAULT_MAX_LINE_HEIGHT;
   1401 
   1402     private static final int COLUMNS_NORMAL = 5;
   1403     private static final int COLUMNS_ELLIPSIZE = 7;
   1404     private static final int START = 0;
   1405     private static final int DIR = START;
   1406     private static final int TAB = START;
   1407     private static final int TOP = 1;
   1408     private static final int DESCENT = 2;
   1409     private static final int EXTRA = 3;
   1410     private static final int HYPHEN = 4;
   1411     private static final int ELLIPSIS_START = 5;
   1412     private static final int ELLIPSIS_COUNT = 6;
   1413 
   1414     private int[] mLines;
   1415     private Directions[] mLineDirections;
   1416     private int mMaximumVisibleLineCount = Integer.MAX_VALUE;
   1417 
   1418     private static final int START_MASK = 0x1FFFFFFF;
   1419     private static final int DIR_SHIFT  = 30;
   1420     private static final int TAB_MASK   = 0x20000000;
   1421     private static final int HYPHEN_MASK = 0xFF;
   1422 
   1423     private static final int TAB_INCREMENT = 20; // same as Layout, but that's private
   1424 
   1425     private static final char CHAR_NEW_LINE = '\n';
   1426 
   1427     private static final double EXTRA_ROUNDING = 0.5;
   1428 
   1429     private static final int DEFAULT_MAX_LINE_HEIGHT = -1;
   1430 
   1431     // This is used to return three arrays from a single JNI call when
   1432     // performing line breaking
   1433     /*package*/ static class LineBreaks {
   1434         private static final int INITIAL_SIZE = 16;
   1435         public int[] breaks = new int[INITIAL_SIZE];
   1436         public float[] widths = new float[INITIAL_SIZE];
   1437         public float[] ascents = new float[INITIAL_SIZE];
   1438         public float[] descents = new float[INITIAL_SIZE];
   1439         public int[] flags = new int[INITIAL_SIZE]; // hasTab
   1440         // breaks, widths, and flags should all have the same length
   1441     }
   1442 
   1443     @Nullable private int[] mLeftIndents;
   1444     @Nullable private int[] mRightIndents;
   1445     @Nullable private int[] mLeftPaddings;
   1446     @Nullable private int[] mRightPaddings;
   1447 }
   1448