1 /*
2  * Copyright (C) 2006 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.text;
18 
19 import android.annotation.Nullable;
20 import android.graphics.Paint;
21 import android.text.style.LeadingMarginSpan;
22 import android.text.style.LeadingMarginSpan.LeadingMarginSpan2;
23 import android.text.style.LineHeightSpan;
24 import android.text.style.MetricAffectingSpan;
25 import android.text.style.TabStopSpan;
26 import android.util.Log;
27 import android.util.Pools.SynchronizedPool;
28 
29 import com.android.internal.util.ArrayUtils;
30 import com.android.internal.util.GrowingArrayUtils;
31 
32 import java.nio.ByteBuffer;
33 import java.util.Arrays;
34 import 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  */
46 public 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 {
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          */
obtain(CharSequence source, int start, int end, TextPaint paint, int width)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 
recycle(Builder b)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
finish()115         /* package */ void finish() {
116             nFinishBuilder(mNativePtr);
117             mText = null;
118             mPaint = null;
119             mLeftIndents = null;
120             mRightIndents = null;
121             mMeasuredText.finish();
122         }
123 
setText(CharSequence source)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          */
setText(CharSequence source, int start, int end)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          */
setPaint(TextPaint paint)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          */
setWidth(int width)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          */
setAlignment(Alignment alignment)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          */
setTextDirection(TextDirectionHeuristic textDir)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          */
setLineSpacing(float spacingAdd, float spacingMult)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          */
setIncludePad(boolean includePad)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          */
setEllipsizedWidth(int ellipsizedWidth)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          */
setEllipsize(@ullable TextUtils.TruncateAt ellipsize)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          */
setMaxLines(int maxLines)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          */
setBreakStrategy(@reakStrategy int breakStrategy)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          */
setHyphenationFrequency(@yphenationFrequency int hyphenationFrequency)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          */
setIndents(int[] leftIndents, int[] rightIndents)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