• 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 static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
20 import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
21 
22 import android.annotation.FlaggedApi;
23 import android.annotation.FloatRange;
24 import android.annotation.IntRange;
25 import android.annotation.NonNull;
26 import android.annotation.Nullable;
27 import android.annotation.SuppressLint;
28 import android.compat.annotation.UnsupportedAppUsage;
29 import android.graphics.Paint;
30 import android.graphics.RectF;
31 import android.graphics.text.LineBreakConfig;
32 import android.graphics.text.LineBreaker;
33 import android.os.Build;
34 import android.os.Trace;
35 import android.text.style.LeadingMarginSpan;
36 import android.text.style.LeadingMarginSpan.LeadingMarginSpan2;
37 import android.text.style.LineHeightSpan;
38 import android.text.style.TabStopSpan;
39 import android.util.Log;
40 import android.util.Pools.SynchronizedPool;
41 
42 import com.android.internal.util.ArrayUtils;
43 import com.android.internal.util.GrowingArrayUtils;
44 import com.android.text.flags.Flags;
45 
46 import java.util.Arrays;
47 
48 /**
49  * StaticLayout is a Layout for text that will not be edited after it
50  * is laid out.  Use {@link DynamicLayout} for text that may change.
51  * <p>This is used by widgets to control text layout. You should not need
52  * to use this class directly unless you are implementing your own widget
53  * or custom display object, or would be tempted to call
54  * {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int,
55  * float, float, android.graphics.Paint)
56  * Canvas.drawText()} directly.</p>
57  */
58 @android.ravenwood.annotation.RavenwoodKeepWholeClass
59 public class StaticLayout extends Layout {
60     /*
61      * The break iteration is done in native code. The protocol for using the native code is as
62      * follows.
63      *
64      * First, call nInit to setup native line breaker object. Then, for each paragraph, do the
65      * following:
66      *
67      *   - Create MeasuredParagraph by MeasuredParagraph.buildForStaticLayout which measures in
68      *     native.
69      *   - Run LineBreaker.computeLineBreaks() to obtain line breaks for the paragraph.
70      *
71      * After all paragraphs, call finish() to release expensive buffers.
72      */
73 
74     static final String TAG = "StaticLayout";
75 
76     /**
77      * Builder for static layouts. The builder is the preferred pattern for constructing
78      * StaticLayout objects and should be preferred over the constructors, particularly to access
79      * newer features. To build a static layout, first call {@link #obtain} with the required
80      * arguments (text, paint, and width), then call setters for optional parameters, and finally
81      * {@link #build} to build the StaticLayout object. Parameters not explicitly set will get
82      * default values.
83      */
84     public final static class Builder {
Builder()85         private Builder() {}
86 
87         /**
88          * Obtain a builder for constructing StaticLayout objects.
89          *
90          * @param source The text to be laid out, optionally with spans
91          * @param start The index of the start of the text
92          * @param end The index + 1 of the end of the text
93          * @param paint The base paint used for layout
94          * @param width The width in pixels
95          * @return a builder object used for constructing the StaticLayout
96          */
97         @NonNull
obtain(@onNull CharSequence source, @IntRange(from = 0) int start, @IntRange(from = 0) int end, @NonNull TextPaint paint, @IntRange(from = 0) int width)98         public static Builder obtain(@NonNull CharSequence source, @IntRange(from = 0) int start,
99                 @IntRange(from = 0) int end, @NonNull TextPaint paint,
100                 @IntRange(from = 0) int width) {
101             Builder b = sPool.acquire();
102             if (b == null) {
103                 b = new Builder();
104             }
105 
106             // set default initial values
107             b.mText = source;
108             b.mStart = start;
109             b.mEnd = end;
110             b.mPaint = paint;
111             b.mWidth = width;
112             b.mAlignment = Alignment.ALIGN_NORMAL;
113             b.mTextDir = TextDirectionHeuristics.FIRSTSTRONG_LTR;
114             b.mSpacingMult = DEFAULT_LINESPACING_MULTIPLIER;
115             b.mSpacingAdd = DEFAULT_LINESPACING_ADDITION;
116             b.mIncludePad = true;
117             b.mFallbackLineSpacing = false;
118             b.mEllipsizedWidth = width;
119             b.mEllipsize = null;
120             b.mMaxLines = Integer.MAX_VALUE;
121             b.mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
122             b.mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
123             b.mJustificationMode = Layout.JUSTIFICATION_MODE_NONE;
124             b.mLineBreakConfig = LineBreakConfig.NONE;
125             b.mUseBoundsForWidth = false;
126             b.mMinimumFontMetrics = null;
127             return b;
128         }
129 
130         /**
131          * This method should be called after the layout is finished getting constructed and the
132          * builder needs to be cleaned up and returned to the pool.
133          */
recycle(@onNull Builder b)134         private static void recycle(@NonNull Builder b) {
135             b.mPaint = null;
136             b.mText = null;
137             b.mLeftIndents = null;
138             b.mRightIndents = null;
139             b.mMinimumFontMetrics = null;
140             sPool.release(b);
141         }
142 
143         // release any expensive state
finish()144         /* package */ void finish() {
145             mText = null;
146             mPaint = null;
147             mLeftIndents = null;
148             mRightIndents = null;
149             mMinimumFontMetrics = null;
150         }
151 
setText(CharSequence source)152         public Builder setText(CharSequence source) {
153             return setText(source, 0, source.length());
154         }
155 
156         /**
157          * Set the text. Only useful when re-using the builder, which is done for
158          * the internal implementation of {@link DynamicLayout} but not as part
159          * of normal {@link StaticLayout} usage.
160          *
161          * @param source The text to be laid out, optionally with spans
162          * @param start The index of the start of the text
163          * @param end The index + 1 of the end of the text
164          * @return this builder, useful for chaining
165          *
166          * @hide
167          */
168         @NonNull
setText(@onNull CharSequence source, int start, int end)169         public Builder setText(@NonNull CharSequence source, int start, int end) {
170             mText = source;
171             mStart = start;
172             mEnd = end;
173             return this;
174         }
175 
176         /**
177          * Set the paint. Internal for reuse cases only.
178          *
179          * @param paint The base paint used for layout
180          * @return this builder, useful for chaining
181          *
182          * @hide
183          */
184         @NonNull
setPaint(@onNull TextPaint paint)185         public Builder setPaint(@NonNull TextPaint paint) {
186             mPaint = paint;
187             return this;
188         }
189 
190         /**
191          * Set the width. Internal for reuse cases only.
192          *
193          * @param width The width in pixels
194          * @return this builder, useful for chaining
195          *
196          * @hide
197          */
198         @NonNull
setWidth(@ntRangefrom = 0) int width)199         public Builder setWidth(@IntRange(from = 0) int width) {
200             mWidth = width;
201             if (mEllipsize == null) {
202                 mEllipsizedWidth = width;
203             }
204             return this;
205         }
206 
207         /**
208          * Set the alignment. The default is {@link Layout.Alignment#ALIGN_NORMAL}.
209          *
210          * @param alignment Alignment for the resulting {@link StaticLayout}
211          * @return this builder, useful for chaining
212          */
213         @NonNull
setAlignment(@onNull Alignment alignment)214         public Builder setAlignment(@NonNull Alignment alignment) {
215             mAlignment = alignment;
216             return this;
217         }
218 
219         /**
220          * Set the text direction heuristic. The text direction heuristic is used to
221          * resolve text direction per-paragraph based on the input text. The default is
222          * {@link TextDirectionHeuristics#FIRSTSTRONG_LTR}.
223          *
224          * @param textDir text direction heuristic for resolving bidi behavior.
225          * @return this builder, useful for chaining
226          */
227         @NonNull
setTextDirection(@onNull TextDirectionHeuristic textDir)228         public Builder setTextDirection(@NonNull TextDirectionHeuristic textDir) {
229             mTextDir = textDir;
230             return this;
231         }
232 
233         /**
234          * Set line spacing parameters. Each line will have its line spacing multiplied by
235          * {@code spacingMult} and then increased by {@code spacingAdd}. The default is 0.0 for
236          * {@code spacingAdd} and 1.0 for {@code spacingMult}.
237          *
238          * @param spacingAdd the amount of line spacing addition
239          * @param spacingMult the line spacing multiplier
240          * @return this builder, useful for chaining
241          * @see android.widget.TextView#setLineSpacing
242          */
243         @NonNull
setLineSpacing(float spacingAdd, @FloatRange(from = 0.0) float spacingMult)244         public Builder setLineSpacing(float spacingAdd, @FloatRange(from = 0.0) float spacingMult) {
245             mSpacingAdd = spacingAdd;
246             mSpacingMult = spacingMult;
247             return this;
248         }
249 
250         /**
251          * Set whether to include extra space beyond font ascent and descent (which is
252          * needed to avoid clipping in some languages, such as Arabic and Kannada). The
253          * default is {@code true}.
254          *
255          * @param includePad whether to include padding
256          * @return this builder, useful for chaining
257          * @see android.widget.TextView#setIncludeFontPadding
258          */
259         @NonNull
setIncludePad(boolean includePad)260         public Builder setIncludePad(boolean includePad) {
261             mIncludePad = includePad;
262             return this;
263         }
264 
265         /**
266          * Set whether to respect the ascent and descent of the fallback fonts that are used in
267          * displaying the text (which is needed to avoid text from consecutive lines running into
268          * each other). If set, fallback fonts that end up getting used can increase the ascent
269          * and descent of the lines that they are used on.
270          *
271          * <p>For backward compatibility reasons, the default is {@code false}, but setting this to
272          * true is strongly recommended. It is required to be true if text could be in languages
273          * like Burmese or Tibetan where text is typically much taller or deeper than Latin text.
274          *
275          * @param useLineSpacingFromFallbacks whether to expand linespacing based on fallback fonts
276          * @return this builder, useful for chaining
277          */
278         @NonNull
setUseLineSpacingFromFallbacks(boolean useLineSpacingFromFallbacks)279         public Builder setUseLineSpacingFromFallbacks(boolean useLineSpacingFromFallbacks) {
280             mFallbackLineSpacing = useLineSpacingFromFallbacks;
281             return this;
282         }
283 
284         /**
285          * Set the width as used for ellipsizing purposes, if it differs from the
286          * normal layout width. The default is the {@code width}
287          * passed to {@link #obtain}.
288          *
289          * @param ellipsizedWidth width used for ellipsizing, in pixels
290          * @return this builder, useful for chaining
291          * @see android.widget.TextView#setEllipsize
292          */
293         @NonNull
setEllipsizedWidth(@ntRangefrom = 0) int ellipsizedWidth)294         public Builder setEllipsizedWidth(@IntRange(from = 0) int ellipsizedWidth) {
295             mEllipsizedWidth = ellipsizedWidth;
296             return this;
297         }
298 
299         /**
300          * Set ellipsizing on the layout. Causes words that are longer than the view
301          * is wide, or exceeding the number of lines (see #setMaxLines) in the case
302          * of {@link android.text.TextUtils.TruncateAt#END} or
303          * {@link android.text.TextUtils.TruncateAt#MARQUEE}, to be ellipsized instead
304          * of broken. The default is {@code null}, indicating no ellipsis is to be applied.
305          *
306          * @param ellipsize type of ellipsis behavior
307          * @return this builder, useful for chaining
308          * @see android.widget.TextView#setEllipsize
309          */
310         @NonNull
setEllipsize(@ullable TextUtils.TruncateAt ellipsize)311         public Builder setEllipsize(@Nullable TextUtils.TruncateAt ellipsize) {
312             mEllipsize = ellipsize;
313             return this;
314         }
315 
316         /**
317          * Set maximum number of lines. This is particularly useful in the case of
318          * ellipsizing, where it changes the layout of the last line. The default is
319          * unlimited.
320          *
321          * @param maxLines maximum number of lines in the layout
322          * @return this builder, useful for chaining
323          * @see android.widget.TextView#setMaxLines
324          */
325         @NonNull
setMaxLines(@ntRangefrom = 0) int maxLines)326         public Builder setMaxLines(@IntRange(from = 0) int maxLines) {
327             mMaxLines = maxLines;
328             return this;
329         }
330 
331         /**
332          * Set break strategy, useful for selecting high quality or balanced paragraph
333          * layout options. The default is {@link Layout#BREAK_STRATEGY_SIMPLE}.
334          * <p/>
335          * Enabling hyphenation with either using {@link Layout#HYPHENATION_FREQUENCY_NORMAL} or
336          * {@link Layout#HYPHENATION_FREQUENCY_FULL} while line breaking is set to one of
337          * {@link Layout#BREAK_STRATEGY_BALANCED}, {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}
338          * improves the structure of text layout however has performance impact and requires more
339          * time to do the text layout.
340          *
341          * @param breakStrategy break strategy for paragraph layout
342          * @return this builder, useful for chaining
343          * @see android.widget.TextView#setBreakStrategy
344          * @see #setHyphenationFrequency(int)
345          */
346         @NonNull
setBreakStrategy(@reakStrategy int breakStrategy)347         public Builder setBreakStrategy(@BreakStrategy int breakStrategy) {
348             mBreakStrategy = breakStrategy;
349             return this;
350         }
351 
352         /**
353          * Set hyphenation frequency, to control the amount of automatic hyphenation used. The
354          * possible values are defined in {@link Layout}, by constants named with the pattern
355          * {@code HYPHENATION_FREQUENCY_*}. The default is
356          * {@link Layout#HYPHENATION_FREQUENCY_NONE}.
357          * <p/>
358          * Enabling hyphenation with either using {@link Layout#HYPHENATION_FREQUENCY_NORMAL} or
359          * {@link Layout#HYPHENATION_FREQUENCY_FULL} while line breaking is set to one of
360          * {@link Layout#BREAK_STRATEGY_BALANCED}, {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}
361          * improves the structure of text layout however has performance impact and requires more
362          * time to do the text layout.
363          *
364          * @param hyphenationFrequency hyphenation frequency for the paragraph
365          * @return this builder, useful for chaining
366          * @see android.widget.TextView#setHyphenationFrequency
367          * @see #setBreakStrategy(int)
368          */
369         @NonNull
setHyphenationFrequency(@yphenationFrequency int hyphenationFrequency)370         public Builder setHyphenationFrequency(@HyphenationFrequency int hyphenationFrequency) {
371             mHyphenationFrequency = hyphenationFrequency;
372             return this;
373         }
374 
375         /**
376          * Set indents. Arguments are arrays holding an indent amount, one per line, measured in
377          * pixels. For lines past the last element in the array, the last element repeats.
378          *
379          * @param leftIndents array of indent values for left margin, in pixels
380          * @param rightIndents array of indent values for right margin, in pixels
381          * @return this builder, useful for chaining
382          */
383         @NonNull
setIndents(@ullable int[] leftIndents, @Nullable int[] rightIndents)384         public Builder setIndents(@Nullable int[] leftIndents, @Nullable int[] rightIndents) {
385             mLeftIndents = leftIndents;
386             mRightIndents = rightIndents;
387             return this;
388         }
389 
390         /**
391          * Set paragraph justification mode. The default value is
392          * {@link Layout#JUSTIFICATION_MODE_NONE}. If the last line is too short for justification,
393          * the last line will be displayed with the alignment set by {@link #setAlignment}.
394          * When Justification mode is JUSTIFICATION_MODE_INTER_WORD, wordSpacing on the given
395          * {@link Paint} will be ignored. This behavior also affects Spans which change the
396          * wordSpacing.
397          *
398          * @param justificationMode justification mode for the paragraph.
399          * @return this builder, useful for chaining.
400          * @see Paint#setWordSpacing(float)
401          */
402         @NonNull
setJustificationMode(@ustificationMode int justificationMode)403         public Builder setJustificationMode(@JustificationMode int justificationMode) {
404             mJustificationMode = justificationMode;
405             return this;
406         }
407 
408         /**
409          * Sets whether the line spacing should be applied for the last line. Default value is
410          * {@code false}.
411          *
412          * @hide
413          */
414         @NonNull
setAddLastLineLineSpacing(boolean value)415         /* package */ Builder setAddLastLineLineSpacing(boolean value) {
416             mAddLastLineLineSpacing = value;
417             return this;
418         }
419 
420         /**
421          * Set the line break configuration. The line break will be passed to native used for
422          * calculating the text wrapping. The default value of the line break style is
423          * {@link LineBreakConfig#LINE_BREAK_STYLE_NONE}
424          *
425          * @param lineBreakConfig the line break configuration for text wrapping.
426          * @return this builder, useful for chaining.
427          * @see android.widget.TextView#setLineBreakStyle
428          * @see android.widget.TextView#setLineBreakWordStyle
429          */
430         @NonNull
setLineBreakConfig(@onNull LineBreakConfig lineBreakConfig)431         public Builder setLineBreakConfig(@NonNull LineBreakConfig lineBreakConfig) {
432             mLineBreakConfig = lineBreakConfig;
433             return this;
434         }
435 
436         /**
437          * Set true for using width of bounding box as a source of automatic line breaking and
438          * drawing.
439          *
440          * If this value is false, the Layout determines the drawing offset and automatic line
441          * breaking based on total advances. By setting true, use all joined glyph's bounding boxes
442          * as a source of text width.
443          *
444          * If the font has glyphs that have negative bearing X or its xMax is greater than advance,
445          * the glyph clipping can happen because the drawing area may be bigger. By setting this to
446          * true, the Layout will reserve more spaces for drawing.
447          *
448          * @param useBoundsForWidth True for using bounding box, false for advances.
449          * @return this builder instance
450          * @see Layout#getUseBoundsForWidth()
451          * @see Layout.Builder#setUseBoundsForWidth(boolean)
452          */
453         @SuppressLint("MissingGetterMatchingBuilder")  // The base class `Layout` has a getter.
454         @NonNull
455         @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
setUseBoundsForWidth(boolean useBoundsForWidth)456         public Builder setUseBoundsForWidth(boolean useBoundsForWidth) {
457             mUseBoundsForWidth = useBoundsForWidth;
458             return this;
459         }
460 
461         /**
462          * Set true for shifting the drawing x offset for showing overhang at the start position.
463          *
464          * This flag is ignored if the {@link #getUseBoundsForWidth()} is false.
465          *
466          * If this value is false, the Layout draws text from the zero even if there is a glyph
467          * stroke in a region where the x coordinate is negative.
468          *
469          * If this value is true, the Layout draws text with shifting the x coordinate of the
470          * drawing bounding box.
471          *
472          * This value is false by default.
473          *
474          * @param shiftDrawingOffsetForStartOverhang true for shifting the drawing offset for
475          *                                          showing the stroke that is in the region where
476          *                                          the x coordinate is negative.
477          * @see #setUseBoundsForWidth(boolean)
478          * @see #getUseBoundsForWidth()
479          */
480         @NonNull
481         // The corresponding getter is getShiftDrawingOffsetForStartOverhang()
482         @SuppressLint("MissingGetterMatchingBuilder")
483         @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
setShiftDrawingOffsetForStartOverhang( boolean shiftDrawingOffsetForStartOverhang)484         public Builder setShiftDrawingOffsetForStartOverhang(
485                 boolean shiftDrawingOffsetForStartOverhang) {
486             mShiftDrawingOffsetForStartOverhang = shiftDrawingOffsetForStartOverhang;
487             return this;
488         }
489 
490         /**
491          * Internal API that tells underlying line breaker that calculating bounding boxes even if
492          * the line break is performed with advances. This is useful for DynamicLayout internal
493          * implementation because it uses bounding box as well as advances.
494          * @hide
495          */
setCalculateBounds(boolean value)496         public Builder setCalculateBounds(boolean value) {
497             mCalculateBounds = value;
498             return this;
499         }
500 
501         /**
502          * Set the minimum font metrics used for line spacing.
503          *
504          * <p>
505          * {@code null} is the default value. If {@code null} is set or left as default, the
506          * font metrics obtained by {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)} is
507          * used.
508          *
509          * <p>
510          * The minimum meaning here is the minimum value of line spacing: maximum value of
511          * {@link Paint#ascent()}, minimum value of {@link Paint#descent()}.
512          *
513          * <p>
514          * By setting this value, each line will have minimum line spacing regardless of the text
515          * rendered. For example, usually Japanese script has larger vertical metrics than Latin
516          * script. By setting the metrics obtained by
517          * {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)} for Japanese or leave it
518          * {@code null} if the Paint's locale is Japanese, the line spacing for Japanese is reserved
519          * if the text is an English text. If the vertical metrics of the text is larger than
520          * Japanese, for example Burmese, the bigger font metrics is used.
521          *
522          * @param minimumFontMetrics A minimum font metrics. Passing {@code null} for using the
523          *                          value obtained by
524          *                          {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)}
525          * @see android.widget.TextView#setMinimumFontMetrics(Paint.FontMetrics)
526          * @see android.widget.TextView#getMinimumFontMetrics()
527          * @see Layout#getMinimumFontMetrics()
528          * @see Layout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
529          * @see DynamicLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
530          */
531         @NonNull
532         @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
setMinimumFontMetrics(@ullable Paint.FontMetrics minimumFontMetrics)533         public Builder setMinimumFontMetrics(@Nullable Paint.FontMetrics minimumFontMetrics) {
534             mMinimumFontMetrics = minimumFontMetrics;
535             return this;
536         }
537 
538         /**
539          * Build the {@link StaticLayout} after options have been set.
540          *
541          * <p>Note: the builder object must not be reused in any way after calling this
542          * method. Setting parameters after calling this method, or calling it a second
543          * time on the same builder object, will likely lead to unexpected results.
544          *
545          * @return the newly constructed {@link StaticLayout} object
546          */
547         @NonNull
build()548         public StaticLayout build() {
549             StaticLayout result = new StaticLayout(this, mIncludePad, mEllipsize != null
550                     ? COLUMNS_ELLIPSIZE : COLUMNS_NORMAL);
551             Builder.recycle(this);
552             return result;
553         }
554 
555         /**
556          * DO NOT USE THIS METHOD OTHER THAN DynamicLayout.
557          *
558          * This class generates a very weird StaticLayout only for getting a result of line break.
559          * Since DynamicLayout keeps StaticLayout reference in the static context for object
560          * recycling but keeping text reference in static context will end up with leaking Context
561          * due to TextWatcher via TextView.
562          *
563          * So, this is a dirty work around that creating StaticLayout without passing text reference
564          * to the super constructor, but calculating the text layout by calling generate function
565          * directly.
566          */
buildPartialStaticLayoutForDynamicLayout( boolean trackpadding, StaticLayout recycle)567         /* package */ @NonNull StaticLayout buildPartialStaticLayoutForDynamicLayout(
568                 boolean trackpadding, StaticLayout recycle) {
569             if (recycle == null) {
570                 recycle = new StaticLayout();
571             }
572             Trace.beginSection("Generating StaticLayout For DynamicLayout");
573             try {
574                 recycle.generate(this, mIncludePad, trackpadding);
575             } finally {
576                 Trace.endSection();
577             }
578             return recycle;
579         }
580 
581         private CharSequence mText;
582         private int mStart;
583         private int mEnd;
584         private TextPaint mPaint;
585         private int mWidth;
586         private Alignment mAlignment;
587         private TextDirectionHeuristic mTextDir;
588         private float mSpacingMult;
589         private float mSpacingAdd;
590         private boolean mIncludePad;
591         private boolean mFallbackLineSpacing;
592         private int mEllipsizedWidth;
593         private TextUtils.TruncateAt mEllipsize;
594         private int mMaxLines;
595         private int mBreakStrategy;
596         private int mHyphenationFrequency;
597         @Nullable private int[] mLeftIndents;
598         @Nullable private int[] mRightIndents;
599         private int mJustificationMode;
600         private boolean mAddLastLineLineSpacing;
601         private LineBreakConfig mLineBreakConfig = LineBreakConfig.NONE;
602         private boolean mUseBoundsForWidth;
603         private boolean mShiftDrawingOffsetForStartOverhang;
604         private boolean mCalculateBounds;
605         @Nullable private Paint.FontMetrics mMinimumFontMetrics;
606 
607         private final Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
608 
609         private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<>(3);
610     }
611 
612     /**
613      * DO NOT USE THIS CONSTRUCTOR OTHER THAN FOR DYNAMIC LAYOUT.
614      * See Builder#buildPartialStaticLayoutForDynamicLayout for the reason of this constructor.
615      */
StaticLayout()616     private StaticLayout() {
617         super(
618                 null,  // text
619                 null,  // paint
620                 0,  // width
621                 null, // alignment
622                 null, // textDir
623                 1, // spacing multiplier
624                 0, // spacing amount
625                 false, // include font padding
626                 false, // fallback line spacing
627                 0,  // ellipsized width
628                 null, // ellipsize
629                 1,  // maxLines
630                 BREAK_STRATEGY_SIMPLE,
631                 HYPHENATION_FREQUENCY_NONE,
632                 null,  // leftIndents
633                 null,  // rightIndents
634                 JUSTIFICATION_MODE_NONE,
635                 null,  // lineBreakConfig,
636                 false,  // useBoundsForWidth
637                 false,  // shiftDrawingOffsetForStartOverhang
638                 null  // minimumFontMetrics
639         );
640 
641         mColumns = COLUMNS_ELLIPSIZE;
642         mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2);
643         mLines  = ArrayUtils.newUnpaddedIntArray(2 * mColumns);
644     }
645 
646     /**
647      * @deprecated Use {@link Builder} instead.
648      */
649     @Deprecated
StaticLayout(CharSequence source, TextPaint paint, int width, Alignment align, float spacingmult, float spacingadd, boolean includepad)650     public StaticLayout(CharSequence source, TextPaint paint,
651                         int width,
652                         Alignment align, float spacingmult, float spacingadd,
653                         boolean includepad) {
654         this(source, 0, source.length(), paint, width, align,
655              spacingmult, spacingadd, includepad);
656     }
657 
658     /**
659      * @deprecated Use {@link Builder} instead.
660      */
661     @Deprecated
StaticLayout(CharSequence source, int bufstart, int bufend, TextPaint paint, int outerwidth, Alignment align, float spacingmult, float spacingadd, boolean includepad)662     public StaticLayout(CharSequence source, int bufstart, int bufend,
663                         TextPaint paint, int outerwidth,
664                         Alignment align,
665                         float spacingmult, float spacingadd,
666                         boolean includepad) {
667         this(source, bufstart, bufend, paint, outerwidth, align,
668              spacingmult, spacingadd, includepad, null, 0);
669     }
670 
671     /**
672      * @deprecated Use {@link Builder} instead.
673      */
674     @Deprecated
StaticLayout(CharSequence source, int bufstart, int bufend, TextPaint paint, int outerwidth, Alignment align, float spacingmult, float spacingadd, boolean includepad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth)675     public StaticLayout(CharSequence source, int bufstart, int bufend,
676             TextPaint paint, int outerwidth,
677             Alignment align,
678             float spacingmult, float spacingadd,
679             boolean includepad,
680             TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
681         this(source, bufstart, bufend, paint, outerwidth, align,
682                 TextDirectionHeuristics.FIRSTSTRONG_LTR,
683                 spacingmult, spacingadd, includepad, ellipsize, ellipsizedWidth, Integer.MAX_VALUE);
684     }
685 
686     /**
687      * @hide
688      * @deprecated Use {@link Builder} instead.
689      */
690     @Deprecated
691     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 117521430)
StaticLayout(CharSequence source, int bufstart, int bufend, TextPaint paint, int outerwidth, Alignment align, TextDirectionHeuristic textDir, float spacingmult, float spacingadd, boolean includepad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth, int maxLines)692     public StaticLayout(CharSequence source, int bufstart, int bufend,
693                         TextPaint paint, int outerwidth,
694                         Alignment align, TextDirectionHeuristic textDir,
695                         float spacingmult, float spacingadd,
696                         boolean includepad,
697                         TextUtils.TruncateAt ellipsize, int ellipsizedWidth, int maxLines) {
698         this(Builder.obtain(source, bufstart, bufend, paint, outerwidth)
699                 .setAlignment(align)
700                 .setTextDirection(textDir)
701                 .setLineSpacing(spacingadd, spacingmult)
702                 .setIncludePad(includepad)
703                 .setEllipsize(ellipsize)
704                 .setEllipsizedWidth(ellipsizedWidth)
705                 .setMaxLines(maxLines), includepad,
706                 ellipsize != null ? COLUMNS_ELLIPSIZE : COLUMNS_NORMAL);
707     }
708 
StaticLayout(Builder b, boolean trackPadding, int columnSize)709     private StaticLayout(Builder b, boolean trackPadding, int columnSize) {
710         super((b.mEllipsize == null) ? b.mText : (b.mText instanceof Spanned)
711                     ? new SpannedEllipsizer(b.mText) : new Ellipsizer(b.mText),
712                 b.mPaint, b.mWidth, b.mAlignment, b.mTextDir, b.mSpacingMult, b.mSpacingAdd,
713                 b.mIncludePad, b.mFallbackLineSpacing, b.mEllipsizedWidth, b.mEllipsize,
714                 b.mMaxLines, b.mBreakStrategy, b.mHyphenationFrequency, b.mLeftIndents,
715                 b.mRightIndents, b.mJustificationMode, b.mLineBreakConfig, b.mUseBoundsForWidth,
716                 b.mShiftDrawingOffsetForStartOverhang, b.mMinimumFontMetrics);
717 
718         mColumns = columnSize;
719         if (b.mEllipsize != null) {
720             Ellipsizer e = (Ellipsizer) getText();
721 
722             e.mLayout = this;
723             e.mWidth = b.mEllipsizedWidth;
724             e.mMethod = b.mEllipsize;
725         }
726 
727         mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2);
728         mLines  = ArrayUtils.newUnpaddedIntArray(2 * mColumns);
729         mMaximumVisibleLineCount = b.mMaxLines;
730 
731         mLeftIndents = b.mLeftIndents;
732         mRightIndents = b.mRightIndents;
733 
734         Trace.beginSection("Constructing StaticLayout");
735         try {
736             generate(b, b.mIncludePad, trackPadding);
737         } finally {
738             Trace.endSection();
739         }
740     }
741 
getBaseHyphenationFrequency(int frequency)742     private static int getBaseHyphenationFrequency(int frequency) {
743         switch (frequency) {
744             case Layout.HYPHENATION_FREQUENCY_FULL:
745             case Layout.HYPHENATION_FREQUENCY_FULL_FAST:
746                 return LineBreaker.HYPHENATION_FREQUENCY_FULL;
747             case Layout.HYPHENATION_FREQUENCY_NORMAL:
748             case Layout.HYPHENATION_FREQUENCY_NORMAL_FAST:
749                 return LineBreaker.HYPHENATION_FREQUENCY_NORMAL;
750             case Layout.HYPHENATION_FREQUENCY_NONE:
751             default:
752                 return LineBreaker.HYPHENATION_FREQUENCY_NONE;
753         }
754     }
755 
generate(Builder b, boolean includepad, boolean trackpad)756     /* package */ void generate(Builder b, boolean includepad, boolean trackpad) {
757         final CharSequence source = b.mText;
758         final int bufStart = b.mStart;
759         final int bufEnd = b.mEnd;
760         TextPaint paint = b.mPaint;
761         int outerWidth = b.mWidth;
762         TextDirectionHeuristic textDir = b.mTextDir;
763         float spacingmult = b.mSpacingMult;
764         float spacingadd = b.mSpacingAdd;
765         float ellipsizedWidth = b.mEllipsizedWidth;
766         TextUtils.TruncateAt ellipsize = b.mEllipsize;
767         final boolean addLastLineSpacing = b.mAddLastLineLineSpacing;
768 
769         int lineBreakCapacity = 0;
770         int[] breaks = null;
771         float[] lineWidths = null;
772         float[] ascents = null;
773         float[] descents = null;
774         boolean[] hasTabs = null;
775         int[] hyphenEdits = null;
776 
777         mLineCount = 0;
778         mEllipsized = false;
779         mMaxLineHeight = mMaximumVisibleLineCount < 1 ? 0 : DEFAULT_MAX_LINE_HEIGHT;
780         mDrawingBounds = null;
781         boolean isFallbackLineSpacing = b.mFallbackLineSpacing;
782 
783         int v = 0;
784         boolean needMultiply = (spacingmult != 1 || spacingadd != 0);
785 
786         Paint.FontMetricsInt fm = b.mFontMetricsInt;
787         int[] chooseHtv = null;
788 
789         final int[] indents;
790         if (mLeftIndents != null || mRightIndents != null) {
791             final int leftLen = mLeftIndents == null ? 0 : mLeftIndents.length;
792             final int rightLen = mRightIndents == null ? 0 : mRightIndents.length;
793             final int indentsLen = Math.max(leftLen, rightLen);
794             indents = new int[indentsLen];
795             for (int i = 0; i < leftLen; i++) {
796                 indents[i] = mLeftIndents[i];
797             }
798             for (int i = 0; i < rightLen; i++) {
799                 indents[i] += mRightIndents[i];
800             }
801         } else {
802             indents = null;
803         }
804 
805         int defaultTop;
806         final int defaultAscent;
807         final int defaultDescent;
808         int defaultBottom;
809         if (Flags.fixLineHeightForLocale() && b.mMinimumFontMetrics != null) {
810             defaultTop = (int) Math.floor(b.mMinimumFontMetrics.top);
811             defaultAscent = Math.round(b.mMinimumFontMetrics.ascent);
812             defaultDescent = Math.round(b.mMinimumFontMetrics.descent);
813             defaultBottom = (int) Math.ceil(b.mMinimumFontMetrics.bottom);
814 
815             // Because the font metrics is provided by public APIs, adjust the top/bottom with
816             // ascent/descent: top must be smaller than ascent, bottom must be larger than descent.
817             defaultTop = Math.min(defaultTop, defaultAscent);
818             defaultBottom = Math.max(defaultBottom, defaultDescent);
819         } else {
820             defaultTop = 0;
821             defaultAscent = 0;
822             defaultDescent = 0;
823             defaultBottom = 0;
824         }
825 
826         final LineBreaker lineBreaker = new LineBreaker.Builder()
827                 .setBreakStrategy(b.mBreakStrategy)
828                 .setHyphenationFrequency(getBaseHyphenationFrequency(b.mHyphenationFrequency))
829                 // TODO: Support more justification mode, e.g. letter spacing, stretching.
830                 .setJustificationMode(b.mJustificationMode)
831                 .setIndents(indents)
832                 .setUseBoundsForWidth(b.mUseBoundsForWidth)
833                 .build();
834 
835         LineBreaker.ParagraphConstraints constraints =
836                 new LineBreaker.ParagraphConstraints();
837 
838         PrecomputedText.ParagraphInfo[] paragraphInfo = null;
839         final Spanned spanned = (source instanceof Spanned) ? (Spanned) source : null;
840         if (source instanceof PrecomputedText) {
841             PrecomputedText precomputed = (PrecomputedText) source;
842             final @PrecomputedText.Params.CheckResultUsableResult int checkResult =
843                     precomputed.checkResultUsable(bufStart, bufEnd, textDir, paint,
844                             b.mBreakStrategy, b.mHyphenationFrequency, b.mLineBreakConfig);
845             switch (checkResult) {
846                 case PrecomputedText.Params.UNUSABLE:
847                     break;
848                 case PrecomputedText.Params.NEED_RECOMPUTE:
849                     final PrecomputedText.Params newParams =
850                             new PrecomputedText.Params.Builder(paint)
851                                 .setBreakStrategy(b.mBreakStrategy)
852                                 .setHyphenationFrequency(b.mHyphenationFrequency)
853                                 .setTextDirection(textDir)
854                                 .setLineBreakConfig(b.mLineBreakConfig)
855                                 .build();
856                     precomputed = PrecomputedText.create(precomputed, newParams);
857                     paragraphInfo = precomputed.getParagraphInfo();
858                     break;
859                 case PrecomputedText.Params.USABLE:
860                     // Some parameters are different from the ones when measured text is created.
861                     paragraphInfo = precomputed.getParagraphInfo();
862                     break;
863             }
864         }
865 
866         if (paragraphInfo == null) {
867             final PrecomputedText.Params param = new PrecomputedText.Params(paint,
868                     b.mLineBreakConfig, textDir, b.mBreakStrategy, b.mHyphenationFrequency);
869             paragraphInfo = PrecomputedText.createMeasuredParagraphs(source, param, bufStart,
870                     bufEnd, false /* computeLayout */, b.mCalculateBounds);
871         }
872 
873         for (int paraIndex = 0; paraIndex < paragraphInfo.length; paraIndex++) {
874             final int paraStart = paraIndex == 0
875                     ? bufStart : paragraphInfo[paraIndex - 1].paragraphEnd;
876             final int paraEnd = paragraphInfo[paraIndex].paragraphEnd;
877 
878             int firstWidthLineCount = 1;
879             int firstWidth = outerWidth;
880             int restWidth = outerWidth;
881 
882             LineHeightSpan[] chooseHt = null;
883             if (spanned != null) {
884                 LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
885                         LeadingMarginSpan.class);
886                 for (int i = 0; i < sp.length; i++) {
887                     LeadingMarginSpan lms = sp[i];
888                     firstWidth -= sp[i].getLeadingMargin(true);
889                     restWidth -= sp[i].getLeadingMargin(false);
890 
891                     // LeadingMarginSpan2 is odd.  The count affects all
892                     // leading margin spans, not just this particular one
893                     if (lms instanceof LeadingMarginSpan2) {
894                         LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
895                         firstWidthLineCount = Math.max(firstWidthLineCount,
896                                 lms2.getLeadingMarginLineCount());
897                     }
898                 }
899 
900                 chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
901 
902                 if (chooseHt.length == 0) {
903                     chooseHt = null; // So that out() would not assume it has any contents
904                 } else {
905                     if (chooseHtv == null || chooseHtv.length < chooseHt.length) {
906                         chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
907                     }
908 
909                     for (int i = 0; i < chooseHt.length; i++) {
910                         int o = spanned.getSpanStart(chooseHt[i]);
911 
912                         if (o < paraStart) {
913                             // starts in this layout, before the
914                             // current paragraph
915 
916                             chooseHtv[i] = getLineTop(getLineForOffset(o));
917                         } else {
918                             // starts in this paragraph
919 
920                             chooseHtv[i] = v;
921                         }
922                     }
923                 }
924             }
925             // tab stop locations
926             float[] variableTabStops = null;
927             if (spanned != null) {
928                 TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
929                         paraEnd, TabStopSpan.class);
930                 if (spans.length > 0) {
931                     float[] stops = new float[spans.length];
932                     for (int i = 0; i < spans.length; i++) {
933                         stops[i] = (float) spans[i].getTabStop();
934                     }
935                     Arrays.sort(stops, 0, stops.length);
936                     variableTabStops = stops;
937                 }
938             }
939 
940             final MeasuredParagraph measuredPara = paragraphInfo[paraIndex].measured;
941             final char[] chs = measuredPara.getChars();
942             final int[] spanEndCache = measuredPara.getSpanEndCache().getRawArray();
943             final int[] fmCache = measuredPara.getFontMetrics().getRawArray();
944 
945             constraints.setWidth(restWidth);
946             constraints.setIndent(firstWidth, firstWidthLineCount);
947             constraints.setTabStops(variableTabStops, TAB_INCREMENT);
948 
949             LineBreaker.Result res = lineBreaker.computeLineBreaks(
950                     measuredPara.getMeasuredText(), constraints, mLineCount);
951             int breakCount = res.getLineCount();
952             if (lineBreakCapacity < breakCount) {
953                 lineBreakCapacity = breakCount;
954                 breaks = new int[lineBreakCapacity];
955                 lineWidths = new float[lineBreakCapacity];
956                 ascents = new float[lineBreakCapacity];
957                 descents = new float[lineBreakCapacity];
958                 hasTabs = new boolean[lineBreakCapacity];
959                 hyphenEdits = new int[lineBreakCapacity];
960             }
961 
962             for (int i = 0; i < breakCount; ++i) {
963                 breaks[i] = res.getLineBreakOffset(i);
964                 lineWidths[i] = res.getLineWidth(i);
965                 ascents[i] = res.getLineAscent(i);
966                 descents[i] = res.getLineDescent(i);
967                 hasTabs[i] = res.hasLineTab(i);
968                 hyphenEdits[i] =
969                     packHyphenEdit(res.getStartLineHyphenEdit(i), res.getEndLineHyphenEdit(i));
970             }
971 
972             final int remainingLineCount = mMaximumVisibleLineCount - mLineCount;
973             final boolean ellipsisMayBeApplied = ellipsize != null
974                     && (ellipsize == TextUtils.TruncateAt.END
975                         || (mMaximumVisibleLineCount == 1
976                                 && ellipsize != TextUtils.TruncateAt.MARQUEE));
977             if (0 < remainingLineCount && remainingLineCount < breakCount
978                     && ellipsisMayBeApplied) {
979                 // Calculate width
980                 float width = 0;
981                 boolean hasTab = false;  // XXX May need to also have starting hyphen edit
982                 for (int i = remainingLineCount - 1; i < breakCount; i++) {
983                     if (i == breakCount - 1) {
984                         width += lineWidths[i];
985                     } else {
986                         for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) {
987                             width += measuredPara.getCharWidthAt(j);
988                         }
989                     }
990                     hasTab |= hasTabs[i];
991                 }
992                 // Treat the last line and overflowed lines as a single line.
993                 breaks[remainingLineCount - 1] = breaks[breakCount - 1];
994                 lineWidths[remainingLineCount - 1] = width;
995                 hasTabs[remainingLineCount - 1] = hasTab;
996 
997                 breakCount = remainingLineCount;
998             }
999 
1000             // here is the offset of the starting character of the line we are currently
1001             // measuring
1002             int here = paraStart;
1003 
1004             int fmTop = defaultTop;
1005             int fmBottom = defaultBottom;
1006             int fmAscent = defaultAscent;
1007             int fmDescent = defaultDescent;
1008             int fmCacheIndex = 0;
1009             int spanEndCacheIndex = 0;
1010             int breakIndex = 0;
1011             for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
1012                 // retrieve end of span
1013                 spanEnd = spanEndCache[spanEndCacheIndex++];
1014 
1015                 // retrieve cached metrics, order matches above
1016                 fm.top = fmCache[fmCacheIndex * 4 + 0];
1017                 fm.bottom = fmCache[fmCacheIndex * 4 + 1];
1018                 fm.ascent = fmCache[fmCacheIndex * 4 + 2];
1019                 fm.descent = fmCache[fmCacheIndex * 4 + 3];
1020                 fmCacheIndex++;
1021 
1022                 if (fm.top < fmTop) {
1023                     fmTop = fm.top;
1024                 }
1025                 if (fm.ascent < fmAscent) {
1026                     fmAscent = fm.ascent;
1027                 }
1028                 if (fm.descent > fmDescent) {
1029                     fmDescent = fm.descent;
1030                 }
1031                 if (fm.bottom > fmBottom) {
1032                     fmBottom = fm.bottom;
1033                 }
1034 
1035                 // skip breaks ending before current span range
1036                 while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
1037                     breakIndex++;
1038                 }
1039 
1040                 while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
1041                     int endPos = paraStart + breaks[breakIndex];
1042 
1043                     boolean moreChars = (endPos < bufEnd);
1044 
1045                     final int ascent = isFallbackLineSpacing
1046                             ? Math.min(fmAscent, Math.round(ascents[breakIndex]))
1047                             : fmAscent;
1048                     final int descent = isFallbackLineSpacing
1049                             ? Math.max(fmDescent, Math.round(descents[breakIndex]))
1050                             : fmDescent;
1051 
1052                     // The fallback ascent/descent may be larger than top/bottom of the default font
1053                     // metrics. Adjust top/bottom with ascent/descent for avoiding unexpected
1054                     // clipping.
1055                     if (isFallbackLineSpacing) {
1056                         if (ascent < fmTop) {
1057                             fmTop = ascent;
1058                         }
1059                         if (descent > fmBottom) {
1060                             fmBottom = descent;
1061                         }
1062                     }
1063 
1064                     v = out(source, here, endPos,
1065                             ascent, descent, fmTop, fmBottom,
1066                             v, spacingmult, spacingadd, chooseHt, chooseHtv, fm,
1067                             hasTabs[breakIndex], hyphenEdits[breakIndex], needMultiply,
1068                             measuredPara, bufEnd, includepad, trackpad, addLastLineSpacing, chs,
1069                             paraStart, ellipsize, ellipsizedWidth, lineWidths[breakIndex],
1070                             paint, moreChars);
1071 
1072                     if (endPos < spanEnd) {
1073                         // preserve metrics for current span
1074                         fmTop = Math.min(defaultTop, fm.top);
1075                         fmBottom = Math.max(defaultBottom, fm.bottom);
1076                         fmAscent = Math.min(defaultAscent, fm.ascent);
1077                         fmDescent = Math.max(defaultDescent, fm.descent);
1078                     } else {
1079                         fmTop = fmBottom = fmAscent = fmDescent = 0;
1080                     }
1081 
1082                     here = endPos;
1083                     breakIndex++;
1084 
1085                     if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) {
1086                         return;
1087                     }
1088                 }
1089             }
1090 
1091             if (paraEnd == bufEnd) {
1092                 break;
1093             }
1094         }
1095 
1096         if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE)
1097                 && mLineCount < mMaximumVisibleLineCount) {
1098             final MeasuredParagraph measuredPara =
1099                     MeasuredParagraph.buildForBidi(source, bufEnd, bufEnd, textDir, null);
1100             if (defaultAscent != 0 && defaultDescent != 0) {
1101                 fm.top = defaultTop;
1102                 fm.ascent = defaultAscent;
1103                 fm.descent = defaultDescent;
1104                 fm.bottom = defaultBottom;
1105             } else {
1106                 paint.getFontMetricsInt(fm);
1107             }
1108 
1109             v = out(source,
1110                     bufEnd, bufEnd, fm.ascent, fm.descent,
1111                     fm.top, fm.bottom,
1112                     v,
1113                     spacingmult, spacingadd, null,
1114                     null, fm, false, 0,
1115                     needMultiply, measuredPara, bufEnd,
1116                     includepad, trackpad, addLastLineSpacing, null,
1117                     bufStart, ellipsize,
1118                     ellipsizedWidth, 0, paint, false);
1119         }
1120     }
1121 
1122     private int out(final CharSequence text, final int start, final int end, int above, int below,
1123             int top, int bottom, int v, final float spacingmult, final float spacingadd,
1124             final LineHeightSpan[] chooseHt, final int[] chooseHtv, final Paint.FontMetricsInt fm,
1125             final boolean hasTab, final int hyphenEdit, final boolean needMultiply,
1126             @NonNull final MeasuredParagraph measured,
1127             final int bufEnd, final boolean includePad, final boolean trackPad,
1128             final boolean addLastLineLineSpacing, final char[] chs,
1129             final int widthStart, final TextUtils.TruncateAt ellipsize, final float ellipsisWidth,
1130             final float textWidth, final TextPaint paint, final boolean moreChars) {
1131         final int j = mLineCount;
1132         final int off = j * mColumns;
1133         final int want = off + mColumns + TOP;
1134         int[] lines = mLines;
1135         final int dir = measured.getParagraphDir();
1136 
1137         if (want >= lines.length) {
1138             final int[] grow = ArrayUtils.newUnpaddedIntArray(GrowingArrayUtils.growSize(want));
1139             System.arraycopy(lines, 0, grow, 0, lines.length);
1140             mLines = grow;
1141             lines = grow;
1142         }
1143 
1144         if (j >= mLineDirections.length) {
1145             final Directions[] grow = ArrayUtils.newUnpaddedArray(Directions.class,
1146                     GrowingArrayUtils.growSize(j));
1147             System.arraycopy(mLineDirections, 0, grow, 0, mLineDirections.length);
1148             mLineDirections = grow;
1149         }
1150 
1151         if (chooseHt != null) {
1152             fm.ascent = above;
1153             fm.descent = below;
1154             fm.top = top;
1155             fm.bottom = bottom;
1156 
1157             for (int i = 0; i < chooseHt.length; i++) {
1158                 if (chooseHt[i] instanceof LineHeightSpan.WithDensity) {
1159                     ((LineHeightSpan.WithDensity) chooseHt[i])
1160                             .chooseHeight(text, start, end, chooseHtv[i], v, fm, paint);
1161                 } else {
1162                     chooseHt[i].chooseHeight(text, start, end, chooseHtv[i], v, fm);
1163                 }
1164             }
1165 
1166             above = fm.ascent;
1167             below = fm.descent;
1168             top = fm.top;
1169             bottom = fm.bottom;
1170         }
1171 
1172         boolean firstLine = (j == 0);
1173         boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);
1174 
1175         if (ellipsize != null) {
1176             // If there is only one line, then do any type of ellipsis except when it is MARQUEE
1177             // if there are multiple lines, just allow END ellipsis on the last line
1178             boolean forceEllipsis = moreChars && (mLineCount + 1 == mMaximumVisibleLineCount);
1179 
1180             boolean doEllipsis =
1181                     (((mMaximumVisibleLineCount == 1 && moreChars) || (firstLine && !moreChars)) &&
1182                             ellipsize != TextUtils.TruncateAt.MARQUEE) ||
1183                     (!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
1184                             ellipsize == TextUtils.TruncateAt.END);
1185             if (doEllipsis) {
1186                 calculateEllipsis(start, end, measured, widthStart,
1187                         ellipsisWidth, ellipsize, j,
1188                         textWidth, paint, forceEllipsis);
1189             } else {
1190                 mLines[mColumns * j + ELLIPSIS_START] = 0;
1191                 mLines[mColumns * j + ELLIPSIS_COUNT] = 0;
1192             }
1193         }
1194 
1195         final boolean lastLine;
1196         if (mEllipsized) {
1197             lastLine = true;
1198         } else {
1199             final boolean lastCharIsNewLine = widthStart != bufEnd && bufEnd > 0
1200                     && text.charAt(bufEnd - 1) == CHAR_NEW_LINE;
1201             if (end == bufEnd && !lastCharIsNewLine) {
1202                 lastLine = true;
1203             } else if (start == bufEnd && lastCharIsNewLine) {
1204                 lastLine = true;
1205             } else {
1206                 lastLine = false;
1207             }
1208         }
1209 
1210         if (firstLine) {
1211             if (trackPad) {
1212                 mTopPadding = top - above;
1213             }
1214 
1215             if (includePad) {
1216                 above = top;
1217             }
1218         }
1219 
1220         int extra;
1221 
1222         if (lastLine) {
1223             if (trackPad) {
1224                 mBottomPadding = bottom - below;
1225             }
1226 
1227             if (includePad) {
1228                 below = bottom;
1229             }
1230         }
1231 
1232         if (needMultiply && (addLastLineLineSpacing || !lastLine)) {
1233             double ex = (below - above) * (spacingmult - 1) + spacingadd;
1234             if (ex >= 0) {
1235                 extra = (int)(ex + EXTRA_ROUNDING);
1236             } else {
1237                 extra = -(int)(-ex + EXTRA_ROUNDING);
1238             }
1239         } else {
1240             extra = 0;
1241         }
1242 
1243         lines[off + START] = start;
1244         lines[off + TOP] = v;
1245         lines[off + DESCENT] = below + extra;
1246         lines[off + EXTRA] = extra;
1247 
1248         // special case for non-ellipsized last visible line when maxLines is set
1249         // store the height as if it was ellipsized
1250         if (!mEllipsized && currentLineIsTheLastVisibleOne) {
1251             // below calculation as if it was the last line
1252             int maxLineBelow = includePad ? bottom : below;
1253             // similar to the calculation of v below, without the extra.
1254             mMaxLineHeight = v + (maxLineBelow - above);
1255         }
1256 
1257         v += (below - above) + extra;
1258         lines[off + mColumns + START] = end;
1259         lines[off + mColumns + TOP] = v;
1260 
1261         // TODO: could move TAB to share same column as HYPHEN, simplifying this code and gaining
1262         // one bit for start field
1263         lines[off + TAB] |= hasTab ? TAB_MASK : 0;
1264         if (mEllipsized) {
1265             if (ellipsize == TextUtils.TruncateAt.START) {
1266                 lines[off + HYPHEN] = packHyphenEdit(Paint.START_HYPHEN_EDIT_NO_EDIT,
1267                         unpackEndHyphenEdit(hyphenEdit));
1268             } else if (ellipsize == TextUtils.TruncateAt.END) {
1269                 lines[off + HYPHEN] = packHyphenEdit(unpackStartHyphenEdit(hyphenEdit),
1270                         Paint.END_HYPHEN_EDIT_NO_EDIT);
1271             } else {  // Middle and marquee ellipsize should show text at the start/end edge.
1272                 lines[off + HYPHEN] = packHyphenEdit(
1273                         Paint.START_HYPHEN_EDIT_NO_EDIT, Paint.END_HYPHEN_EDIT_NO_EDIT);
1274             }
1275         } else {
1276             lines[off + HYPHEN] = hyphenEdit;
1277         }
1278 
1279         lines[off + DIR] |= dir << DIR_SHIFT;
1280         mLineDirections[j] = measured.getDirections(start - widthStart, end - widthStart);
1281 
1282         mLineCount++;
1283         return v;
1284     }
1285 
1286     private void calculateEllipsis(int lineStart, int lineEnd,
1287                                    MeasuredParagraph measured, int widthStart,
1288                                    float avail, TextUtils.TruncateAt where,
1289                                    int line, float textWidth, TextPaint paint,
1290                                    boolean forceEllipsis) {
1291         avail -= getTotalInsets(line);
1292         if (textWidth <= avail && !forceEllipsis) {
1293             // Everything fits!
1294             mLines[mColumns * line + ELLIPSIS_START] = 0;
1295             mLines[mColumns * line + ELLIPSIS_COUNT] = 0;
1296             return;
1297         }
1298 
1299         float ellipsisWidth = paint.measureText(TextUtils.getEllipsisString(where));
1300         int ellipsisStart = 0;
1301         int ellipsisCount = 0;
1302         int len = lineEnd - lineStart;
1303 
1304         // We only support start ellipsis on a single line
1305         if (where == TextUtils.TruncateAt.START) {
1306             if (mMaximumVisibleLineCount == 1) {
1307                 float sum = 0;
1308                 int i;
1309 
1310                 for (i = len; i > 0; i--) {
1311                     float w = measured.getCharWidthAt(i - 1 + lineStart - widthStart);
1312                     if (w + sum + ellipsisWidth > avail) {
1313                         while (i < len
1314                                 && measured.getCharWidthAt(i + lineStart - widthStart) == 0.0f) {
1315                             i++;
1316                         }
1317                         break;
1318                     }
1319 
1320                     sum += w;
1321                 }
1322 
1323                 ellipsisStart = 0;
1324                 ellipsisCount = i;
1325             } else {
1326                 if (Log.isLoggable(TAG, Log.WARN)) {
1327                     Log.w(TAG, "Start Ellipsis only supported with one line");
1328                 }
1329             }
1330         } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE ||
1331                 where == TextUtils.TruncateAt.END_SMALL) {
1332             float sum = 0;
1333             int i;
1334 
1335             for (i = 0; i < len; i++) {
1336                 float w = measured.getCharWidthAt(i + lineStart - widthStart);
1337 
1338                 if (w + sum + ellipsisWidth > avail) {
1339                     break;
1340                 }
1341 
1342                 sum += w;
1343             }
1344 
1345             ellipsisStart = i;
1346             ellipsisCount = len - i;
1347             if (forceEllipsis && ellipsisCount == 0 && len > 0) {
1348                 ellipsisStart = len - 1;
1349                 ellipsisCount = 1;
1350             }
1351         } else {
1352             // where = TextUtils.TruncateAt.MIDDLE We only support middle ellipsis on a single line
1353             if (mMaximumVisibleLineCount == 1) {
1354                 float lsum = 0, rsum = 0;
1355                 int left = 0, right = len;
1356 
1357                 float ravail = (avail - ellipsisWidth) / 2;
1358                 for (right = len; right > 0; right--) {
1359                     float w = measured.getCharWidthAt(right - 1 + lineStart - widthStart);
1360 
1361                     if (w + rsum > ravail) {
1362                         while (right < len
1363                                 && measured.getCharWidthAt(right + lineStart - widthStart)
1364                                     == 0.0f) {
1365                             right++;
1366                         }
1367                         break;
1368                     }
1369                     rsum += w;
1370                 }
1371 
1372                 float lavail = avail - ellipsisWidth - rsum;
1373                 for (left = 0; left < right; left++) {
1374                     float w = measured.getCharWidthAt(left + lineStart - widthStart);
1375 
1376                     if (w + lsum > lavail) {
1377                         break;
1378                     }
1379 
1380                     lsum += w;
1381                 }
1382 
1383                 ellipsisStart = left;
1384                 ellipsisCount = right - left;
1385             } else {
1386                 if (Log.isLoggable(TAG, Log.WARN)) {
1387                     Log.w(TAG, "Middle Ellipsis only supported with one line");
1388                 }
1389             }
1390         }
1391         mEllipsized = true;
1392         mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
1393         mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
1394     }
1395 
1396     private float getTotalInsets(int line) {
1397         int totalIndent = 0;
1398         if (mLeftIndents != null) {
1399             totalIndent = mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1400         }
1401         if (mRightIndents != null) {
1402             totalIndent += mRightIndents[Math.min(line, mRightIndents.length - 1)];
1403         }
1404         return totalIndent;
1405     }
1406 
1407     // Override the base class so we can directly access our members,
1408     // rather than relying on member functions.
1409     // The logic mirrors that of Layout.getLineForVertical
1410     // FIXME: It may be faster to do a linear search for layouts without many lines.
1411     @Override
1412     public int getLineForVertical(int vertical) {
1413         int high = mLineCount;
1414         int low = -1;
1415         int guess;
1416         int[] lines = mLines;
1417         while (high - low > 1) {
1418             guess = (high + low) >> 1;
1419             if (lines[mColumns * guess + TOP] > vertical){
1420                 high = guess;
1421             } else {
1422                 low = guess;
1423             }
1424         }
1425         if (low < 0) {
1426             return 0;
1427         } else {
1428             return low;
1429         }
1430     }
1431 
1432     @Override
1433     public int getLineCount() {
1434         return mLineCount;
1435     }
1436 
1437     @Override
1438     public int getLineTop(int line) {
1439         return mLines[mColumns * line + TOP];
1440     }
1441 
1442     /**
1443      * @hide
1444      */
1445     @Override
1446     public int getLineExtra(int line) {
1447         return mLines[mColumns * line + EXTRA];
1448     }
1449 
1450     @Override
1451     public int getLineDescent(int line) {
1452         return mLines[mColumns * line + DESCENT];
1453     }
1454 
1455     @Override
1456     public int getLineStart(int line) {
1457         return mLines[mColumns * line + START] & START_MASK;
1458     }
1459 
1460     @Override
1461     public int getParagraphDirection(int line) {
1462         return mLines[mColumns * line + DIR] >> DIR_SHIFT;
1463     }
1464 
1465     @Override
1466     public boolean getLineContainsTab(int line) {
1467         return (mLines[mColumns * line + TAB] & TAB_MASK) != 0;
1468     }
1469 
1470     @Override
1471     public final Directions getLineDirections(int line) {
1472         if (line > getLineCount()) {
1473             throw new ArrayIndexOutOfBoundsException();
1474         }
1475         return mLineDirections[line];
1476     }
1477 
1478     @Override
1479     public int getTopPadding() {
1480         return mTopPadding;
1481     }
1482 
1483     @Override
1484     public int getBottomPadding() {
1485         return mBottomPadding;
1486     }
1487 
1488     // To store into single int field, pack the pair of start and end hyphen edit.
1489     static int packHyphenEdit(
1490             @Paint.StartHyphenEdit int start, @Paint.EndHyphenEdit int end) {
1491         return start << START_HYPHEN_BITS_SHIFT | end;
1492     }
1493 
1494     static int unpackStartHyphenEdit(int packedHyphenEdit) {
1495         return (packedHyphenEdit & START_HYPHEN_MASK) >> START_HYPHEN_BITS_SHIFT;
1496     }
1497 
1498     static int unpackEndHyphenEdit(int packedHyphenEdit) {
1499         return packedHyphenEdit & END_HYPHEN_MASK;
1500     }
1501 
1502     /**
1503      * Returns the start hyphen edit value for this line.
1504      *
1505      * @param lineNumber a line number
1506      * @return A start hyphen edit value.
1507      * @hide
1508      */
1509     @Override
1510     public @Paint.StartHyphenEdit int getStartHyphenEdit(int lineNumber) {
1511         return unpackStartHyphenEdit(mLines[mColumns * lineNumber + HYPHEN] & HYPHEN_MASK);
1512     }
1513 
1514     /**
1515      * Returns the packed hyphen edit value for this line.
1516      *
1517      * @param lineNumber a line number
1518      * @return An end hyphen edit value.
1519      * @hide
1520      */
1521     @Override
1522     public @Paint.EndHyphenEdit int getEndHyphenEdit(int lineNumber) {
1523         return unpackEndHyphenEdit(mLines[mColumns * lineNumber + HYPHEN] & HYPHEN_MASK);
1524     }
1525 
1526     /**
1527      * @hide
1528      */
1529     @Override
1530     public int getIndentAdjust(int line, Alignment align) {
1531         if (align == Alignment.ALIGN_LEFT) {
1532             if (mLeftIndents == null) {
1533                 return 0;
1534             } else {
1535                 return mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1536             }
1537         } else if (align == Alignment.ALIGN_RIGHT) {
1538             if (mRightIndents == null) {
1539                 return 0;
1540             } else {
1541                 return -mRightIndents[Math.min(line, mRightIndents.length - 1)];
1542             }
1543         } else if (align == Alignment.ALIGN_CENTER) {
1544             int left = 0;
1545             if (mLeftIndents != null) {
1546                 left = mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1547             }
1548             int right = 0;
1549             if (mRightIndents != null) {
1550                 right = mRightIndents[Math.min(line, mRightIndents.length - 1)];
1551             }
1552             return (left - right) >> 1;
1553         } else {
1554             throw new AssertionError("unhandled alignment " + align);
1555         }
1556     }
1557 
1558     @Override
1559     public int getEllipsisCount(int line) {
1560         if (mColumns < COLUMNS_ELLIPSIZE) {
1561             return 0;
1562         }
1563 
1564         return mLines[mColumns * line + ELLIPSIS_COUNT];
1565     }
1566 
1567     @Override
1568     public int getEllipsisStart(int line) {
1569         if (mColumns < COLUMNS_ELLIPSIZE) {
1570             return 0;
1571         }
1572 
1573         return mLines[mColumns * line + ELLIPSIS_START];
1574     }
1575 
1576     @Override
1577     @NonNull
1578     public RectF computeDrawingBoundingBox() {
1579         // Cache the drawing bounds result because it does not change after created.
1580         if (mDrawingBounds == null) {
1581             mDrawingBounds = super.computeDrawingBoundingBox();
1582         }
1583         return mDrawingBounds;
1584     }
1585 
1586     /**
1587      * Return the total height of this layout.
1588      *
1589      * @param cap if true and max lines is set, returns the height of the layout at the max lines.
1590      *
1591      * @hide
1592      */
1593     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
1594     public int getHeight(boolean cap) {
1595         if (cap && mLineCount > mMaximumVisibleLineCount && mMaxLineHeight == -1
1596                 && Log.isLoggable(TAG, Log.WARN)) {
1597             Log.w(TAG, "maxLineHeight should not be -1. "
1598                     + " maxLines:" + mMaximumVisibleLineCount
1599                     + " lineCount:" + mLineCount);
1600         }
1601 
1602         return cap && mLineCount > mMaximumVisibleLineCount && mMaxLineHeight != -1
1603                 ? mMaxLineHeight : super.getHeight();
1604     }
1605 
1606     @UnsupportedAppUsage
1607     private int mLineCount;
1608     private int mTopPadding, mBottomPadding;
1609     @UnsupportedAppUsage
1610     private int mColumns;
1611     private RectF mDrawingBounds = null;  // lazy calculation.
1612 
1613     /**
1614      * Keeps track if ellipsize is applied to the text.
1615      */
1616     private boolean mEllipsized;
1617 
1618     /**
1619      * If maxLines is set, ellipsize is not set, and the actual line count of text is greater than
1620      * or equal to maxLine, this variable holds the ideal visual height of the maxLine'th line
1621      * starting from the top of the layout. If maxLines is not set its value will be -1.
1622      *
1623      * The value is the same as getLineTop(maxLines) for ellipsized version where structurally no
1624      * more than maxLines is contained.
1625      */
1626     private int mMaxLineHeight = DEFAULT_MAX_LINE_HEIGHT;
1627 
1628     private static final int COLUMNS_NORMAL = 5;
1629     private static final int COLUMNS_ELLIPSIZE = 7;
1630     private static final int START = 0;
1631     private static final int DIR = START;
1632     private static final int TAB = START;
1633     private static final int TOP = 1;
1634     private static final int DESCENT = 2;
1635     private static final int EXTRA = 3;
1636     private static final int HYPHEN = 4;
1637     @UnsupportedAppUsage
1638     private static final int ELLIPSIS_START = 5;
1639     private static final int ELLIPSIS_COUNT = 6;
1640 
1641     @UnsupportedAppUsage
1642     private int[] mLines;
1643     @UnsupportedAppUsage
1644     private Directions[] mLineDirections;
1645     @UnsupportedAppUsage
1646     private int mMaximumVisibleLineCount = Integer.MAX_VALUE;
1647 
1648     private static final int START_MASK = 0x1FFFFFFF;
1649     private static final int DIR_SHIFT  = 30;
1650     private static final int TAB_MASK   = 0x20000000;
1651     private static final int HYPHEN_MASK = 0xFF;
1652     private static final int START_HYPHEN_BITS_SHIFT = 3;
1653     private static final int START_HYPHEN_MASK = 0x18; // 0b11000
1654     private static final int END_HYPHEN_MASK = 0x7;  // 0b00111
1655 
1656     private static final float TAB_INCREMENT = 20; // same as Layout, but that's private
1657 
1658     private static final char CHAR_NEW_LINE = '\n';
1659 
1660     private static final double EXTRA_ROUNDING = 0.5;
1661 
1662     private static final int DEFAULT_MAX_LINE_HEIGHT = -1;
1663 
1664     // Unused, here because of gray list private API accesses.
1665     /*package*/ static class LineBreaks {
1666         private static final int INITIAL_SIZE = 16;
1667         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
1668         public int[] breaks = new int[INITIAL_SIZE];
1669         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
1670         public float[] widths = new float[INITIAL_SIZE];
1671         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
1672         public float[] ascents = new float[INITIAL_SIZE];
1673         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
1674         public float[] descents = new float[INITIAL_SIZE];
1675         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
1676         public int[] flags = new int[INITIAL_SIZE]; // hasTab
1677         // breaks, widths, and flags should all have the same length
1678     }
1679 
1680     @Nullable private int[] mLeftIndents;
1681     @Nullable private int[] mRightIndents;
1682 }
1683