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