StaticLayout.java revision c3328d648e827c8a65f46ed3a8b0ec96076b5ebe
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
17package android.text;
18
19import android.annotation.FloatRange;
20import android.annotation.IntRange;
21import android.annotation.NonNull;
22import android.annotation.Nullable;
23import android.graphics.Paint;
24import android.text.AutoGrowArray.FloatArray;
25import android.text.style.LeadingMarginSpan;
26import android.text.style.LeadingMarginSpan.LeadingMarginSpan2;
27import android.text.style.LineHeightSpan;
28import android.text.style.TabStopSpan;
29import android.util.Log;
30import android.util.Pools.SynchronizedPool;
31
32import com.android.internal.util.ArrayUtils;
33import com.android.internal.util.GrowingArrayUtils;
34
35import dalvik.annotation.optimization.CriticalNative;
36import dalvik.annotation.optimization.FastNative;
37
38import 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 */
50public 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