blob: 69cfc03a7ff856f65e60d416232833243c091c05 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.text;
18
Roozbeh Pournader22a167c2017-08-21 12:53:44 -070019import android.annotation.FloatRange;
20import android.annotation.IntRange;
21import android.annotation.NonNull;
Raph Levien531c30c2015-04-30 16:29:59 -070022import android.annotation.Nullable;
Mathew Inwoodefeab842018-08-14 15:21:30 +010023import android.annotation.UnsupportedAppUsage;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080024import android.graphics.Paint;
Seigo Nonaka70200b02018-10-01 16:04:11 -070025import android.graphics.text.LineBreaker;
Mathew Inwood8c854f82018-09-14 12:35:36 +010026import android.os.Build;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080027import android.text.style.LeadingMarginSpan;
Gilles Debunne66111472010-11-19 11:04:37 -080028import android.text.style.LeadingMarginSpan.LeadingMarginSpan2;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080029import android.text.style.LineHeightSpan;
Doug Feltc982f602010-05-25 11:51:40 -070030import android.text.style.TabStopSpan;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -070031import android.util.Log;
Raph Levien39b4db72015-03-25 13:18:20 -070032import android.util.Pools.SynchronizedPool;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080033
Doug Feltcb3791202011-07-07 11:57:48 -070034import com.android.internal.util.ArrayUtils;
Adam Lesinski776abc22014-03-07 11:30:59 -050035import com.android.internal.util.GrowingArrayUtils;
Doug Feltcb3791202011-07-07 11:57:48 -070036
Anish Athalyec8f9e622014-07-21 15:26:34 -070037import java.util.Arrays;
38
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080039/**
40 * StaticLayout is a Layout for text that will not be edited after it
41 * is laid out. Use {@link DynamicLayout} for text that may change.
42 * <p>This is used by widgets to control text layout. You should not need
43 * to use this class directly unless you are implementing your own widget
44 * or custom display object, or would be tempted to call
Doug Felt4e0c5e52010-03-15 16:56:02 -070045 * {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int,
46 * float, float, android.graphics.Paint)
47 * Canvas.drawText()} directly.</p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080048 */
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -080049public class StaticLayout extends Layout {
Seigo Nonaka6f1868b2017-12-01 16:24:19 -080050 /*
51 * The break iteration is done in native code. The protocol for using the native code is as
52 * follows.
53 *
54 * First, call nInit to setup native line breaker object. Then, for each paragraph, do the
55 * following:
56 *
Seigo Nonaka9d3bd082018-01-11 10:02:12 -080057 * - Create MeasuredParagraph by MeasuredParagraph.buildForStaticLayout which measures in
58 * native.
Seigo Nonaka70200b02018-10-01 16:04:11 -070059 * - Run LineBreaker.computeLineBreaks() to obtain line breaks for the paragraph.
Seigo Nonaka6f1868b2017-12-01 16:24:19 -080060 *
61 * After all paragraphs, call finish() to release expensive buffers.
62 */
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -080063
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -070064 static final String TAG = "StaticLayout";
65
Raph Leviend3ab6922015-03-02 14:30:53 -080066 /**
Roozbeh Pournader22a167c2017-08-21 12:53:44 -070067 * Builder for static layouts. The builder is the preferred pattern for constructing
68 * StaticLayout objects and should be preferred over the constructors, particularly to access
69 * newer features. To build a static layout, first call {@link #obtain} with the required
70 * arguments (text, paint, and width), then call setters for optional parameters, and finally
71 * {@link #build} to build the StaticLayout object. Parameters not explicitly set will get
Raph Levien531c30c2015-04-30 16:29:59 -070072 * default values.
Raph Leviend3ab6922015-03-02 14:30:53 -080073 */
74 public final static class Builder {
Seigo Nonakab90e08f2017-10-20 15:23:51 -070075 private Builder() {}
Raph Levien4c1f12e2015-03-02 16:29:23 -080076
Raph Levien531c30c2015-04-30 16:29:59 -070077 /**
Roozbeh Pournader22a167c2017-08-21 12:53:44 -070078 * Obtain a builder for constructing StaticLayout objects.
Raph Levien531c30c2015-04-30 16:29:59 -070079 *
80 * @param source The text to be laid out, optionally with spans
81 * @param start The index of the start of the text
82 * @param end The index + 1 of the end of the text
83 * @param paint The base paint used for layout
84 * @param width The width in pixels
85 * @return a builder object used for constructing the StaticLayout
86 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -070087 @NonNull
88 public static Builder obtain(@NonNull CharSequence source, @IntRange(from = 0) int start,
89 @IntRange(from = 0) int end, @NonNull TextPaint paint,
90 @IntRange(from = 0) int width) {
Raph Levien39b4db72015-03-25 13:18:20 -070091 Builder b = sPool.acquire();
Raph Leviend3ab6922015-03-02 14:30:53 -080092 if (b == null) {
93 b = new Builder();
94 }
95
96 // set default initial values
Raph Levien39b4db72015-03-25 13:18:20 -070097 b.mText = source;
98 b.mStart = start;
99 b.mEnd = end;
Raph Levienebd66ca2015-04-30 15:27:57 -0700100 b.mPaint = paint;
Raph Levien39b4db72015-03-25 13:18:20 -0700101 b.mWidth = width;
102 b.mAlignment = Alignment.ALIGN_NORMAL;
Raph Leviend3ab6922015-03-02 14:30:53 -0800103 b.mTextDir = TextDirectionHeuristics.FIRSTSTRONG_LTR;
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700104 b.mSpacingMult = DEFAULT_LINESPACING_MULTIPLIER;
105 b.mSpacingAdd = DEFAULT_LINESPACING_ADDITION;
Raph Leviend3ab6922015-03-02 14:30:53 -0800106 b.mIncludePad = true;
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700107 b.mFallbackLineSpacing = false;
Raph Levien39b4db72015-03-25 13:18:20 -0700108 b.mEllipsizedWidth = width;
Raph Leviend3ab6922015-03-02 14:30:53 -0800109 b.mEllipsize = null;
110 b.mMaxLines = Integer.MAX_VALUE;
Raph Levien3bd60c72015-05-06 14:26:35 -0700111 b.mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700112 b.mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700113 b.mJustificationMode = Layout.JUSTIFICATION_MODE_NONE;
Raph Leviend3ab6922015-03-02 14:30:53 -0800114 return b;
115 }
116
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700117 /**
118 * This method should be called after the layout is finished getting constructed and the
119 * builder needs to be cleaned up and returned to the pool.
120 */
121 private static void recycle(@NonNull Builder b) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800122 b.mPaint = null;
123 b.mText = null;
Raph Levien2ea52902015-07-01 14:39:31 -0700124 b.mLeftIndents = null;
125 b.mRightIndents = null;
Raph Levien39b4db72015-03-25 13:18:20 -0700126 sPool.release(b);
Raph Leviend3ab6922015-03-02 14:30:53 -0800127 }
128
129 // release any expensive state
130 /* package */ void finish() {
Raph Levien22ba7862015-07-29 12:34:13 -0700131 mText = null;
132 mPaint = null;
133 mLeftIndents = null;
134 mRightIndents = null;
Raph Leviend3ab6922015-03-02 14:30:53 -0800135 }
136
137 public Builder setText(CharSequence source) {
138 return setText(source, 0, source.length());
139 }
140
Raph Levien531c30c2015-04-30 16:29:59 -0700141 /**
142 * Set the text. Only useful when re-using the builder, which is done for
143 * the internal implementation of {@link DynamicLayout} but not as part
144 * of normal {@link StaticLayout} usage.
145 *
146 * @param source The text to be laid out, optionally with spans
147 * @param start The index of the start of the text
148 * @param end The index + 1 of the end of the text
149 * @return this builder, useful for chaining
150 *
151 * @hide
152 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700153 @NonNull
154 public Builder setText(@NonNull CharSequence source, int start, int end) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800155 mText = source;
156 mStart = start;
157 mEnd = end;
158 return this;
159 }
160
Raph Levien531c30c2015-04-30 16:29:59 -0700161 /**
162 * Set the paint. Internal for reuse cases only.
163 *
164 * @param paint The base paint used for layout
165 * @return this builder, useful for chaining
166 *
167 * @hide
168 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700169 @NonNull
170 public Builder setPaint(@NonNull TextPaint paint) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800171 mPaint = paint;
172 return this;
173 }
174
Raph Levien531c30c2015-04-30 16:29:59 -0700175 /**
176 * Set the width. Internal for reuse cases only.
177 *
178 * @param width The width in pixels
179 * @return this builder, useful for chaining
180 *
181 * @hide
182 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700183 @NonNull
184 public Builder setWidth(@IntRange(from = 0) int width) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800185 mWidth = width;
186 if (mEllipsize == null) {
187 mEllipsizedWidth = width;
188 }
189 return this;
190 }
191
Raph Levien531c30c2015-04-30 16:29:59 -0700192 /**
193 * Set the alignment. The default is {@link Layout.Alignment#ALIGN_NORMAL}.
194 *
195 * @param alignment Alignment for the resulting {@link StaticLayout}
196 * @return this builder, useful for chaining
197 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700198 @NonNull
199 public Builder setAlignment(@NonNull Alignment alignment) {
Raph Levien39b4db72015-03-25 13:18:20 -0700200 mAlignment = alignment;
201 return this;
202 }
203
Raph Levien531c30c2015-04-30 16:29:59 -0700204 /**
205 * Set the text direction heuristic. The text direction heuristic is used to
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700206 * resolve text direction per-paragraph based on the input text. The default is
Raph Levien531c30c2015-04-30 16:29:59 -0700207 * {@link TextDirectionHeuristics#FIRSTSTRONG_LTR}.
208 *
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700209 * @param textDir text direction heuristic for resolving bidi behavior.
Raph Levien531c30c2015-04-30 16:29:59 -0700210 * @return this builder, useful for chaining
211 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700212 @NonNull
213 public Builder setTextDirection(@NonNull TextDirectionHeuristic textDir) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800214 mTextDir = textDir;
215 return this;
216 }
217
Raph Levien531c30c2015-04-30 16:29:59 -0700218 /**
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700219 * Set line spacing parameters. Each line will have its line spacing multiplied by
220 * {@code spacingMult} and then increased by {@code spacingAdd}. The default is 0.0 for
221 * {@code spacingAdd} and 1.0 for {@code spacingMult}.
Raph Levien531c30c2015-04-30 16:29:59 -0700222 *
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700223 * @param spacingAdd the amount of line spacing addition
224 * @param spacingMult the line spacing multiplier
Raph Levien531c30c2015-04-30 16:29:59 -0700225 * @return this builder, useful for chaining
226 * @see android.widget.TextView#setLineSpacing
227 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700228 @NonNull
229 public Builder setLineSpacing(float spacingAdd, @FloatRange(from = 0.0) float spacingMult) {
Raph Levien531c30c2015-04-30 16:29:59 -0700230 mSpacingAdd = spacingAdd;
Raph Leviend3ab6922015-03-02 14:30:53 -0800231 mSpacingMult = spacingMult;
232 return this;
233 }
234
Raph Levien531c30c2015-04-30 16:29:59 -0700235 /**
236 * Set whether to include extra space beyond font ascent and descent (which is
237 * needed to avoid clipping in some languages, such as Arabic and Kannada). The
238 * default is {@code true}.
239 *
240 * @param includePad whether to include padding
241 * @return this builder, useful for chaining
242 * @see android.widget.TextView#setIncludeFontPadding
243 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700244 @NonNull
Raph Leviend3ab6922015-03-02 14:30:53 -0800245 public Builder setIncludePad(boolean includePad) {
246 mIncludePad = includePad;
247 return this;
248 }
249
Raph Levien531c30c2015-04-30 16:29:59 -0700250 /**
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700251 * Set whether to respect the ascent and descent of the fallback fonts that are used in
252 * displaying the text (which is needed to avoid text from consecutive lines running into
253 * each other). If set, fallback fonts that end up getting used can increase the ascent
254 * and descent of the lines that they are used on.
255 *
256 * <p>For backward compatibility reasons, the default is {@code false}, but setting this to
257 * true is strongly recommended. It is required to be true if text could be in languages
258 * like Burmese or Tibetan where text is typically much taller or deeper than Latin text.
259 *
260 * @param useLineSpacingFromFallbacks whether to expand linespacing based on fallback fonts
261 * @return this builder, useful for chaining
262 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700263 @NonNull
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700264 public Builder setUseLineSpacingFromFallbacks(boolean useLineSpacingFromFallbacks) {
265 mFallbackLineSpacing = useLineSpacingFromFallbacks;
266 return this;
267 }
268
269 /**
Raph Levien531c30c2015-04-30 16:29:59 -0700270 * Set the width as used for ellipsizing purposes, if it differs from the
271 * normal layout width. The default is the {@code width}
272 * passed to {@link #obtain}.
273 *
274 * @param ellipsizedWidth width used for ellipsizing, in pixels
275 * @return this builder, useful for chaining
276 * @see android.widget.TextView#setEllipsize
277 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700278 @NonNull
279 public Builder setEllipsizedWidth(@IntRange(from = 0) int ellipsizedWidth) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800280 mEllipsizedWidth = ellipsizedWidth;
281 return this;
282 }
283
Raph Levien531c30c2015-04-30 16:29:59 -0700284 /**
285 * Set ellipsizing on the layout. Causes words that are longer than the view
286 * is wide, or exceeding the number of lines (see #setMaxLines) in the case
287 * of {@link android.text.TextUtils.TruncateAt#END} or
288 * {@link android.text.TextUtils.TruncateAt#MARQUEE}, to be ellipsized instead
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700289 * of broken. The default is {@code null}, indicating no ellipsis is to be applied.
Raph Levien531c30c2015-04-30 16:29:59 -0700290 *
291 * @param ellipsize type of ellipsis behavior
292 * @return this builder, useful for chaining
293 * @see android.widget.TextView#setEllipsize
294 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700295 @NonNull
Raph Levien531c30c2015-04-30 16:29:59 -0700296 public Builder setEllipsize(@Nullable TextUtils.TruncateAt ellipsize) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800297 mEllipsize = ellipsize;
298 return this;
299 }
300
Raph Levien531c30c2015-04-30 16:29:59 -0700301 /**
302 * Set maximum number of lines. This is particularly useful in the case of
303 * ellipsizing, where it changes the layout of the last line. The default is
304 * unlimited.
305 *
306 * @param maxLines maximum number of lines in the layout
307 * @return this builder, useful for chaining
308 * @see android.widget.TextView#setMaxLines
309 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700310 @NonNull
311 public Builder setMaxLines(@IntRange(from = 0) int maxLines) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800312 mMaxLines = maxLines;
313 return this;
314 }
315
Raph Levien531c30c2015-04-30 16:29:59 -0700316 /**
317 * Set break strategy, useful for selecting high quality or balanced paragraph
318 * layout options. The default is {@link Layout#BREAK_STRATEGY_SIMPLE}.
Siyamed Sinirc0dab362018-07-22 13:48:51 -0700319 * <p/>
320 * Enabling hyphenation with either using {@link Layout#HYPHENATION_FREQUENCY_NORMAL} or
321 * {@link Layout#HYPHENATION_FREQUENCY_FULL} while line breaking is set to one of
322 * {@link Layout#BREAK_STRATEGY_BALANCED}, {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}
323 * improves the structure of text layout however has performance impact and requires more
324 * time to do the text layout.
Raph Levien531c30c2015-04-30 16:29:59 -0700325 *
326 * @param breakStrategy break strategy for paragraph layout
327 * @return this builder, useful for chaining
328 * @see android.widget.TextView#setBreakStrategy
Siyamed Sinirc0dab362018-07-22 13:48:51 -0700329 * @see #setHyphenationFrequency(int)
Raph Levien531c30c2015-04-30 16:29:59 -0700330 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700331 @NonNull
Raph Levien39b4db72015-03-25 13:18:20 -0700332 public Builder setBreakStrategy(@BreakStrategy int breakStrategy) {
333 mBreakStrategy = breakStrategy;
334 return this;
335 }
336
Raph Levien531c30c2015-04-30 16:29:59 -0700337 /**
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700338 * Set hyphenation frequency, to control the amount of automatic hyphenation used. The
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700339 * possible values are defined in {@link Layout}, by constants named with the pattern
340 * {@code HYPHENATION_FREQUENCY_*}. The default is
341 * {@link Layout#HYPHENATION_FREQUENCY_NONE}.
Siyamed Sinirc0dab362018-07-22 13:48:51 -0700342 * <p/>
343 * Enabling hyphenation with either using {@link Layout#HYPHENATION_FREQUENCY_NORMAL} or
344 * {@link Layout#HYPHENATION_FREQUENCY_FULL} while line breaking is set to one of
345 * {@link Layout#BREAK_STRATEGY_BALANCED}, {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}
346 * improves the structure of text layout however has performance impact and requires more
347 * time to do the text layout.
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700348 *
349 * @param hyphenationFrequency hyphenation frequency for the paragraph
350 * @return this builder, useful for chaining
351 * @see android.widget.TextView#setHyphenationFrequency
Siyamed Sinirc0dab362018-07-22 13:48:51 -0700352 * @see #setBreakStrategy(int)
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700353 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700354 @NonNull
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700355 public Builder setHyphenationFrequency(@HyphenationFrequency int hyphenationFrequency) {
356 mHyphenationFrequency = hyphenationFrequency;
357 return this;
358 }
359
360 /**
Raph Levien531c30c2015-04-30 16:29:59 -0700361 * Set indents. Arguments are arrays holding an indent amount, one per line, measured in
362 * pixels. For lines past the last element in the array, the last element repeats.
363 *
364 * @param leftIndents array of indent values for left margin, in pixels
365 * @param rightIndents array of indent values for right margin, in pixels
366 * @return this builder, useful for chaining
Raph Levien531c30c2015-04-30 16:29:59 -0700367 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700368 @NonNull
369 public Builder setIndents(@Nullable int[] leftIndents, @Nullable int[] rightIndents) {
Raph Levien2ea52902015-07-01 14:39:31 -0700370 mLeftIndents = leftIndents;
371 mRightIndents = rightIndents;
Raph Leviene319d5a2015-04-14 23:51:07 -0700372 return this;
373 }
374
Raph Levien70616ec2015-03-04 10:41:30 -0800375 /**
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700376 * Set paragraph justification mode. The default value is
377 * {@link Layout#JUSTIFICATION_MODE_NONE}. If the last line is too short for justification,
378 * the last line will be displayed with the alignment set by {@link #setAlignment}.
Haoyu Zhangeccdc6b2019-01-07 15:11:46 -0800379 * When Justification mode is JUSTIFICATION_MODE_INTER_WORD, wordSpacing on the given
380 * {@link Paint} will be ignored. This behavior also affects Spans which change the
381 * wordSpacing.
Seigo Nonaka09da71a2016-11-28 16:24:14 +0900382 *
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700383 * @param justificationMode justification mode for the paragraph.
Seigo Nonaka09da71a2016-11-28 16:24:14 +0900384 * @return this builder, useful for chaining.
Haoyu Zhangeccdc6b2019-01-07 15:11:46 -0800385 * @see Paint#setWordSpacing(float)
Seigo Nonaka09da71a2016-11-28 16:24:14 +0900386 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700387 @NonNull
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700388 public Builder setJustificationMode(@JustificationMode int justificationMode) {
389 mJustificationMode = justificationMode;
Seigo Nonaka09da71a2016-11-28 16:24:14 +0900390 return this;
391 }
392
Siyamed Sinir442c1512017-07-24 12:18:27 -0700393 /**
394 * Sets whether the line spacing should be applied for the last line. Default value is
395 * {@code false}.
396 *
397 * @hide
398 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700399 @NonNull
Siyamed Sinir442c1512017-07-24 12:18:27 -0700400 /* package */ Builder setAddLastLineLineSpacing(boolean value) {
401 mAddLastLineLineSpacing = value;
402 return this;
403 }
404
Seigo Nonaka09da71a2016-11-28 16:24:14 +0900405 /**
Raph Levien531c30c2015-04-30 16:29:59 -0700406 * Build the {@link StaticLayout} after options have been set.
407 *
408 * <p>Note: the builder object must not be reused in any way after calling this
409 * method. Setting parameters after calling this method, or calling it a second
410 * time on the same builder object, will likely lead to unexpected results.
411 *
412 * @return the newly constructed {@link StaticLayout} object
413 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700414 @NonNull
Raph Leviend3ab6922015-03-02 14:30:53 -0800415 public StaticLayout build() {
Raph Levien39b4db72015-03-25 13:18:20 -0700416 StaticLayout result = new StaticLayout(this);
417 Builder.recycle(this);
Raph Leviend3ab6922015-03-02 14:30:53 -0800418 return result;
419 }
420
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700421 private CharSequence mText;
422 private int mStart;
423 private int mEnd;
424 private TextPaint mPaint;
425 private int mWidth;
426 private Alignment mAlignment;
427 private TextDirectionHeuristic mTextDir;
428 private float mSpacingMult;
429 private float mSpacingAdd;
430 private boolean mIncludePad;
431 private boolean mFallbackLineSpacing;
432 private int mEllipsizedWidth;
433 private TextUtils.TruncateAt mEllipsize;
434 private int mMaxLines;
435 private int mBreakStrategy;
436 private int mHyphenationFrequency;
Seigo Nonakabafe1972017-08-24 15:30:29 -0700437 @Nullable private int[] mLeftIndents;
438 @Nullable private int[] mRightIndents;
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700439 private int mJustificationMode;
440 private boolean mAddLastLineLineSpacing;
Raph Leviend3ab6922015-03-02 14:30:53 -0800441
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700442 private final Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
Raph Leviend3ab6922015-03-02 14:30:53 -0800443
Siyamed Sinira273a702017-10-05 11:22:12 -0700444 private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<>(3);
Raph Leviend3ab6922015-03-02 14:30:53 -0800445 }
446
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000447 /**
448 * @deprecated Use {@link Builder} instead.
449 */
450 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800451 public StaticLayout(CharSequence source, TextPaint paint,
452 int width,
453 Alignment align, float spacingmult, float spacingadd,
454 boolean includepad) {
455 this(source, 0, source.length(), paint, width, align,
456 spacingmult, spacingadd, includepad);
457 }
458
Doug Feltcb3791202011-07-07 11:57:48 -0700459 /**
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000460 * @deprecated Use {@link Builder} instead.
Doug Feltcb3791202011-07-07 11:57:48 -0700461 */
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000462 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800463 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
Doug Feltcb3791202011-07-07 11:57:48 -0700472 /**
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000473 * @deprecated Use {@link Builder} instead.
Doug Feltcb3791202011-07-07 11:57:48 -0700474 */
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000475 @Deprecated
Doug Feltcb3791202011-07-07 11:57:48 -0700476 public StaticLayout(CharSequence source, int bufstart, int bufend,
477 TextPaint paint, int outerwidth,
478 Alignment align,
479 float spacingmult, float spacingadd,
480 boolean includepad,
481 TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
Seigo Nonaka4e90fa22018-02-13 21:40:01 +0000482 this(source, bufstart, bufend, paint, outerwidth, align,
Doug Feltcb3791202011-07-07 11:57:48 -0700483 TextDirectionHeuristics.FIRSTSTRONG_LTR,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700484 spacingmult, spacingadd, includepad, ellipsize, ellipsizedWidth, Integer.MAX_VALUE);
Doug Feltcb3791202011-07-07 11:57:48 -0700485 }
486
487 /**
488 * @hide
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000489 * @deprecated Use {@link Builder} instead.
Doug Feltcb3791202011-07-07 11:57:48 -0700490 */
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000491 @Deprecated
Siyamed Sinir00589be2018-11-27 12:46:14 -0800492 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 117521430)
Seigo Nonaka4e90fa22018-02-13 21:40:01 +0000493 public StaticLayout(CharSequence source, int bufstart, int bufend,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800494 TextPaint paint, int outerwidth,
Doug Feltcb3791202011-07-07 11:57:48 -0700495 Alignment align, TextDirectionHeuristic textDir,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800496 float spacingmult, float spacingadd,
497 boolean includepad,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700498 TextUtils.TruncateAt ellipsize, int ellipsizedWidth, int maxLines) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800499 super((ellipsize == null)
Doug Felt4e0c5e52010-03-15 16:56:02 -0700500 ? source
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800501 : (source instanceof Spanned)
502 ? new SpannedEllipsizer(source)
503 : new Ellipsizer(source),
Doug Feltcb3791202011-07-07 11:57:48 -0700504 paint, outerwidth, align, textDir, spacingmult, spacingadd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800505
Raph Levienebd66ca2015-04-30 15:27:57 -0700506 Builder b = Builder.obtain(source, bufstart, bufend, paint, outerwidth)
Raph Levien39b4db72015-03-25 13:18:20 -0700507 .setAlignment(align)
Raph Leviena6a08282015-06-03 13:20:45 -0700508 .setTextDirection(textDir)
Raph Levien531c30c2015-04-30 16:29:59 -0700509 .setLineSpacing(spacingadd, spacingmult)
Raph Leviend3ab6922015-03-02 14:30:53 -0800510 .setIncludePad(includepad)
511 .setEllipsizedWidth(ellipsizedWidth)
512 .setEllipsize(ellipsize)
513 .setMaxLines(maxLines);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800514 /*
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700515 * This is annoying, but we can't refer to the layout until superclass construction is
516 * finished, and the superclass constructor wants the reference to the display text.
Doug Felt4e0c5e52010-03-15 16:56:02 -0700517 *
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700518 * In other words, the two Ellipsizer classes in Layout.java need a (Dynamic|Static)Layout
519 * as a parameter to do their calculations, but the Ellipsizers also need to be the input
520 * to the superclass's constructor (Layout). In order to go around the circular
521 * dependency, we construct the Ellipsizer with only one of the parameters, the text. And
522 * we fill in the rest of the needed information (layout, width, and method) later, here.
523 *
524 * This will break if the superclass constructor ever actually cares about the content
525 * instead of just holding the reference.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800526 */
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
Siyamed Siniraf398512017-07-25 19:08:42 -0700541 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2);
542 mLines = ArrayUtils.newUnpaddedIntArray(2 * mColumns);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700543 mMaximumVisibleLineCount = maxLines;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800544
Raph Levien70616ec2015-03-04 10:41:30 -0800545 generate(b, b.mIncludePad, b.mIncludePad);
Doug Felte8e45f22010-03-29 14:58:40 -0700546
Raph Leviend3ab6922015-03-02 14:30:53 -0800547 Builder.recycle(b);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800548 }
549
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000550 /**
551 * Used by DynamicLayout.
552 */
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700553 /* package */ StaticLayout(@Nullable CharSequence text) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700554 super(text, null, 0, null, 0, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800555
556 mColumns = COLUMNS_ELLIPSIZE;
Siyamed Siniraf398512017-07-25 19:08:42 -0700557 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2);
558 mLines = ArrayUtils.newUnpaddedIntArray(2 * mColumns);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800559 }
560
Raph Levien39b4db72015-03-25 13:18:20 -0700561 private StaticLayout(Builder b) {
562 super((b.mEllipsize == null)
563 ? b.mText
564 : (b.mText instanceof Spanned)
565 ? new SpannedEllipsizer(b.mText)
566 : new Ellipsizer(b.mText),
Roozbeh Pournader158dfaf2017-09-21 13:23:50 -0700567 b.mPaint, b.mWidth, b.mAlignment, b.mTextDir, b.mSpacingMult, b.mSpacingAdd);
Raph Levien39b4db72015-03-25 13:18:20 -0700568
569 if (b.mEllipsize != null) {
570 Ellipsizer e = (Ellipsizer) getText();
571
572 e.mLayout = this;
573 e.mWidth = b.mEllipsizedWidth;
574 e.mMethod = b.mEllipsize;
575 mEllipsizedWidth = b.mEllipsizedWidth;
576
577 mColumns = COLUMNS_ELLIPSIZE;
578 } else {
579 mColumns = COLUMNS_NORMAL;
580 mEllipsizedWidth = b.mWidth;
581 }
582
Siyamed Siniraf398512017-07-25 19:08:42 -0700583 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2);
584 mLines = ArrayUtils.newUnpaddedIntArray(2 * mColumns);
Raph Levien39b4db72015-03-25 13:18:20 -0700585 mMaximumVisibleLineCount = b.mMaxLines;
586
Raph Levien2ea52902015-07-01 14:39:31 -0700587 mLeftIndents = b.mLeftIndents;
588 mRightIndents = b.mRightIndents;
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700589 setJustificationMode(b.mJustificationMode);
Raph Levien2ea52902015-07-01 14:39:31 -0700590
Raph Levien39b4db72015-03-25 13:18:20 -0700591 generate(b, b.mIncludePad, b.mIncludePad);
592 }
593
Raph Leviend3ab6922015-03-02 14:30:53 -0800594 /* package */ void generate(Builder b, boolean includepad, boolean trackpad) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700595 final CharSequence source = b.mText;
Seigo Nonakafbe63bd2017-12-02 19:28:05 -0800596 final int bufStart = b.mStart;
597 final int bufEnd = b.mEnd;
Raph Leviend3ab6922015-03-02 14:30:53 -0800598 TextPaint paint = b.mPaint;
599 int outerWidth = b.mWidth;
600 TextDirectionHeuristic textDir = b.mTextDir;
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700601 final boolean fallbackLineSpacing = b.mFallbackLineSpacing;
Raph Leviend3ab6922015-03-02 14:30:53 -0800602 float spacingmult = b.mSpacingMult;
603 float spacingadd = b.mSpacingAdd;
604 float ellipsizedWidth = b.mEllipsizedWidth;
605 TextUtils.TruncateAt ellipsize = b.mEllipsize;
Siyamed Sinir442c1512017-07-24 12:18:27 -0700606 final boolean addLastLineSpacing = b.mAddLastLineLineSpacing;
Seigo Nonakaab9b4792018-09-24 17:37:15 -0700607
608 int lineBreakCapacity = 0;
609 int[] breaks = null;
610 float[] lineWidths = null;
611 float[] ascents = null;
612 float[] descents = null;
613 boolean[] hasTabs = null;
614 int[] hyphenEdits = null;
Anish Athalye88b5b0b2014-06-24 14:39:43 -0700615
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800616 mLineCount = 0;
Siyamed Sinira19cd512017-08-03 22:01:56 -0700617 mEllipsized = false;
Siyamed Sinira8d982d2017-08-21 13:27:47 -0700618 mMaxLineHeight = mMaximumVisibleLineCount < 1 ? 0 : DEFAULT_MAX_LINE_HEIGHT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800619
620 int v = 0;
621 boolean needMultiply = (spacingmult != 1 || spacingadd != 0);
622
Raph Leviend3ab6922015-03-02 14:30:53 -0800623 Paint.FontMetricsInt fm = b.mFontMetricsInt;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800624 int[] chooseHtv = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800625
Seigo Nonakabafe1972017-08-24 15:30:29 -0700626 final int[] indents;
627 if (mLeftIndents != null || mRightIndents != null) {
628 final int leftLen = mLeftIndents == null ? 0 : mLeftIndents.length;
629 final int rightLen = mRightIndents == null ? 0 : mRightIndents.length;
630 final int indentsLen = Math.max(leftLen, rightLen);
631 indents = new int[indentsLen];
632 for (int i = 0; i < leftLen; i++) {
633 indents[i] = mLeftIndents[i];
634 }
635 for (int i = 0; i < rightLen; i++) {
636 indents[i] += mRightIndents[i];
637 }
638 } else {
639 indents = null;
640 }
641
Seigo Nonaka70200b02018-10-01 16:04:11 -0700642 final LineBreaker lineBreaker = new LineBreaker.Builder()
Seigo Nonaka41bb4fa2018-08-07 15:15:42 -0700643 .setBreakStrategy(b.mBreakStrategy)
644 .setHyphenationFrequency(b.mHyphenationFrequency)
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700645 // TODO: Support more justification mode, e.g. letter spacing, stretching.
Seigo Nonakade41eea2019-03-07 16:19:05 -0800646 .setJustificationMode(b.mJustificationMode)
Seigo Nonaka41bb4fa2018-08-07 15:15:42 -0700647 .setIndents(indents)
648 .build();
649
Seigo Nonaka70200b02018-10-01 16:04:11 -0700650 LineBreaker.ParagraphConstraints constraints =
651 new LineBreaker.ParagraphConstraints();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800652
Seigo Nonakac3328d62018-03-20 15:18:59 -0700653 PrecomputedText.ParagraphInfo[] paragraphInfo = null;
654 final Spanned spanned = (source instanceof Spanned) ? (Spanned) source : null;
Seigo Nonakabeafa1f2018-02-01 21:39:24 -0800655 if (source instanceof PrecomputedText) {
Seigo Nonakac3328d62018-03-20 15:18:59 -0700656 PrecomputedText precomputed = (PrecomputedText) source;
Seigo Nonaka291ef052018-11-16 10:41:32 -0800657 final @PrecomputedText.Params.CheckResultUsableResult int checkResult =
658 precomputed.checkResultUsable(bufStart, bufEnd, textDir, paint,
659 b.mBreakStrategy, b.mHyphenationFrequency);
660 switch (checkResult) {
661 case PrecomputedText.Params.UNUSABLE:
662 break;
663 case PrecomputedText.Params.NEED_RECOMPUTE:
664 final PrecomputedText.Params newParams =
665 new PrecomputedText.Params.Builder(paint)
666 .setBreakStrategy(b.mBreakStrategy)
667 .setHyphenationFrequency(b.mHyphenationFrequency)
668 .setTextDirection(textDir)
669 .build();
670 precomputed = PrecomputedText.create(precomputed, newParams);
671 paragraphInfo = precomputed.getParagraphInfo();
672 break;
673 case PrecomputedText.Params.USABLE:
674 // Some parameters are different from the ones when measured text is created.
675 paragraphInfo = precomputed.getParagraphInfo();
676 break;
Seigo Nonakafbe63bd2017-12-02 19:28:05 -0800677 }
Seigo Nonaka87b15472018-01-12 14:06:29 -0800678 }
679
Seigo Nonakac3328d62018-03-20 15:18:59 -0700680 if (paragraphInfo == null) {
Seigo Nonakabeafa1f2018-02-01 21:39:24 -0800681 final PrecomputedText.Params param = new PrecomputedText.Params(paint, textDir,
682 b.mBreakStrategy, b.mHyphenationFrequency);
Seigo Nonakac3328d62018-03-20 15:18:59 -0700683 paragraphInfo = PrecomputedText.createMeasuredParagraphs(source, param, bufStart,
684 bufEnd, false /* computeLayout */);
Seigo Nonaka4e90fa22018-02-13 21:40:01 +0000685 }
Seigo Nonakafbe63bd2017-12-02 19:28:05 -0800686
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700687 for (int paraIndex = 0; paraIndex < paragraphInfo.length; paraIndex++) {
688 final int paraStart = paraIndex == 0
689 ? bufStart : paragraphInfo[paraIndex - 1].paragraphEnd;
690 final int paraEnd = paragraphInfo[paraIndex].paragraphEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800691
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700692 int firstWidthLineCount = 1;
693 int firstWidth = outerWidth;
694 int restWidth = outerWidth;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800695
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700696 LineHeightSpan[] chooseHt = null;
697 if (spanned != null) {
698 LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
699 LeadingMarginSpan.class);
700 for (int i = 0; i < sp.length; i++) {
701 LeadingMarginSpan lms = sp[i];
702 firstWidth -= sp[i].getLeadingMargin(true);
703 restWidth -= sp[i].getLeadingMargin(false);
Doug Feltcb3791202011-07-07 11:57:48 -0700704
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700705 // LeadingMarginSpan2 is odd. The count affects all
706 // leading margin spans, not just this particular one
707 if (lms instanceof LeadingMarginSpan2) {
708 LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
709 firstWidthLineCount = Math.max(firstWidthLineCount,
710 lms2.getLeadingMarginLineCount());
711 }
712 }
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700713
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700714 chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
715
716 if (chooseHt.length == 0) {
717 chooseHt = null; // So that out() would not assume it has any contents
718 } else {
719 if (chooseHtv == null || chooseHtv.length < chooseHt.length) {
720 chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700721 }
722
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700723 for (int i = 0; i < chooseHt.length; i++) {
724 int o = spanned.getSpanStart(chooseHt[i]);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700725
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700726 if (o < paraStart) {
727 // starts in this layout, before the
728 // current paragraph
729
730 chooseHtv[i] = getLineTop(getLineForOffset(o));
731 } else {
732 // starts in this paragraph
733
734 chooseHtv[i] = v;
735 }
736 }
737 }
738 }
739 // tab stop locations
Seigo Nonakad5621ed2019-03-08 16:04:37 -0800740 float[] variableTabStops = null;
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700741 if (spanned != null) {
742 TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
743 paraEnd, TabStopSpan.class);
744 if (spans.length > 0) {
Seigo Nonakad5621ed2019-03-08 16:04:37 -0800745 float[] stops = new float[spans.length];
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700746 for (int i = 0; i < spans.length; i++) {
Seigo Nonakad5621ed2019-03-08 16:04:37 -0800747 stops[i] = (float) spans[i].getTabStop();
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700748 }
749 Arrays.sort(stops, 0, stops.length);
750 variableTabStops = stops;
751 }
752 }
753
754 final MeasuredParagraph measuredPara = paragraphInfo[paraIndex].measured;
755 final char[] chs = measuredPara.getChars();
756 final int[] spanEndCache = measuredPara.getSpanEndCache().getRawArray();
757 final int[] fmCache = measuredPara.getFontMetrics().getRawArray();
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700758
Seigo Nonaka41bb4fa2018-08-07 15:15:42 -0700759 constraints.setWidth(restWidth);
760 constraints.setIndent(firstWidth, firstWidthLineCount);
761 constraints.setTabStops(variableTabStops, TAB_INCREMENT);
762
Seigo Nonaka70200b02018-10-01 16:04:11 -0700763 LineBreaker.Result res = lineBreaker.computeLineBreaks(
764 measuredPara.getMeasuredText(), constraints, mLineCount);
Seigo Nonakaab9b4792018-09-24 17:37:15 -0700765 int breakCount = res.getLineCount();
766 if (lineBreakCapacity < breakCount) {
767 lineBreakCapacity = breakCount;
768 breaks = new int[lineBreakCapacity];
769 lineWidths = new float[lineBreakCapacity];
770 ascents = new float[lineBreakCapacity];
771 descents = new float[lineBreakCapacity];
772 hasTabs = new boolean[lineBreakCapacity];
773 hyphenEdits = new int[lineBreakCapacity];
774 }
775
776 for (int i = 0; i < breakCount; ++i) {
777 breaks[i] = res.getLineBreakOffset(i);
778 lineWidths[i] = res.getLineWidth(i);
779 ascents[i] = res.getLineAscent(i);
780 descents[i] = res.getLineDescent(i);
781 hasTabs[i] = res.hasLineTab(i);
Seigo Nonakafb1b4792019-03-08 14:05:08 -0800782 hyphenEdits[i] =
783 packHyphenEdit(res.getStartLineHyphenEdit(i), res.getEndLineHyphenEdit(i));
Seigo Nonakaab9b4792018-09-24 17:37:15 -0700784 }
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700785
786 final int remainingLineCount = mMaximumVisibleLineCount - mLineCount;
787 final boolean ellipsisMayBeApplied = ellipsize != null
788 && (ellipsize == TextUtils.TruncateAt.END
789 || (mMaximumVisibleLineCount == 1
790 && ellipsize != TextUtils.TruncateAt.MARQUEE));
791 if (0 < remainingLineCount && remainingLineCount < breakCount
792 && ellipsisMayBeApplied) {
793 // Calculate width
794 float width = 0;
Seigo Nonakaab9b4792018-09-24 17:37:15 -0700795 boolean hasTab = false; // XXX May need to also have starting hyphen edit
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700796 for (int i = remainingLineCount - 1; i < breakCount; i++) {
797 if (i == breakCount - 1) {
798 width += lineWidths[i];
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700799 } else {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700800 for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) {
Seigo Nonaka5783c132018-10-15 17:35:56 -0700801 width += measuredPara.getCharWidthAt(j);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700802 }
Mark Wagner7b5676e2009-10-16 11:44:23 -0700803 }
Seigo Nonakaab9b4792018-09-24 17:37:15 -0700804 hasTab |= hasTabs[i];
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700805 }
806 // Treat the last line and overflowed lines as a single line.
807 breaks[remainingLineCount - 1] = breaks[breakCount - 1];
808 lineWidths[remainingLineCount - 1] = width;
Seigo Nonakaab9b4792018-09-24 17:37:15 -0700809 hasTabs[remainingLineCount - 1] = hasTab;
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700810
811 breakCount = remainingLineCount;
812 }
813
814 // here is the offset of the starting character of the line we are currently
815 // measuring
816 int here = paraStart;
817
818 int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
819 int fmCacheIndex = 0;
820 int spanEndCacheIndex = 0;
821 int breakIndex = 0;
822 for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
823 // retrieve end of span
824 spanEnd = spanEndCache[spanEndCacheIndex++];
825
826 // retrieve cached metrics, order matches above
827 fm.top = fmCache[fmCacheIndex * 4 + 0];
828 fm.bottom = fmCache[fmCacheIndex * 4 + 1];
829 fm.ascent = fmCache[fmCacheIndex * 4 + 2];
830 fm.descent = fmCache[fmCacheIndex * 4 + 3];
831 fmCacheIndex++;
832
833 if (fm.top < fmTop) {
834 fmTop = fm.top;
835 }
836 if (fm.ascent < fmAscent) {
837 fmAscent = fm.ascent;
838 }
839 if (fm.descent > fmDescent) {
840 fmDescent = fm.descent;
841 }
842 if (fm.bottom > fmBottom) {
843 fmBottom = fm.bottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800844 }
845
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700846 // skip breaks ending before current span range
847 while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
848 breakIndex++;
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700849 }
850
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700851 while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
852 int endPos = paraStart + breaks[breakIndex];
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800853
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700854 boolean moreChars = (endPos < bufEnd);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700855
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700856 final int ascent = fallbackLineSpacing
857 ? Math.min(fmAscent, Math.round(ascents[breakIndex]))
858 : fmAscent;
859 final int descent = fallbackLineSpacing
860 ? Math.max(fmDescent, Math.round(descents[breakIndex]))
861 : fmDescent;
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700862
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700863 v = out(source, here, endPos,
864 ascent, descent, fmTop, fmBottom,
865 v, spacingmult, spacingadd, chooseHt, chooseHtv, fm,
Seigo Nonakaab9b4792018-09-24 17:37:15 -0700866 hasTabs[breakIndex], hyphenEdits[breakIndex], needMultiply,
867 measuredPara, bufEnd, includepad, trackpad, addLastLineSpacing, chs,
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700868 paraStart, ellipsize, ellipsizedWidth, lineWidths[breakIndex],
869 paint, moreChars);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700870
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700871 if (endPos < spanEnd) {
872 // preserve metrics for current span
Anish Athalyec8f9e622014-07-21 15:26:34 -0700873 fmTop = fm.top;
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700874 fmBottom = fm.bottom;
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700875 fmAscent = fm.ascent;
876 fmDescent = fm.descent;
877 } else {
878 fmTop = fmBottom = fmAscent = fmDescent = 0;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700879 }
880
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700881 here = endPos;
882 breakIndex++;
883
884 if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) {
885 return;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700886 }
887 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800888 }
889
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700890 if (paraEnd == bufEnd) {
891 break;
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700892 }
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700893 }
894
895 if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE)
896 && mLineCount < mMaximumVisibleLineCount) {
897 final MeasuredParagraph measuredPara =
898 MeasuredParagraph.buildForBidi(source, bufEnd, bufEnd, textDir, null);
899 paint.getFontMetricsInt(fm);
900 v = out(source,
901 bufEnd, bufEnd, fm.ascent, fm.descent,
902 fm.top, fm.bottom,
903 v,
904 spacingmult, spacingadd, null,
Seigo Nonakaab9b4792018-09-24 17:37:15 -0700905 null, fm, false, 0,
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700906 needMultiply, measuredPara, bufEnd,
907 includepad, trackpad, addLastLineSpacing, null,
908 bufStart, ellipsize,
909 ellipsizedWidth, 0, paint, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800910 }
911 }
912
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700913 private int out(final CharSequence text, final int start, final int end, int above, int below,
914 int top, int bottom, int v, final float spacingmult, final float spacingadd,
915 final LineHeightSpan[] chooseHt, final int[] chooseHtv, final Paint.FontMetricsInt fm,
Seigo Nonakaab9b4792018-09-24 17:37:15 -0700916 final boolean hasTab, final int hyphenEdit, final boolean needMultiply,
917 @NonNull final MeasuredParagraph measured,
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800918 final int bufEnd, final boolean includePad, final boolean trackPad,
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700919 final boolean addLastLineLineSpacing, final char[] chs,
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700920 final int widthStart, final TextUtils.TruncateAt ellipsize, final float ellipsisWidth,
921 final float textWidth, final TextPaint paint, final boolean moreChars) {
Siyamed Siniraf398512017-07-25 19:08:42 -0700922 final int j = mLineCount;
923 final int off = j * mColumns;
924 final int want = off + mColumns + TOP;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800925 int[] lines = mLines;
Seigo Nonaka4b6bcee2017-12-11 11:39:51 -0800926 final int dir = measured.getParagraphDir();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800927
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800928 if (want >= lines.length) {
Siyamed Siniraf398512017-07-25 19:08:42 -0700929 final int[] grow = ArrayUtils.newUnpaddedIntArray(GrowingArrayUtils.growSize(want));
Adam Lesinski776abc22014-03-07 11:30:59 -0500930 System.arraycopy(lines, 0, grow, 0, lines.length);
931 mLines = grow;
932 lines = grow;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800933 }
934
Siyamed Siniraf398512017-07-25 19:08:42 -0700935 if (j >= mLineDirections.length) {
936 final Directions[] grow = ArrayUtils.newUnpaddedArray(Directions.class,
937 GrowingArrayUtils.growSize(j));
938 System.arraycopy(mLineDirections, 0, grow, 0, mLineDirections.length);
939 mLineDirections = grow;
940 }
941
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800942 if (chooseHt != null) {
943 fm.ascent = above;
944 fm.descent = below;
945 fm.top = top;
946 fm.bottom = bottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800947
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800948 for (int i = 0; i < chooseHt.length; i++) {
949 if (chooseHt[i] instanceof LineHeightSpan.WithDensity) {
950 ((LineHeightSpan.WithDensity) chooseHt[i])
951 .chooseHeight(text, start, end, chooseHtv[i], v, fm, paint);
952 } else {
953 chooseHt[i].chooseHeight(text, start, end, chooseHtv[i], v, fm);
954 }
955 }
Eric Fischera9f1dd02009-08-12 15:00:10 -0700956
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800957 above = fm.ascent;
958 below = fm.descent;
959 top = fm.top;
960 bottom = fm.bottom;
961 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800962
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800963 boolean firstLine = (j == 0);
964 boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);
Siyamed Sinir0745c722016-05-31 20:39:33 -0700965
966 if (ellipsize != null) {
967 // If there is only one line, then do any type of ellipsis except when it is MARQUEE
968 // if there are multiple lines, just allow END ellipsis on the last line
969 boolean forceEllipsis = moreChars && (mLineCount + 1 == mMaximumVisibleLineCount);
970
971 boolean doEllipsis =
972 (((mMaximumVisibleLineCount == 1 && moreChars) || (firstLine && !moreChars)) &&
973 ellipsize != TextUtils.TruncateAt.MARQUEE) ||
974 (!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
975 ellipsize == TextUtils.TruncateAt.END);
976 if (doEllipsis) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700977 calculateEllipsis(start, end, measured, widthStart,
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800978 ellipsisWidth, ellipsize, j,
979 textWidth, paint, forceEllipsis);
Siyamed Sinir0745c722016-05-31 20:39:33 -0700980 }
981 }
982
Siyamed Sinirfb0b2dc2017-07-26 22:08:24 -0700983 final boolean lastLine;
984 if (mEllipsized) {
985 lastLine = true;
986 } else {
987 final boolean lastCharIsNewLine = widthStart != bufEnd && bufEnd > 0
988 && text.charAt(bufEnd - 1) == CHAR_NEW_LINE;
989 if (end == bufEnd && !lastCharIsNewLine) {
990 lastLine = true;
991 } else if (start == bufEnd && lastCharIsNewLine) {
992 lastLine = true;
993 } else {
994 lastLine = false;
995 }
996 }
Raph Leviend97b0972014-04-24 12:51:35 -0700997
998 if (firstLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800999 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001000 mTopPadding = top - above;
1001 }
1002
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001003 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001004 above = top;
1005 }
1006 }
Raph Leviend97b0972014-04-24 12:51:35 -07001007
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001008 int extra;
1009
Raph Leviend97b0972014-04-24 12:51:35 -07001010 if (lastLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001011 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001012 mBottomPadding = bottom - below;
1013 }
1014
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001015 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001016 below = bottom;
1017 }
1018 }
1019
Siyamed Sinir442c1512017-07-24 12:18:27 -07001020 if (needMultiply && (addLastLineLineSpacing || !lastLine)) {
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001021 double ex = (below - above) * (spacingmult - 1) + spacingadd;
Doug Felt10657582010-02-22 11:19:01 -08001022 if (ex >= 0) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001023 extra = (int)(ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -08001024 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001025 extra = -(int)(-ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -08001026 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001027 } else {
1028 extra = 0;
1029 }
1030
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001031 lines[off + START] = start;
1032 lines[off + TOP] = v;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001033 lines[off + DESCENT] = below + extra;
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001034 lines[off + EXTRA] = extra;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001035
Siyamed Sinir0745c722016-05-31 20:39:33 -07001036 // special case for non-ellipsized last visible line when maxLines is set
1037 // store the height as if it was ellipsized
1038 if (!mEllipsized && currentLineIsTheLastVisibleOne) {
1039 // below calculation as if it was the last line
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001040 int maxLineBelow = includePad ? bottom : below;
Siyamed Sinir0745c722016-05-31 20:39:33 -07001041 // similar to the calculation of v below, without the extra.
1042 mMaxLineHeight = v + (maxLineBelow - above);
1043 }
1044
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001045 v += (below - above) + extra;
1046 lines[off + mColumns + START] = end;
1047 lines[off + mColumns + TOP] = v;
1048
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001049 // TODO: could move TAB to share same column as HYPHEN, simplifying this code and gaining
1050 // one bit for start field
Seigo Nonakaab9b4792018-09-24 17:37:15 -07001051 lines[off + TAB] |= hasTab ? TAB_MASK : 0;
1052 lines[off + HYPHEN] = hyphenEdit;
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001053 lines[off + DIR] |= dir << DIR_SHIFT;
1054 mLineDirections[j] = measured.getDirections(start - widthStart, end - widthStart);
1055
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001056 mLineCount++;
1057 return v;
1058 }
1059
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001060 private void calculateEllipsis(int lineStart, int lineEnd,
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001061 MeasuredParagraph measured, int widthStart,
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001062 float avail, TextUtils.TruncateAt where,
1063 int line, float textWidth, TextPaint paint,
1064 boolean forceEllipsis) {
1065 avail -= getTotalInsets(line);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001066 if (textWidth <= avail && !forceEllipsis) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001067 // Everything fits!
1068 mLines[mColumns * line + ELLIPSIS_START] = 0;
1069 mLines[mColumns * line + ELLIPSIS_COUNT] = 0;
1070 return;
1071 }
1072
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001073 float ellipsisWidth = paint.measureText(TextUtils.getEllipsisString(where));
1074 int ellipsisStart = 0;
1075 int ellipsisCount = 0;
1076 int len = lineEnd - lineStart;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001077
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001078 // We only support start ellipsis on a single line
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001079 if (where == TextUtils.TruncateAt.START) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001080 if (mMaximumVisibleLineCount == 1) {
1081 float sum = 0;
1082 int i;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001083
Keisuke Kuroyanagied2eea12015-04-14 18:18:35 +09001084 for (i = len; i > 0; i--) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001085 float w = measured.getCharWidthAt(i - 1 + lineStart - widthStart);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001086 if (w + sum + ellipsisWidth > avail) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001087 while (i < len
1088 && measured.getCharWidthAt(i + lineStart - widthStart) == 0.0f) {
Keisuke Kuroyanagi82d1c442016-09-14 14:30:14 +09001089 i++;
1090 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001091 break;
1092 }
1093
1094 sum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001095 }
1096
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001097 ellipsisStart = 0;
1098 ellipsisCount = i;
1099 } else {
1100 if (Log.isLoggable(TAG, Log.WARN)) {
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001101 Log.w(TAG, "Start Ellipsis only supported with one line");
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001102 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001103 }
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001104 } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE ||
1105 where == TextUtils.TruncateAt.END_SMALL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001106 float sum = 0;
1107 int i;
1108
1109 for (i = 0; i < len; i++) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001110 float w = measured.getCharWidthAt(i + lineStart - widthStart);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001111
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001112 if (w + sum + ellipsisWidth > avail) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001113 break;
1114 }
1115
1116 sum += w;
1117 }
1118
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001119 ellipsisStart = i;
1120 ellipsisCount = len - i;
1121 if (forceEllipsis && ellipsisCount == 0 && len > 0) {
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -07001122 ellipsisStart = len - 1;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001123 ellipsisCount = 1;
1124 }
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001125 } else {
1126 // where = TextUtils.TruncateAt.MIDDLE We only support middle ellipsis on a single line
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001127 if (mMaximumVisibleLineCount == 1) {
1128 float lsum = 0, rsum = 0;
1129 int left = 0, right = len;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001130
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001131 float ravail = (avail - ellipsisWidth) / 2;
Raph Levien0e3c5e82014-12-04 13:26:07 -08001132 for (right = len; right > 0; right--) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001133 float w = measured.getCharWidthAt(right - 1 + lineStart - widthStart);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001134
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001135 if (w + rsum > ravail) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001136 while (right < len
1137 && measured.getCharWidthAt(right + lineStart - widthStart)
1138 == 0.0f) {
Keisuke Kuroyanagi82d1c442016-09-14 14:30:14 +09001139 right++;
1140 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001141 break;
1142 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001143 rsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001144 }
1145
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001146 float lavail = avail - ellipsisWidth - rsum;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001147 for (left = 0; left < right; left++) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001148 float w = measured.getCharWidthAt(left + lineStart - widthStart);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001149
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001150 if (w + lsum > lavail) {
1151 break;
1152 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001153
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001154 lsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001155 }
1156
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001157 ellipsisStart = left;
1158 ellipsisCount = right - left;
1159 } else {
1160 if (Log.isLoggable(TAG, Log.WARN)) {
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001161 Log.w(TAG, "Middle Ellipsis only supported with one line");
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001162 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001163 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001164 }
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001165 mEllipsized = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001166 mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
1167 mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
1168 }
1169
Selim Cinek365ec092017-03-09 00:10:52 -08001170 private float getTotalInsets(int line) {
1171 int totalIndent = 0;
1172 if (mLeftIndents != null) {
1173 totalIndent = mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1174 }
1175 if (mRightIndents != null) {
1176 totalIndent += mRightIndents[Math.min(line, mRightIndents.length - 1)];
1177 }
1178 return totalIndent;
1179 }
1180
Doug Felte8e45f22010-03-29 14:58:40 -07001181 // Override the base class so we can directly access our members,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001182 // rather than relying on member functions.
1183 // The logic mirrors that of Layout.getLineForVertical
1184 // FIXME: It may be faster to do a linear search for layouts without many lines.
Gilles Debunne66111472010-11-19 11:04:37 -08001185 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001186 public int getLineForVertical(int vertical) {
1187 int high = mLineCount;
1188 int low = -1;
1189 int guess;
1190 int[] lines = mLines;
1191 while (high - low > 1) {
1192 guess = (high + low) >> 1;
1193 if (lines[mColumns * guess + TOP] > vertical){
1194 high = guess;
1195 } else {
1196 low = guess;
1197 }
1198 }
1199 if (low < 0) {
1200 return 0;
1201 } else {
1202 return low;
1203 }
1204 }
1205
Gilles Debunne66111472010-11-19 11:04:37 -08001206 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001207 public int getLineCount() {
1208 return mLineCount;
1209 }
1210
Gilles Debunne66111472010-11-19 11:04:37 -08001211 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001212 public int getLineTop(int line) {
Raph Levien07e6c232016-04-04 12:34:06 -07001213 return mLines[mColumns * line + TOP];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001214 }
1215
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001216 /**
1217 * @hide
1218 */
1219 @Override
1220 public int getLineExtra(int line) {
1221 return mLines[mColumns * line + EXTRA];
1222 }
1223
Gilles Debunne66111472010-11-19 11:04:37 -08001224 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001225 public int getLineDescent(int line) {
Raph Levien07e6c232016-04-04 12:34:06 -07001226 return mLines[mColumns * line + DESCENT];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001227 }
1228
Gilles Debunne66111472010-11-19 11:04:37 -08001229 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001230 public int getLineStart(int line) {
1231 return mLines[mColumns * line + START] & START_MASK;
1232 }
1233
Gilles Debunne66111472010-11-19 11:04:37 -08001234 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001235 public int getParagraphDirection(int line) {
1236 return mLines[mColumns * line + DIR] >> DIR_SHIFT;
1237 }
1238
Gilles Debunne66111472010-11-19 11:04:37 -08001239 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001240 public boolean getLineContainsTab(int line) {
1241 return (mLines[mColumns * line + TAB] & TAB_MASK) != 0;
1242 }
1243
Gilles Debunne66111472010-11-19 11:04:37 -08001244 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001245 public final Directions getLineDirections(int line) {
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001246 if (line > getLineCount()) {
1247 throw new ArrayIndexOutOfBoundsException();
1248 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001249 return mLineDirections[line];
1250 }
1251
Gilles Debunne66111472010-11-19 11:04:37 -08001252 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001253 public int getTopPadding() {
1254 return mTopPadding;
1255 }
1256
Gilles Debunne66111472010-11-19 11:04:37 -08001257 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001258 public int getBottomPadding() {
1259 return mBottomPadding;
1260 }
1261
Seigo Nonakafb1b4792019-03-08 14:05:08 -08001262 // To store into single int field, pack the pair of start and end hyphen edit.
1263 static int packHyphenEdit(
1264 @Paint.StartHyphenEdit int start, @Paint.EndHyphenEdit int end) {
1265 return start << START_HYPHEN_BITS_SHIFT | end;
1266 }
1267
1268 static int unpackStartHyphenEdit(int packedHyphenEdit) {
1269 return (packedHyphenEdit & START_HYPHEN_MASK) >> START_HYPHEN_BITS_SHIFT;
1270 }
1271
1272 static int unpackEndHyphenEdit(int packedHyphenEdit) {
1273 return packedHyphenEdit & END_HYPHEN_MASK;
1274 }
1275
Raph Levien26d443a2015-03-30 14:18:32 -07001276 /**
Seigo Nonakafb1b4792019-03-08 14:05:08 -08001277 * Returns the start hyphen edit value for this line.
Seigo Nonaka9033e0c2018-09-06 18:42:10 -07001278 *
1279 * @param lineNumber a line number
Seigo Nonakafb1b4792019-03-08 14:05:08 -08001280 * @return A start hyphen edit value.
Raph Levien26d443a2015-03-30 14:18:32 -07001281 * @hide
1282 */
1283 @Override
Seigo Nonakafb1b4792019-03-08 14:05:08 -08001284 public @Paint.StartHyphenEdit int getStartHyphenEdit(int lineNumber) {
1285 return unpackStartHyphenEdit(mLines[mColumns * lineNumber + HYPHEN] & HYPHEN_MASK);
1286 }
1287
1288 /**
1289 * Returns the packed hyphen edit value for this line.
1290 *
1291 * @param lineNumber a line number
1292 * @return An end hyphen edit value.
1293 * @hide
1294 */
1295 @Override
1296 public @Paint.EndHyphenEdit int getEndHyphenEdit(int lineNumber) {
1297 return unpackEndHyphenEdit(mLines[mColumns * lineNumber + HYPHEN] & HYPHEN_MASK);
Raph Levien26d443a2015-03-30 14:18:32 -07001298 }
1299
Raph Levien2ea52902015-07-01 14:39:31 -07001300 /**
1301 * @hide
1302 */
1303 @Override
1304 public int getIndentAdjust(int line, Alignment align) {
1305 if (align == Alignment.ALIGN_LEFT) {
1306 if (mLeftIndents == null) {
1307 return 0;
1308 } else {
1309 return mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1310 }
1311 } else if (align == Alignment.ALIGN_RIGHT) {
1312 if (mRightIndents == null) {
1313 return 0;
1314 } else {
1315 return -mRightIndents[Math.min(line, mRightIndents.length - 1)];
1316 }
1317 } else if (align == Alignment.ALIGN_CENTER) {
1318 int left = 0;
1319 if (mLeftIndents != null) {
1320 left = mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1321 }
1322 int right = 0;
1323 if (mRightIndents != null) {
1324 right = mRightIndents[Math.min(line, mRightIndents.length - 1)];
1325 }
1326 return (left - right) >> 1;
1327 } else {
1328 throw new AssertionError("unhandled alignment " + align);
1329 }
1330 }
1331
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001332 @Override
1333 public int getEllipsisCount(int line) {
1334 if (mColumns < COLUMNS_ELLIPSIZE) {
1335 return 0;
1336 }
1337
1338 return mLines[mColumns * line + ELLIPSIS_COUNT];
1339 }
1340
1341 @Override
1342 public int getEllipsisStart(int line) {
1343 if (mColumns < COLUMNS_ELLIPSIZE) {
1344 return 0;
1345 }
1346
1347 return mLines[mColumns * line + ELLIPSIS_START];
1348 }
1349
1350 @Override
1351 public int getEllipsizedWidth() {
1352 return mEllipsizedWidth;
1353 }
1354
Siyamed Sinir0745c722016-05-31 20:39:33 -07001355 /**
1356 * Return the total height of this layout.
1357 *
1358 * @param cap if true and max lines is set, returns the height of the layout at the max lines.
1359 *
1360 * @hide
1361 */
Mathew Inwood8c854f82018-09-14 12:35:36 +01001362 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
Siyamed Sinir0745c722016-05-31 20:39:33 -07001363 public int getHeight(boolean cap) {
Siyamed Sinir70ffd282018-03-08 17:19:46 -08001364 if (cap && mLineCount > mMaximumVisibleLineCount && mMaxLineHeight == -1
1365 && Log.isLoggable(TAG, Log.WARN)) {
Siyamed Sinir0745c722016-05-31 20:39:33 -07001366 Log.w(TAG, "maxLineHeight should not be -1. "
1367 + " maxLines:" + mMaximumVisibleLineCount
1368 + " lineCount:" + mLineCount);
1369 }
1370
Siyamed Sinir70ffd282018-03-08 17:19:46 -08001371 return cap && mLineCount > mMaximumVisibleLineCount && mMaxLineHeight != -1
1372 ? mMaxLineHeight : super.getHeight();
Siyamed Sinir0745c722016-05-31 20:39:33 -07001373 }
1374
Mathew Inwoodefeab842018-08-14 15:21:30 +01001375 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001376 private int mLineCount;
1377 private int mTopPadding, mBottomPadding;
Mathew Inwoodefeab842018-08-14 15:21:30 +01001378 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001379 private int mColumns;
1380 private int mEllipsizedWidth;
1381
Siyamed Sinir0745c722016-05-31 20:39:33 -07001382 /**
1383 * Keeps track if ellipsize is applied to the text.
1384 */
1385 private boolean mEllipsized;
1386
1387 /**
1388 * If maxLines is set, ellipsize is not set, and the actual line count of text is greater than
1389 * or equal to maxLine, this variable holds the ideal visual height of the maxLine'th line
1390 * starting from the top of the layout. If maxLines is not set its value will be -1.
1391 *
1392 * The value is the same as getLineTop(maxLines) for ellipsized version where structurally no
1393 * more than maxLines is contained.
1394 */
Siyamed Sinira19cd512017-08-03 22:01:56 -07001395 private int mMaxLineHeight = DEFAULT_MAX_LINE_HEIGHT;
Siyamed Sinir0745c722016-05-31 20:39:33 -07001396
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001397 private static final int COLUMNS_NORMAL = 5;
1398 private static final int COLUMNS_ELLIPSIZE = 7;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001399 private static final int START = 0;
1400 private static final int DIR = START;
1401 private static final int TAB = START;
1402 private static final int TOP = 1;
1403 private static final int DESCENT = 2;
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001404 private static final int EXTRA = 3;
1405 private static final int HYPHEN = 4;
Mathew Inwoodefeab842018-08-14 15:21:30 +01001406 @UnsupportedAppUsage
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001407 private static final int ELLIPSIS_START = 5;
1408 private static final int ELLIPSIS_COUNT = 6;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001409
Mathew Inwoodefeab842018-08-14 15:21:30 +01001410 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001411 private int[] mLines;
Mathew Inwoodefeab842018-08-14 15:21:30 +01001412 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001413 private Directions[] mLineDirections;
Mathew Inwoodefeab842018-08-14 15:21:30 +01001414 @UnsupportedAppUsage
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001415 private int mMaximumVisibleLineCount = Integer.MAX_VALUE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001416
1417 private static final int START_MASK = 0x1FFFFFFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001418 private static final int DIR_SHIFT = 30;
1419 private static final int TAB_MASK = 0x20000000;
Keisuke Kuroyanagif5af4a32016-08-31 21:40:53 +09001420 private static final int HYPHEN_MASK = 0xFF;
Seigo Nonakafb1b4792019-03-08 14:05:08 -08001421 private static final int START_HYPHEN_BITS_SHIFT = 3;
1422 private static final int START_HYPHEN_MASK = 0x18; // 0b11000
1423 private static final int END_HYPHEN_MASK = 0x7; // 0b00111
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001424
Seigo Nonakad5621ed2019-03-08 16:04:37 -08001425 private static final float TAB_INCREMENT = 20; // same as Layout, but that's private
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001426
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001427 private static final char CHAR_NEW_LINE = '\n';
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001428
1429 private static final double EXTRA_ROUNDING = 0.5;
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001430
Siyamed Sinira19cd512017-08-03 22:01:56 -07001431 private static final int DEFAULT_MAX_LINE_HEIGHT = -1;
1432
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001433 // Unused, here because of gray list private API accesses.
Deepanshu Gupta70539192014-10-15 15:57:40 -07001434 /*package*/ static class LineBreaks {
Anish Athalyec8f9e622014-07-21 15:26:34 -07001435 private static final int INITIAL_SIZE = 16;
Mathew Inwoodefeab842018-08-14 15:21:30 +01001436 @UnsupportedAppUsage
Anish Athalyec8f9e622014-07-21 15:26:34 -07001437 public int[] breaks = new int[INITIAL_SIZE];
Mathew Inwoodefeab842018-08-14 15:21:30 +01001438 @UnsupportedAppUsage
Anish Athalyec8f9e622014-07-21 15:26:34 -07001439 public float[] widths = new float[INITIAL_SIZE];
Mathew Inwoodefeab842018-08-14 15:21:30 +01001440 @UnsupportedAppUsage
Roozbeh Pournader737dfea2017-08-10 11:32:24 -07001441 public float[] ascents = new float[INITIAL_SIZE];
Mathew Inwoodefeab842018-08-14 15:21:30 +01001442 @UnsupportedAppUsage
Roozbeh Pournader737dfea2017-08-10 11:32:24 -07001443 public float[] descents = new float[INITIAL_SIZE];
Mathew Inwoodefeab842018-08-14 15:21:30 +01001444 @UnsupportedAppUsage
Roozbeh Pournader112d9c72015-08-07 12:44:41 -07001445 public int[] flags = new int[INITIAL_SIZE]; // hasTab
Anish Athalyec8f9e622014-07-21 15:26:34 -07001446 // breaks, widths, and flags should all have the same length
1447 }
1448
Seigo Nonakabafe1972017-08-24 15:30:29 -07001449 @Nullable private int[] mLeftIndents;
1450 @Nullable private int[] mRightIndents;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001451}