blob: 24d746e0dfe2f3ede99fd4a8b415e7fea3a26bbf [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 Nonaka41bb4fa2018-08-07 15:15:42 -0700646 .setJustified(b.mJustificationMode)
647 .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
740 int[] variableTabStops = null;
741 if (spanned != null) {
742 TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
743 paraEnd, TabStopSpan.class);
744 if (spans.length > 0) {
745 int[] stops = new int[spans.length];
746 for (int i = 0; i < spans.length; i++) {
747 stops[i] = spans[i].getTabStop();
748 }
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);
782 hyphenEdits[i] = res.getLineHyphenEdit(i);
783 }
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700784
785 final int remainingLineCount = mMaximumVisibleLineCount - mLineCount;
786 final boolean ellipsisMayBeApplied = ellipsize != null
787 && (ellipsize == TextUtils.TruncateAt.END
788 || (mMaximumVisibleLineCount == 1
789 && ellipsize != TextUtils.TruncateAt.MARQUEE));
790 if (0 < remainingLineCount && remainingLineCount < breakCount
791 && ellipsisMayBeApplied) {
792 // Calculate width
793 float width = 0;
Seigo Nonakaab9b4792018-09-24 17:37:15 -0700794 boolean hasTab = false; // XXX May need to also have starting hyphen edit
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700795 for (int i = remainingLineCount - 1; i < breakCount; i++) {
796 if (i == breakCount - 1) {
797 width += lineWidths[i];
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700798 } else {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700799 for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) {
Seigo Nonaka5783c132018-10-15 17:35:56 -0700800 width += measuredPara.getCharWidthAt(j);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700801 }
Mark Wagner7b5676e2009-10-16 11:44:23 -0700802 }
Seigo Nonakaab9b4792018-09-24 17:37:15 -0700803 hasTab |= hasTabs[i];
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700804 }
805 // Treat the last line and overflowed lines as a single line.
806 breaks[remainingLineCount - 1] = breaks[breakCount - 1];
807 lineWidths[remainingLineCount - 1] = width;
Seigo Nonakaab9b4792018-09-24 17:37:15 -0700808 hasTabs[remainingLineCount - 1] = hasTab;
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700809
810 breakCount = remainingLineCount;
811 }
812
813 // here is the offset of the starting character of the line we are currently
814 // measuring
815 int here = paraStart;
816
817 int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
818 int fmCacheIndex = 0;
819 int spanEndCacheIndex = 0;
820 int breakIndex = 0;
821 for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
822 // retrieve end of span
823 spanEnd = spanEndCache[spanEndCacheIndex++];
824
825 // retrieve cached metrics, order matches above
826 fm.top = fmCache[fmCacheIndex * 4 + 0];
827 fm.bottom = fmCache[fmCacheIndex * 4 + 1];
828 fm.ascent = fmCache[fmCacheIndex * 4 + 2];
829 fm.descent = fmCache[fmCacheIndex * 4 + 3];
830 fmCacheIndex++;
831
832 if (fm.top < fmTop) {
833 fmTop = fm.top;
834 }
835 if (fm.ascent < fmAscent) {
836 fmAscent = fm.ascent;
837 }
838 if (fm.descent > fmDescent) {
839 fmDescent = fm.descent;
840 }
841 if (fm.bottom > fmBottom) {
842 fmBottom = fm.bottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800843 }
844
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700845 // skip breaks ending before current span range
846 while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
847 breakIndex++;
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700848 }
849
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700850 while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
851 int endPos = paraStart + breaks[breakIndex];
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800852
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700853 boolean moreChars = (endPos < bufEnd);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700854
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700855 final int ascent = fallbackLineSpacing
856 ? Math.min(fmAscent, Math.round(ascents[breakIndex]))
857 : fmAscent;
858 final int descent = fallbackLineSpacing
859 ? Math.max(fmDescent, Math.round(descents[breakIndex]))
860 : fmDescent;
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700861
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700862 v = out(source, here, endPos,
863 ascent, descent, fmTop, fmBottom,
864 v, spacingmult, spacingadd, chooseHt, chooseHtv, fm,
Seigo Nonakaab9b4792018-09-24 17:37:15 -0700865 hasTabs[breakIndex], hyphenEdits[breakIndex], needMultiply,
866 measuredPara, bufEnd, includepad, trackpad, addLastLineSpacing, chs,
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700867 paraStart, ellipsize, ellipsizedWidth, lineWidths[breakIndex],
868 paint, moreChars);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700869
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700870 if (endPos < spanEnd) {
871 // preserve metrics for current span
Anish Athalyec8f9e622014-07-21 15:26:34 -0700872 fmTop = fm.top;
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700873 fmBottom = fm.bottom;
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700874 fmAscent = fm.ascent;
875 fmDescent = fm.descent;
876 } else {
877 fmTop = fmBottom = fmAscent = fmDescent = 0;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700878 }
879
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700880 here = endPos;
881 breakIndex++;
882
883 if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) {
884 return;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700885 }
886 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800887 }
888
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700889 if (paraEnd == bufEnd) {
890 break;
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700891 }
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700892 }
893
894 if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE)
895 && mLineCount < mMaximumVisibleLineCount) {
896 final MeasuredParagraph measuredPara =
897 MeasuredParagraph.buildForBidi(source, bufEnd, bufEnd, textDir, null);
898 paint.getFontMetricsInt(fm);
899 v = out(source,
900 bufEnd, bufEnd, fm.ascent, fm.descent,
901 fm.top, fm.bottom,
902 v,
903 spacingmult, spacingadd, null,
Seigo Nonakaab9b4792018-09-24 17:37:15 -0700904 null, fm, false, 0,
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700905 needMultiply, measuredPara, bufEnd,
906 includepad, trackpad, addLastLineSpacing, null,
907 bufStart, ellipsize,
908 ellipsizedWidth, 0, paint, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800909 }
910 }
911
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700912 private int out(final CharSequence text, final int start, final int end, int above, int below,
913 int top, int bottom, int v, final float spacingmult, final float spacingadd,
914 final LineHeightSpan[] chooseHt, final int[] chooseHtv, final Paint.FontMetricsInt fm,
Seigo Nonakaab9b4792018-09-24 17:37:15 -0700915 final boolean hasTab, final int hyphenEdit, final boolean needMultiply,
916 @NonNull final MeasuredParagraph measured,
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800917 final int bufEnd, final boolean includePad, final boolean trackPad,
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700918 final boolean addLastLineLineSpacing, final char[] chs,
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700919 final int widthStart, final TextUtils.TruncateAt ellipsize, final float ellipsisWidth,
920 final float textWidth, final TextPaint paint, final boolean moreChars) {
Siyamed Siniraf398512017-07-25 19:08:42 -0700921 final int j = mLineCount;
922 final int off = j * mColumns;
923 final int want = off + mColumns + TOP;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800924 int[] lines = mLines;
Seigo Nonaka4b6bcee2017-12-11 11:39:51 -0800925 final int dir = measured.getParagraphDir();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800926
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800927 if (want >= lines.length) {
Siyamed Siniraf398512017-07-25 19:08:42 -0700928 final int[] grow = ArrayUtils.newUnpaddedIntArray(GrowingArrayUtils.growSize(want));
Adam Lesinski776abc22014-03-07 11:30:59 -0500929 System.arraycopy(lines, 0, grow, 0, lines.length);
930 mLines = grow;
931 lines = grow;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800932 }
933
Siyamed Siniraf398512017-07-25 19:08:42 -0700934 if (j >= mLineDirections.length) {
935 final Directions[] grow = ArrayUtils.newUnpaddedArray(Directions.class,
936 GrowingArrayUtils.growSize(j));
937 System.arraycopy(mLineDirections, 0, grow, 0, mLineDirections.length);
938 mLineDirections = grow;
939 }
940
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800941 if (chooseHt != null) {
942 fm.ascent = above;
943 fm.descent = below;
944 fm.top = top;
945 fm.bottom = bottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800946
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800947 for (int i = 0; i < chooseHt.length; i++) {
948 if (chooseHt[i] instanceof LineHeightSpan.WithDensity) {
949 ((LineHeightSpan.WithDensity) chooseHt[i])
950 .chooseHeight(text, start, end, chooseHtv[i], v, fm, paint);
951 } else {
952 chooseHt[i].chooseHeight(text, start, end, chooseHtv[i], v, fm);
953 }
954 }
Eric Fischera9f1dd02009-08-12 15:00:10 -0700955
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800956 above = fm.ascent;
957 below = fm.descent;
958 top = fm.top;
959 bottom = fm.bottom;
960 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800961
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800962 boolean firstLine = (j == 0);
963 boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);
Siyamed Sinir0745c722016-05-31 20:39:33 -0700964
965 if (ellipsize != null) {
966 // If there is only one line, then do any type of ellipsis except when it is MARQUEE
967 // if there are multiple lines, just allow END ellipsis on the last line
968 boolean forceEllipsis = moreChars && (mLineCount + 1 == mMaximumVisibleLineCount);
969
970 boolean doEllipsis =
971 (((mMaximumVisibleLineCount == 1 && moreChars) || (firstLine && !moreChars)) &&
972 ellipsize != TextUtils.TruncateAt.MARQUEE) ||
973 (!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
974 ellipsize == TextUtils.TruncateAt.END);
975 if (doEllipsis) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700976 calculateEllipsis(start, end, measured, widthStart,
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800977 ellipsisWidth, ellipsize, j,
978 textWidth, paint, forceEllipsis);
Siyamed Sinir0745c722016-05-31 20:39:33 -0700979 }
980 }
981
Siyamed Sinirfb0b2dc2017-07-26 22:08:24 -0700982 final boolean lastLine;
983 if (mEllipsized) {
984 lastLine = true;
985 } else {
986 final boolean lastCharIsNewLine = widthStart != bufEnd && bufEnd > 0
987 && text.charAt(bufEnd - 1) == CHAR_NEW_LINE;
988 if (end == bufEnd && !lastCharIsNewLine) {
989 lastLine = true;
990 } else if (start == bufEnd && lastCharIsNewLine) {
991 lastLine = true;
992 } else {
993 lastLine = false;
994 }
995 }
Raph Leviend97b0972014-04-24 12:51:35 -0700996
997 if (firstLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800998 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800999 mTopPadding = top - above;
1000 }
1001
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001002 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001003 above = top;
1004 }
1005 }
Raph Leviend97b0972014-04-24 12:51:35 -07001006
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001007 int extra;
1008
Raph Leviend97b0972014-04-24 12:51:35 -07001009 if (lastLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001010 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001011 mBottomPadding = bottom - below;
1012 }
1013
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001014 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001015 below = bottom;
1016 }
1017 }
1018
Siyamed Sinir442c1512017-07-24 12:18:27 -07001019 if (needMultiply && (addLastLineLineSpacing || !lastLine)) {
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001020 double ex = (below - above) * (spacingmult - 1) + spacingadd;
Doug Felt10657582010-02-22 11:19:01 -08001021 if (ex >= 0) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001022 extra = (int)(ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -08001023 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001024 extra = -(int)(-ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -08001025 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001026 } else {
1027 extra = 0;
1028 }
1029
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001030 lines[off + START] = start;
1031 lines[off + TOP] = v;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001032 lines[off + DESCENT] = below + extra;
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001033 lines[off + EXTRA] = extra;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001034
Siyamed Sinir0745c722016-05-31 20:39:33 -07001035 // special case for non-ellipsized last visible line when maxLines is set
1036 // store the height as if it was ellipsized
1037 if (!mEllipsized && currentLineIsTheLastVisibleOne) {
1038 // below calculation as if it was the last line
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001039 int maxLineBelow = includePad ? bottom : below;
Siyamed Sinir0745c722016-05-31 20:39:33 -07001040 // similar to the calculation of v below, without the extra.
1041 mMaxLineHeight = v + (maxLineBelow - above);
1042 }
1043
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001044 v += (below - above) + extra;
1045 lines[off + mColumns + START] = end;
1046 lines[off + mColumns + TOP] = v;
1047
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001048 // TODO: could move TAB to share same column as HYPHEN, simplifying this code and gaining
1049 // one bit for start field
Seigo Nonakaab9b4792018-09-24 17:37:15 -07001050 lines[off + TAB] |= hasTab ? TAB_MASK : 0;
1051 lines[off + HYPHEN] = hyphenEdit;
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001052 lines[off + DIR] |= dir << DIR_SHIFT;
1053 mLineDirections[j] = measured.getDirections(start - widthStart, end - widthStart);
1054
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001055 mLineCount++;
1056 return v;
1057 }
1058
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001059 private void calculateEllipsis(int lineStart, int lineEnd,
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001060 MeasuredParagraph measured, int widthStart,
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001061 float avail, TextUtils.TruncateAt where,
1062 int line, float textWidth, TextPaint paint,
1063 boolean forceEllipsis) {
1064 avail -= getTotalInsets(line);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001065 if (textWidth <= avail && !forceEllipsis) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001066 // Everything fits!
1067 mLines[mColumns * line + ELLIPSIS_START] = 0;
1068 mLines[mColumns * line + ELLIPSIS_COUNT] = 0;
1069 return;
1070 }
1071
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001072 float ellipsisWidth = paint.measureText(TextUtils.getEllipsisString(where));
1073 int ellipsisStart = 0;
1074 int ellipsisCount = 0;
1075 int len = lineEnd - lineStart;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001076
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001077 // We only support start ellipsis on a single line
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001078 if (where == TextUtils.TruncateAt.START) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001079 if (mMaximumVisibleLineCount == 1) {
1080 float sum = 0;
1081 int i;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001082
Keisuke Kuroyanagied2eea12015-04-14 18:18:35 +09001083 for (i = len; i > 0; i--) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001084 float w = measured.getCharWidthAt(i - 1 + lineStart - widthStart);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001085 if (w + sum + ellipsisWidth > avail) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001086 while (i < len
1087 && measured.getCharWidthAt(i + lineStart - widthStart) == 0.0f) {
Keisuke Kuroyanagi82d1c442016-09-14 14:30:14 +09001088 i++;
1089 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001090 break;
1091 }
1092
1093 sum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001094 }
1095
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001096 ellipsisStart = 0;
1097 ellipsisCount = i;
1098 } else {
1099 if (Log.isLoggable(TAG, Log.WARN)) {
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001100 Log.w(TAG, "Start Ellipsis only supported with one line");
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001101 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001102 }
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001103 } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE ||
1104 where == TextUtils.TruncateAt.END_SMALL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001105 float sum = 0;
1106 int i;
1107
1108 for (i = 0; i < len; i++) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001109 float w = measured.getCharWidthAt(i + lineStart - widthStart);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001110
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001111 if (w + sum + ellipsisWidth > avail) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001112 break;
1113 }
1114
1115 sum += w;
1116 }
1117
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001118 ellipsisStart = i;
1119 ellipsisCount = len - i;
1120 if (forceEllipsis && ellipsisCount == 0 && len > 0) {
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -07001121 ellipsisStart = len - 1;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001122 ellipsisCount = 1;
1123 }
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001124 } else {
1125 // where = TextUtils.TruncateAt.MIDDLE We only support middle ellipsis on a single line
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001126 if (mMaximumVisibleLineCount == 1) {
1127 float lsum = 0, rsum = 0;
1128 int left = 0, right = len;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001129
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001130 float ravail = (avail - ellipsisWidth) / 2;
Raph Levien0e3c5e82014-12-04 13:26:07 -08001131 for (right = len; right > 0; right--) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001132 float w = measured.getCharWidthAt(right - 1 + lineStart - widthStart);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001133
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001134 if (w + rsum > ravail) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001135 while (right < len
1136 && measured.getCharWidthAt(right + lineStart - widthStart)
1137 == 0.0f) {
Keisuke Kuroyanagi82d1c442016-09-14 14:30:14 +09001138 right++;
1139 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001140 break;
1141 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001142 rsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001143 }
1144
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001145 float lavail = avail - ellipsisWidth - rsum;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001146 for (left = 0; left < right; left++) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001147 float w = measured.getCharWidthAt(left + lineStart - widthStart);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001148
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001149 if (w + lsum > lavail) {
1150 break;
1151 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001152
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001153 lsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001154 }
1155
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001156 ellipsisStart = left;
1157 ellipsisCount = right - left;
1158 } else {
1159 if (Log.isLoggable(TAG, Log.WARN)) {
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001160 Log.w(TAG, "Middle Ellipsis only supported with one line");
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001161 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001162 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001163 }
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001164 mEllipsized = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001165 mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
1166 mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
1167 }
1168
Selim Cinek365ec092017-03-09 00:10:52 -08001169 private float getTotalInsets(int line) {
1170 int totalIndent = 0;
1171 if (mLeftIndents != null) {
1172 totalIndent = mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1173 }
1174 if (mRightIndents != null) {
1175 totalIndent += mRightIndents[Math.min(line, mRightIndents.length - 1)];
1176 }
1177 return totalIndent;
1178 }
1179
Doug Felte8e45f22010-03-29 14:58:40 -07001180 // Override the base class so we can directly access our members,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001181 // rather than relying on member functions.
1182 // The logic mirrors that of Layout.getLineForVertical
1183 // FIXME: It may be faster to do a linear search for layouts without many lines.
Gilles Debunne66111472010-11-19 11:04:37 -08001184 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001185 public int getLineForVertical(int vertical) {
1186 int high = mLineCount;
1187 int low = -1;
1188 int guess;
1189 int[] lines = mLines;
1190 while (high - low > 1) {
1191 guess = (high + low) >> 1;
1192 if (lines[mColumns * guess + TOP] > vertical){
1193 high = guess;
1194 } else {
1195 low = guess;
1196 }
1197 }
1198 if (low < 0) {
1199 return 0;
1200 } else {
1201 return low;
1202 }
1203 }
1204
Gilles Debunne66111472010-11-19 11:04:37 -08001205 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001206 public int getLineCount() {
1207 return mLineCount;
1208 }
1209
Gilles Debunne66111472010-11-19 11:04:37 -08001210 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001211 public int getLineTop(int line) {
Raph Levien07e6c232016-04-04 12:34:06 -07001212 return mLines[mColumns * line + TOP];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001213 }
1214
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001215 /**
1216 * @hide
1217 */
1218 @Override
1219 public int getLineExtra(int line) {
1220 return mLines[mColumns * line + EXTRA];
1221 }
1222
Gilles Debunne66111472010-11-19 11:04:37 -08001223 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001224 public int getLineDescent(int line) {
Raph Levien07e6c232016-04-04 12:34:06 -07001225 return mLines[mColumns * line + DESCENT];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001226 }
1227
Gilles Debunne66111472010-11-19 11:04:37 -08001228 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001229 public int getLineStart(int line) {
1230 return mLines[mColumns * line + START] & START_MASK;
1231 }
1232
Gilles Debunne66111472010-11-19 11:04:37 -08001233 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001234 public int getParagraphDirection(int line) {
1235 return mLines[mColumns * line + DIR] >> DIR_SHIFT;
1236 }
1237
Gilles Debunne66111472010-11-19 11:04:37 -08001238 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001239 public boolean getLineContainsTab(int line) {
1240 return (mLines[mColumns * line + TAB] & TAB_MASK) != 0;
1241 }
1242
Gilles Debunne66111472010-11-19 11:04:37 -08001243 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001244 public final Directions getLineDirections(int line) {
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001245 if (line > getLineCount()) {
1246 throw new ArrayIndexOutOfBoundsException();
1247 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001248 return mLineDirections[line];
1249 }
1250
Gilles Debunne66111472010-11-19 11:04:37 -08001251 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001252 public int getTopPadding() {
1253 return mTopPadding;
1254 }
1255
Gilles Debunne66111472010-11-19 11:04:37 -08001256 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001257 public int getBottomPadding() {
1258 return mBottomPadding;
1259 }
1260
Raph Levien26d443a2015-03-30 14:18:32 -07001261 /**
Seigo Nonaka9033e0c2018-09-06 18:42:10 -07001262 * Returns the packed hyphen edit value for this line.
1263 *
1264 * You can extract start hyphen edit and end hyphen edit by using
1265 * {@link Hyphenator#unpackStartHyphenEdit(int)} and
1266 * {@link Hyphenator#unpackEndHyphenEdit(int)}.
1267 *
1268 * @param lineNumber a line number
1269 * @return A packed hyphen edit value.
Raph Levien26d443a2015-03-30 14:18:32 -07001270 * @hide
1271 */
1272 @Override
Seigo Nonaka9033e0c2018-09-06 18:42:10 -07001273 public int getHyphen(int lineNumber) {
1274 return mLines[mColumns * lineNumber + HYPHEN] & HYPHEN_MASK;
Raph Levien26d443a2015-03-30 14:18:32 -07001275 }
1276
Raph Levien2ea52902015-07-01 14:39:31 -07001277 /**
1278 * @hide
1279 */
1280 @Override
1281 public int getIndentAdjust(int line, Alignment align) {
1282 if (align == Alignment.ALIGN_LEFT) {
1283 if (mLeftIndents == null) {
1284 return 0;
1285 } else {
1286 return mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1287 }
1288 } else if (align == Alignment.ALIGN_RIGHT) {
1289 if (mRightIndents == null) {
1290 return 0;
1291 } else {
1292 return -mRightIndents[Math.min(line, mRightIndents.length - 1)];
1293 }
1294 } else if (align == Alignment.ALIGN_CENTER) {
1295 int left = 0;
1296 if (mLeftIndents != null) {
1297 left = mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1298 }
1299 int right = 0;
1300 if (mRightIndents != null) {
1301 right = mRightIndents[Math.min(line, mRightIndents.length - 1)];
1302 }
1303 return (left - right) >> 1;
1304 } else {
1305 throw new AssertionError("unhandled alignment " + align);
1306 }
1307 }
1308
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001309 @Override
1310 public int getEllipsisCount(int line) {
1311 if (mColumns < COLUMNS_ELLIPSIZE) {
1312 return 0;
1313 }
1314
1315 return mLines[mColumns * line + ELLIPSIS_COUNT];
1316 }
1317
1318 @Override
1319 public int getEllipsisStart(int line) {
1320 if (mColumns < COLUMNS_ELLIPSIZE) {
1321 return 0;
1322 }
1323
1324 return mLines[mColumns * line + ELLIPSIS_START];
1325 }
1326
1327 @Override
1328 public int getEllipsizedWidth() {
1329 return mEllipsizedWidth;
1330 }
1331
Siyamed Sinir0745c722016-05-31 20:39:33 -07001332 /**
1333 * Return the total height of this layout.
1334 *
1335 * @param cap if true and max lines is set, returns the height of the layout at the max lines.
1336 *
1337 * @hide
1338 */
Mathew Inwood8c854f82018-09-14 12:35:36 +01001339 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
Siyamed Sinir0745c722016-05-31 20:39:33 -07001340 public int getHeight(boolean cap) {
Siyamed Sinir70ffd282018-03-08 17:19:46 -08001341 if (cap && mLineCount > mMaximumVisibleLineCount && mMaxLineHeight == -1
1342 && Log.isLoggable(TAG, Log.WARN)) {
Siyamed Sinir0745c722016-05-31 20:39:33 -07001343 Log.w(TAG, "maxLineHeight should not be -1. "
1344 + " maxLines:" + mMaximumVisibleLineCount
1345 + " lineCount:" + mLineCount);
1346 }
1347
Siyamed Sinir70ffd282018-03-08 17:19:46 -08001348 return cap && mLineCount > mMaximumVisibleLineCount && mMaxLineHeight != -1
1349 ? mMaxLineHeight : super.getHeight();
Siyamed Sinir0745c722016-05-31 20:39:33 -07001350 }
1351
Mathew Inwoodefeab842018-08-14 15:21:30 +01001352 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001353 private int mLineCount;
1354 private int mTopPadding, mBottomPadding;
Mathew Inwoodefeab842018-08-14 15:21:30 +01001355 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001356 private int mColumns;
1357 private int mEllipsizedWidth;
1358
Siyamed Sinir0745c722016-05-31 20:39:33 -07001359 /**
1360 * Keeps track if ellipsize is applied to the text.
1361 */
1362 private boolean mEllipsized;
1363
1364 /**
1365 * If maxLines is set, ellipsize is not set, and the actual line count of text is greater than
1366 * or equal to maxLine, this variable holds the ideal visual height of the maxLine'th line
1367 * starting from the top of the layout. If maxLines is not set its value will be -1.
1368 *
1369 * The value is the same as getLineTop(maxLines) for ellipsized version where structurally no
1370 * more than maxLines is contained.
1371 */
Siyamed Sinira19cd512017-08-03 22:01:56 -07001372 private int mMaxLineHeight = DEFAULT_MAX_LINE_HEIGHT;
Siyamed Sinir0745c722016-05-31 20:39:33 -07001373
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001374 private static final int COLUMNS_NORMAL = 5;
1375 private static final int COLUMNS_ELLIPSIZE = 7;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001376 private static final int START = 0;
1377 private static final int DIR = START;
1378 private static final int TAB = START;
1379 private static final int TOP = 1;
1380 private static final int DESCENT = 2;
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001381 private static final int EXTRA = 3;
1382 private static final int HYPHEN = 4;
Mathew Inwoodefeab842018-08-14 15:21:30 +01001383 @UnsupportedAppUsage
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001384 private static final int ELLIPSIS_START = 5;
1385 private static final int ELLIPSIS_COUNT = 6;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001386
Mathew Inwoodefeab842018-08-14 15:21:30 +01001387 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001388 private int[] mLines;
Mathew Inwoodefeab842018-08-14 15:21:30 +01001389 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001390 private Directions[] mLineDirections;
Mathew Inwoodefeab842018-08-14 15:21:30 +01001391 @UnsupportedAppUsage
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001392 private int mMaximumVisibleLineCount = Integer.MAX_VALUE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001393
1394 private static final int START_MASK = 0x1FFFFFFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001395 private static final int DIR_SHIFT = 30;
1396 private static final int TAB_MASK = 0x20000000;
Keisuke Kuroyanagif5af4a32016-08-31 21:40:53 +09001397 private static final int HYPHEN_MASK = 0xFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001398
Doug Feltc982f602010-05-25 11:51:40 -07001399 private static final int TAB_INCREMENT = 20; // same as Layout, but that's private
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001400
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001401 private static final char CHAR_NEW_LINE = '\n';
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001402
1403 private static final double EXTRA_ROUNDING = 0.5;
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001404
Siyamed Sinira19cd512017-08-03 22:01:56 -07001405 private static final int DEFAULT_MAX_LINE_HEIGHT = -1;
1406
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001407 // Unused, here because of gray list private API accesses.
Deepanshu Gupta70539192014-10-15 15:57:40 -07001408 /*package*/ static class LineBreaks {
Anish Athalyec8f9e622014-07-21 15:26:34 -07001409 private static final int INITIAL_SIZE = 16;
Mathew Inwoodefeab842018-08-14 15:21:30 +01001410 @UnsupportedAppUsage
Anish Athalyec8f9e622014-07-21 15:26:34 -07001411 public int[] breaks = new int[INITIAL_SIZE];
Mathew Inwoodefeab842018-08-14 15:21:30 +01001412 @UnsupportedAppUsage
Anish Athalyec8f9e622014-07-21 15:26:34 -07001413 public float[] widths = new float[INITIAL_SIZE];
Mathew Inwoodefeab842018-08-14 15:21:30 +01001414 @UnsupportedAppUsage
Roozbeh Pournader737dfea2017-08-10 11:32:24 -07001415 public float[] ascents = new float[INITIAL_SIZE];
Mathew Inwoodefeab842018-08-14 15:21:30 +01001416 @UnsupportedAppUsage
Roozbeh Pournader737dfea2017-08-10 11:32:24 -07001417 public float[] descents = new float[INITIAL_SIZE];
Mathew Inwoodefeab842018-08-14 15:21:30 +01001418 @UnsupportedAppUsage
Roozbeh Pournader112d9c72015-08-07 12:44:41 -07001419 public int[] flags = new int[INITIAL_SIZE]; // hasTab
Anish Athalyec8f9e622014-07-21 15:26:34 -07001420 // breaks, widths, and flags should all have the same length
1421 }
1422
Seigo Nonakabafe1972017-08-24 15:30:29 -07001423 @Nullable private int[] mLeftIndents;
1424 @Nullable private int[] mRightIndents;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001425}