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