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 {
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
obtain(@onNull CharSequence source, @IntRange(from = 0) int start, @IntRange(from = 0) int end, @NonNull TextPaint paint, @IntRange(from = 0) int width)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          */
recycle(@onNull Builder b)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
finish()133         /* package */ void finish() {
134             mText = null;
135             mPaint = null;
136             mLeftIndents = null;
137             mRightIndents = null;
138             mLeftPaddings = null;
139             mRightPaddings = null;
140         }
141 
setText(CharSequence source)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
setText(@onNull CharSequence source, int start, int end)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
setPaint(@onNull TextPaint paint)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
setWidth(@ntRangefrom = 0) int width)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
setAlignment(@onNull Alignment alignment)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
setTextDirection(@onNull TextDirectionHeuristic textDir)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
setLineSpacing(float spacingAdd, @FloatRange(from = 0.0) float spacingMult)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
setIncludePad(boolean includePad)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
setUseLineSpacingFromFallbacks(boolean useLineSpacingFromFallbacks)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
setEllipsizedWidth(@ntRangefrom = 0) int ellipsizedWidth)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
setEllipsize(@ullable TextUtils.TruncateAt ellipsize)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
setMaxLines(@ntRangefrom = 0) int maxLines)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
setBreakStrategy(@reakStrategy int breakStrategy)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
setHyphenationFrequency(@yphenationFrequency int hyphenationFrequency)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
setIndents(@ullable int[] leftIndents, @Nullable int[] rightIndents)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
setAvailablePaddings(@ullable int[] leftPaddings, @Nullable int[] rightPaddings)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
setJustificationMode(@ustificationMode int justificationMode)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
setAddLastLineLineSpacing(boolean value)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
build()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
StaticLayout(CharSequence source, TextPaint paint, int width, Alignment align, float spacingmult, float spacingadd, boolean includepad)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
StaticLayout(CharSequence source, int bufstart, int bufend, TextPaint paint, int outerwidth, Alignment align, float spacingmult, float spacingadd, boolean includepad)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
StaticLayout(CharSequence source, int bufstart, int bufend, TextPaint paint, int outerwidth, Alignment align, float spacingmult, float spacingadd, boolean includepad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth)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
StaticLayout(CharSequence source, int bufstart, int bufend, TextPaint paint, int outerwidth, Alignment align, TextDirectionHeuristic textDir, float spacingmult, float spacingadd, boolean includepad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth, int maxLines)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      */
StaticLayout(@ullable CharSequence text)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 
StaticLayout(Builder b)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 
generate(Builder b, boolean includepad, boolean trackpad)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