blob: d2f08536944836868aa231c7f3763e3d3b3fca95 [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;
Mathew Inwood8c854f82018-09-14 12:35:36 +010025import android.os.Build;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080026import android.text.style.LeadingMarginSpan;
Gilles Debunne66111472010-11-19 11:04:37 -080027import android.text.style.LeadingMarginSpan.LeadingMarginSpan2;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080028import android.text.style.LineHeightSpan;
Doug Feltc982f602010-05-25 11:51:40 -070029import android.text.style.TabStopSpan;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -070030import android.util.Log;
Raph Levien39b4db72015-03-25 13:18:20 -070031import android.util.Pools.SynchronizedPool;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080032
Doug Feltcb3791202011-07-07 11:57:48 -070033import com.android.internal.util.ArrayUtils;
Adam Lesinski776abc22014-03-07 11:30:59 -050034import com.android.internal.util.GrowingArrayUtils;
Doug Feltcb3791202011-07-07 11:57:48 -070035
Anish Athalyec8f9e622014-07-21 15:26:34 -070036import java.util.Arrays;
37
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080038/**
39 * StaticLayout is a Layout for text that will not be edited after it
40 * is laid out. Use {@link DynamicLayout} for text that may change.
41 * <p>This is used by widgets to control text layout. You should not need
42 * to use this class directly unless you are implementing your own widget
43 * or custom display object, or would be tempted to call
Doug Felt4e0c5e52010-03-15 16:56:02 -070044 * {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int,
45 * float, float, android.graphics.Paint)
46 * Canvas.drawText()} directly.</p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080047 */
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -080048public class StaticLayout extends Layout {
Seigo Nonaka6f1868b2017-12-01 16:24:19 -080049 /*
50 * The break iteration is done in native code. The protocol for using the native code is as
51 * follows.
52 *
53 * First, call nInit to setup native line breaker object. Then, for each paragraph, do the
54 * following:
55 *
Seigo Nonaka9d3bd082018-01-11 10:02:12 -080056 * - Create MeasuredParagraph by MeasuredParagraph.buildForStaticLayout which measures in
57 * native.
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -070058 * - Run NativeLineBreaker.computeLineBreaks() to obtain line breaks for the paragraph.
Seigo Nonaka6f1868b2017-12-01 16:24:19 -080059 *
60 * After all paragraphs, call finish() to release expensive buffers.
61 */
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -080062
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -070063 static final String TAG = "StaticLayout";
64
Raph Leviend3ab6922015-03-02 14:30:53 -080065 /**
Roozbeh Pournader22a167c2017-08-21 12:53:44 -070066 * Builder for static layouts. The builder is the preferred pattern for constructing
67 * StaticLayout objects and should be preferred over the constructors, particularly to access
68 * newer features. To build a static layout, first call {@link #obtain} with the required
69 * arguments (text, paint, and width), then call setters for optional parameters, and finally
70 * {@link #build} to build the StaticLayout object. Parameters not explicitly set will get
Raph Levien531c30c2015-04-30 16:29:59 -070071 * default values.
Raph Leviend3ab6922015-03-02 14:30:53 -080072 */
73 public final static class Builder {
Seigo Nonakab90e08f2017-10-20 15:23:51 -070074 private Builder() {}
Raph Levien4c1f12e2015-03-02 16:29:23 -080075
Raph Levien531c30c2015-04-30 16:29:59 -070076 /**
Roozbeh Pournader22a167c2017-08-21 12:53:44 -070077 * Obtain a builder for constructing StaticLayout objects.
Raph Levien531c30c2015-04-30 16:29:59 -070078 *
79 * @param source The text to be laid out, optionally with spans
80 * @param start The index of the start of the text
81 * @param end The index + 1 of the end of the text
82 * @param paint The base paint used for layout
83 * @param width The width in pixels
84 * @return a builder object used for constructing the StaticLayout
85 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -070086 @NonNull
87 public static Builder obtain(@NonNull CharSequence source, @IntRange(from = 0) int start,
88 @IntRange(from = 0) int end, @NonNull TextPaint paint,
89 @IntRange(from = 0) int width) {
Raph Levien39b4db72015-03-25 13:18:20 -070090 Builder b = sPool.acquire();
Raph Leviend3ab6922015-03-02 14:30:53 -080091 if (b == null) {
92 b = new Builder();
93 }
94
95 // set default initial values
Raph Levien39b4db72015-03-25 13:18:20 -070096 b.mText = source;
97 b.mStart = start;
98 b.mEnd = end;
Raph Levienebd66ca2015-04-30 15:27:57 -070099 b.mPaint = paint;
Raph Levien39b4db72015-03-25 13:18:20 -0700100 b.mWidth = width;
101 b.mAlignment = Alignment.ALIGN_NORMAL;
Raph Leviend3ab6922015-03-02 14:30:53 -0800102 b.mTextDir = TextDirectionHeuristics.FIRSTSTRONG_LTR;
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700103 b.mSpacingMult = DEFAULT_LINESPACING_MULTIPLIER;
104 b.mSpacingAdd = DEFAULT_LINESPACING_ADDITION;
Raph Leviend3ab6922015-03-02 14:30:53 -0800105 b.mIncludePad = true;
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700106 b.mFallbackLineSpacing = false;
Raph Levien39b4db72015-03-25 13:18:20 -0700107 b.mEllipsizedWidth = width;
Raph Leviend3ab6922015-03-02 14:30:53 -0800108 b.mEllipsize = null;
109 b.mMaxLines = Integer.MAX_VALUE;
Raph Levien3bd60c72015-05-06 14:26:35 -0700110 b.mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700111 b.mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700112 b.mJustificationMode = Layout.JUSTIFICATION_MODE_NONE;
Raph Leviend3ab6922015-03-02 14:30:53 -0800113 return b;
114 }
115
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700116 /**
117 * This method should be called after the layout is finished getting constructed and the
118 * builder needs to be cleaned up and returned to the pool.
119 */
120 private static void recycle(@NonNull Builder b) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800121 b.mPaint = null;
122 b.mText = null;
Raph Levien2ea52902015-07-01 14:39:31 -0700123 b.mLeftIndents = null;
124 b.mRightIndents = null;
Raph Levien39b4db72015-03-25 13:18:20 -0700125 sPool.release(b);
Raph Leviend3ab6922015-03-02 14:30:53 -0800126 }
127
128 // release any expensive state
129 /* package */ void finish() {
Raph Levien22ba7862015-07-29 12:34:13 -0700130 mText = null;
131 mPaint = null;
132 mLeftIndents = null;
133 mRightIndents = null;
Raph Leviend3ab6922015-03-02 14:30:53 -0800134 }
135
136 public Builder setText(CharSequence source) {
137 return setText(source, 0, source.length());
138 }
139
Raph Levien531c30c2015-04-30 16:29:59 -0700140 /**
141 * Set the text. Only useful when re-using the builder, which is done for
142 * the internal implementation of {@link DynamicLayout} but not as part
143 * of normal {@link StaticLayout} usage.
144 *
145 * @param source The text to be laid out, optionally with spans
146 * @param start The index of the start of the text
147 * @param end The index + 1 of the end of the text
148 * @return this builder, useful for chaining
149 *
150 * @hide
151 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700152 @NonNull
153 public Builder setText(@NonNull CharSequence source, int start, int end) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800154 mText = source;
155 mStart = start;
156 mEnd = end;
157 return this;
158 }
159
Raph Levien531c30c2015-04-30 16:29:59 -0700160 /**
161 * Set the paint. Internal for reuse cases only.
162 *
163 * @param paint The base paint used for layout
164 * @return this builder, useful for chaining
165 *
166 * @hide
167 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700168 @NonNull
169 public Builder setPaint(@NonNull TextPaint paint) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800170 mPaint = paint;
171 return this;
172 }
173
Raph Levien531c30c2015-04-30 16:29:59 -0700174 /**
175 * Set the width. Internal for reuse cases only.
176 *
177 * @param width The width in pixels
178 * @return this builder, useful for chaining
179 *
180 * @hide
181 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700182 @NonNull
183 public Builder setWidth(@IntRange(from = 0) int width) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800184 mWidth = width;
185 if (mEllipsize == null) {
186 mEllipsizedWidth = width;
187 }
188 return this;
189 }
190
Raph Levien531c30c2015-04-30 16:29:59 -0700191 /**
192 * Set the alignment. The default is {@link Layout.Alignment#ALIGN_NORMAL}.
193 *
194 * @param alignment Alignment for the resulting {@link StaticLayout}
195 * @return this builder, useful for chaining
196 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700197 @NonNull
198 public Builder setAlignment(@NonNull Alignment alignment) {
Raph Levien39b4db72015-03-25 13:18:20 -0700199 mAlignment = alignment;
200 return this;
201 }
202
Raph Levien531c30c2015-04-30 16:29:59 -0700203 /**
204 * Set the text direction heuristic. The text direction heuristic is used to
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700205 * resolve text direction per-paragraph based on the input text. The default is
Raph Levien531c30c2015-04-30 16:29:59 -0700206 * {@link TextDirectionHeuristics#FIRSTSTRONG_LTR}.
207 *
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700208 * @param textDir text direction heuristic for resolving bidi behavior.
Raph Levien531c30c2015-04-30 16:29:59 -0700209 * @return this builder, useful for chaining
210 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700211 @NonNull
212 public Builder setTextDirection(@NonNull TextDirectionHeuristic textDir) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800213 mTextDir = textDir;
214 return this;
215 }
216
Raph Levien531c30c2015-04-30 16:29:59 -0700217 /**
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700218 * Set line spacing parameters. Each line will have its line spacing multiplied by
219 * {@code spacingMult} and then increased by {@code spacingAdd}. The default is 0.0 for
220 * {@code spacingAdd} and 1.0 for {@code spacingMult}.
Raph Levien531c30c2015-04-30 16:29:59 -0700221 *
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700222 * @param spacingAdd the amount of line spacing addition
223 * @param spacingMult the line spacing multiplier
Raph Levien531c30c2015-04-30 16:29:59 -0700224 * @return this builder, useful for chaining
225 * @see android.widget.TextView#setLineSpacing
226 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700227 @NonNull
228 public Builder setLineSpacing(float spacingAdd, @FloatRange(from = 0.0) float spacingMult) {
Raph Levien531c30c2015-04-30 16:29:59 -0700229 mSpacingAdd = spacingAdd;
Raph Leviend3ab6922015-03-02 14:30:53 -0800230 mSpacingMult = spacingMult;
231 return this;
232 }
233
Raph Levien531c30c2015-04-30 16:29:59 -0700234 /**
235 * Set whether to include extra space beyond font ascent and descent (which is
236 * needed to avoid clipping in some languages, such as Arabic and Kannada). The
237 * default is {@code true}.
238 *
239 * @param includePad whether to include padding
240 * @return this builder, useful for chaining
241 * @see android.widget.TextView#setIncludeFontPadding
242 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700243 @NonNull
Raph Leviend3ab6922015-03-02 14:30:53 -0800244 public Builder setIncludePad(boolean includePad) {
245 mIncludePad = includePad;
246 return this;
247 }
248
Raph Levien531c30c2015-04-30 16:29:59 -0700249 /**
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700250 * Set whether to respect the ascent and descent of the fallback fonts that are used in
251 * displaying the text (which is needed to avoid text from consecutive lines running into
252 * each other). If set, fallback fonts that end up getting used can increase the ascent
253 * and descent of the lines that they are used on.
254 *
255 * <p>For backward compatibility reasons, the default is {@code false}, but setting this to
256 * true is strongly recommended. It is required to be true if text could be in languages
257 * like Burmese or Tibetan where text is typically much taller or deeper than Latin text.
258 *
259 * @param useLineSpacingFromFallbacks whether to expand linespacing based on fallback fonts
260 * @return this builder, useful for chaining
261 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700262 @NonNull
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700263 public Builder setUseLineSpacingFromFallbacks(boolean useLineSpacingFromFallbacks) {
264 mFallbackLineSpacing = useLineSpacingFromFallbacks;
265 return this;
266 }
267
268 /**
Raph Levien531c30c2015-04-30 16:29:59 -0700269 * Set the width as used for ellipsizing purposes, if it differs from the
270 * normal layout width. The default is the {@code width}
271 * passed to {@link #obtain}.
272 *
273 * @param ellipsizedWidth width used for ellipsizing, in pixels
274 * @return this builder, useful for chaining
275 * @see android.widget.TextView#setEllipsize
276 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700277 @NonNull
278 public Builder setEllipsizedWidth(@IntRange(from = 0) int ellipsizedWidth) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800279 mEllipsizedWidth = ellipsizedWidth;
280 return this;
281 }
282
Raph Levien531c30c2015-04-30 16:29:59 -0700283 /**
284 * Set ellipsizing on the layout. Causes words that are longer than the view
285 * is wide, or exceeding the number of lines (see #setMaxLines) in the case
286 * of {@link android.text.TextUtils.TruncateAt#END} or
287 * {@link android.text.TextUtils.TruncateAt#MARQUEE}, to be ellipsized instead
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700288 * of broken. The default is {@code null}, indicating no ellipsis is to be applied.
Raph Levien531c30c2015-04-30 16:29:59 -0700289 *
290 * @param ellipsize type of ellipsis behavior
291 * @return this builder, useful for chaining
292 * @see android.widget.TextView#setEllipsize
293 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700294 @NonNull
Raph Levien531c30c2015-04-30 16:29:59 -0700295 public Builder setEllipsize(@Nullable TextUtils.TruncateAt ellipsize) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800296 mEllipsize = ellipsize;
297 return this;
298 }
299
Raph Levien531c30c2015-04-30 16:29:59 -0700300 /**
301 * Set maximum number of lines. This is particularly useful in the case of
302 * ellipsizing, where it changes the layout of the last line. The default is
303 * unlimited.
304 *
305 * @param maxLines maximum number of lines in the layout
306 * @return this builder, useful for chaining
307 * @see android.widget.TextView#setMaxLines
308 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700309 @NonNull
310 public Builder setMaxLines(@IntRange(from = 0) int maxLines) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800311 mMaxLines = maxLines;
312 return this;
313 }
314
Raph Levien531c30c2015-04-30 16:29:59 -0700315 /**
316 * Set break strategy, useful for selecting high quality or balanced paragraph
317 * layout options. The default is {@link Layout#BREAK_STRATEGY_SIMPLE}.
Siyamed Sinirc0dab362018-07-22 13:48:51 -0700318 * <p/>
319 * Enabling hyphenation with either using {@link Layout#HYPHENATION_FREQUENCY_NORMAL} or
320 * {@link Layout#HYPHENATION_FREQUENCY_FULL} while line breaking is set to one of
321 * {@link Layout#BREAK_STRATEGY_BALANCED}, {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}
322 * improves the structure of text layout however has performance impact and requires more
323 * time to do the text layout.
Raph Levien531c30c2015-04-30 16:29:59 -0700324 *
325 * @param breakStrategy break strategy for paragraph layout
326 * @return this builder, useful for chaining
327 * @see android.widget.TextView#setBreakStrategy
Siyamed Sinirc0dab362018-07-22 13:48:51 -0700328 * @see #setHyphenationFrequency(int)
Raph Levien531c30c2015-04-30 16:29:59 -0700329 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700330 @NonNull
Raph Levien39b4db72015-03-25 13:18:20 -0700331 public Builder setBreakStrategy(@BreakStrategy int breakStrategy) {
332 mBreakStrategy = breakStrategy;
333 return this;
334 }
335
Raph Levien531c30c2015-04-30 16:29:59 -0700336 /**
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700337 * Set hyphenation frequency, to control the amount of automatic hyphenation used. The
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700338 * possible values are defined in {@link Layout}, by constants named with the pattern
339 * {@code HYPHENATION_FREQUENCY_*}. The default is
340 * {@link Layout#HYPHENATION_FREQUENCY_NONE}.
Siyamed Sinirc0dab362018-07-22 13:48:51 -0700341 * <p/>
342 * Enabling hyphenation with either using {@link Layout#HYPHENATION_FREQUENCY_NORMAL} or
343 * {@link Layout#HYPHENATION_FREQUENCY_FULL} while line breaking is set to one of
344 * {@link Layout#BREAK_STRATEGY_BALANCED}, {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}
345 * improves the structure of text layout however has performance impact and requires more
346 * time to do the text layout.
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700347 *
348 * @param hyphenationFrequency hyphenation frequency for the paragraph
349 * @return this builder, useful for chaining
350 * @see android.widget.TextView#setHyphenationFrequency
Siyamed Sinirc0dab362018-07-22 13:48:51 -0700351 * @see #setBreakStrategy(int)
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700352 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700353 @NonNull
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700354 public Builder setHyphenationFrequency(@HyphenationFrequency int hyphenationFrequency) {
355 mHyphenationFrequency = hyphenationFrequency;
356 return this;
357 }
358
359 /**
Raph Levien531c30c2015-04-30 16:29:59 -0700360 * Set indents. Arguments are arrays holding an indent amount, one per line, measured in
361 * pixels. For lines past the last element in the array, the last element repeats.
362 *
363 * @param leftIndents array of indent values for left margin, in pixels
364 * @param rightIndents array of indent values for right margin, in pixels
365 * @return this builder, useful for chaining
Raph Levien531c30c2015-04-30 16:29:59 -0700366 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700367 @NonNull
368 public Builder setIndents(@Nullable int[] leftIndents, @Nullable int[] rightIndents) {
Raph Levien2ea52902015-07-01 14:39:31 -0700369 mLeftIndents = leftIndents;
370 mRightIndents = rightIndents;
Raph Leviene319d5a2015-04-14 23:51:07 -0700371 return this;
372 }
373
Raph Levien70616ec2015-03-04 10:41:30 -0800374 /**
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700375 * Set paragraph justification mode. The default value is
376 * {@link Layout#JUSTIFICATION_MODE_NONE}. If the last line is too short for justification,
377 * the last line will be displayed with the alignment set by {@link #setAlignment}.
Seigo Nonaka09da71a2016-11-28 16:24:14 +0900378 *
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700379 * @param justificationMode justification mode for the paragraph.
Seigo Nonaka09da71a2016-11-28 16:24:14 +0900380 * @return this builder, useful for chaining.
381 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700382 @NonNull
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700383 public Builder setJustificationMode(@JustificationMode int justificationMode) {
384 mJustificationMode = justificationMode;
Seigo Nonaka09da71a2016-11-28 16:24:14 +0900385 return this;
386 }
387
Siyamed Sinir442c1512017-07-24 12:18:27 -0700388 /**
389 * Sets whether the line spacing should be applied for the last line. Default value is
390 * {@code false}.
391 *
392 * @hide
393 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700394 @NonNull
Siyamed Sinir442c1512017-07-24 12:18:27 -0700395 /* package */ Builder setAddLastLineLineSpacing(boolean value) {
396 mAddLastLineLineSpacing = value;
397 return this;
398 }
399
Seigo Nonaka09da71a2016-11-28 16:24:14 +0900400 /**
Raph Levien531c30c2015-04-30 16:29:59 -0700401 * Build the {@link StaticLayout} after options have been set.
402 *
403 * <p>Note: the builder object must not be reused in any way after calling this
404 * method. Setting parameters after calling this method, or calling it a second
405 * time on the same builder object, will likely lead to unexpected results.
406 *
407 * @return the newly constructed {@link StaticLayout} object
408 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700409 @NonNull
Raph Leviend3ab6922015-03-02 14:30:53 -0800410 public StaticLayout build() {
Raph Levien39b4db72015-03-25 13:18:20 -0700411 StaticLayout result = new StaticLayout(this);
412 Builder.recycle(this);
Raph Leviend3ab6922015-03-02 14:30:53 -0800413 return result;
414 }
415
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700416 private CharSequence mText;
417 private int mStart;
418 private int mEnd;
419 private TextPaint mPaint;
420 private int mWidth;
421 private Alignment mAlignment;
422 private TextDirectionHeuristic mTextDir;
423 private float mSpacingMult;
424 private float mSpacingAdd;
425 private boolean mIncludePad;
426 private boolean mFallbackLineSpacing;
427 private int mEllipsizedWidth;
428 private TextUtils.TruncateAt mEllipsize;
429 private int mMaxLines;
430 private int mBreakStrategy;
431 private int mHyphenationFrequency;
Seigo Nonakabafe1972017-08-24 15:30:29 -0700432 @Nullable private int[] mLeftIndents;
433 @Nullable private int[] mRightIndents;
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700434 private int mJustificationMode;
435 private boolean mAddLastLineLineSpacing;
Raph Leviend3ab6922015-03-02 14:30:53 -0800436
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700437 private final Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
Raph Leviend3ab6922015-03-02 14:30:53 -0800438
Siyamed Sinira273a702017-10-05 11:22:12 -0700439 private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<>(3);
Raph Leviend3ab6922015-03-02 14:30:53 -0800440 }
441
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000442 /**
443 * @deprecated Use {@link Builder} instead.
444 */
445 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800446 public StaticLayout(CharSequence source, TextPaint paint,
447 int width,
448 Alignment align, float spacingmult, float spacingadd,
449 boolean includepad) {
450 this(source, 0, source.length(), paint, width, align,
451 spacingmult, spacingadd, includepad);
452 }
453
Doug Feltcb3791202011-07-07 11:57:48 -0700454 /**
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000455 * @deprecated Use {@link Builder} instead.
Doug Feltcb3791202011-07-07 11:57:48 -0700456 */
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000457 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800458 public StaticLayout(CharSequence source, int bufstart, int bufend,
459 TextPaint paint, int outerwidth,
460 Alignment align,
461 float spacingmult, float spacingadd,
462 boolean includepad) {
463 this(source, bufstart, bufend, paint, outerwidth, align,
464 spacingmult, spacingadd, includepad, null, 0);
465 }
466
Doug Feltcb3791202011-07-07 11:57:48 -0700467 /**
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000468 * @deprecated Use {@link Builder} instead.
Doug Feltcb3791202011-07-07 11:57:48 -0700469 */
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000470 @Deprecated
Doug Feltcb3791202011-07-07 11:57:48 -0700471 public StaticLayout(CharSequence source, int bufstart, int bufend,
472 TextPaint paint, int outerwidth,
473 Alignment align,
474 float spacingmult, float spacingadd,
475 boolean includepad,
476 TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
Seigo Nonaka4e90fa22018-02-13 21:40:01 +0000477 this(source, bufstart, bufend, paint, outerwidth, align,
Doug Feltcb3791202011-07-07 11:57:48 -0700478 TextDirectionHeuristics.FIRSTSTRONG_LTR,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700479 spacingmult, spacingadd, includepad, ellipsize, ellipsizedWidth, Integer.MAX_VALUE);
Doug Feltcb3791202011-07-07 11:57:48 -0700480 }
481
482 /**
483 * @hide
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000484 * @deprecated Use {@link Builder} instead.
Doug Feltcb3791202011-07-07 11:57:48 -0700485 */
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000486 @Deprecated
Mathew Inwoodefeab842018-08-14 15:21:30 +0100487 @UnsupportedAppUsage
Seigo Nonaka4e90fa22018-02-13 21:40:01 +0000488 public StaticLayout(CharSequence source, int bufstart, int bufend,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800489 TextPaint paint, int outerwidth,
Doug Feltcb3791202011-07-07 11:57:48 -0700490 Alignment align, TextDirectionHeuristic textDir,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800491 float spacingmult, float spacingadd,
492 boolean includepad,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700493 TextUtils.TruncateAt ellipsize, int ellipsizedWidth, int maxLines) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800494 super((ellipsize == null)
Doug Felt4e0c5e52010-03-15 16:56:02 -0700495 ? source
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800496 : (source instanceof Spanned)
497 ? new SpannedEllipsizer(source)
498 : new Ellipsizer(source),
Doug Feltcb3791202011-07-07 11:57:48 -0700499 paint, outerwidth, align, textDir, spacingmult, spacingadd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800500
Raph Levienebd66ca2015-04-30 15:27:57 -0700501 Builder b = Builder.obtain(source, bufstart, bufend, paint, outerwidth)
Raph Levien39b4db72015-03-25 13:18:20 -0700502 .setAlignment(align)
Raph Leviena6a08282015-06-03 13:20:45 -0700503 .setTextDirection(textDir)
Raph Levien531c30c2015-04-30 16:29:59 -0700504 .setLineSpacing(spacingadd, spacingmult)
Raph Leviend3ab6922015-03-02 14:30:53 -0800505 .setIncludePad(includepad)
506 .setEllipsizedWidth(ellipsizedWidth)
507 .setEllipsize(ellipsize)
508 .setMaxLines(maxLines);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800509 /*
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700510 * This is annoying, but we can't refer to the layout until superclass construction is
511 * finished, and the superclass constructor wants the reference to the display text.
Doug Felt4e0c5e52010-03-15 16:56:02 -0700512 *
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700513 * In other words, the two Ellipsizer classes in Layout.java need a (Dynamic|Static)Layout
514 * as a parameter to do their calculations, but the Ellipsizers also need to be the input
515 * to the superclass's constructor (Layout). In order to go around the circular
516 * dependency, we construct the Ellipsizer with only one of the parameters, the text. And
517 * we fill in the rest of the needed information (layout, width, and method) later, here.
518 *
519 * This will break if the superclass constructor ever actually cares about the content
520 * instead of just holding the reference.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800521 */
522 if (ellipsize != null) {
523 Ellipsizer e = (Ellipsizer) getText();
524
525 e.mLayout = this;
526 e.mWidth = ellipsizedWidth;
527 e.mMethod = ellipsize;
528 mEllipsizedWidth = ellipsizedWidth;
529
530 mColumns = COLUMNS_ELLIPSIZE;
531 } else {
532 mColumns = COLUMNS_NORMAL;
533 mEllipsizedWidth = outerwidth;
534 }
535
Siyamed Siniraf398512017-07-25 19:08:42 -0700536 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2);
537 mLines = ArrayUtils.newUnpaddedIntArray(2 * mColumns);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700538 mMaximumVisibleLineCount = maxLines;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800539
Raph Levien70616ec2015-03-04 10:41:30 -0800540 generate(b, b.mIncludePad, b.mIncludePad);
Doug Felte8e45f22010-03-29 14:58:40 -0700541
Raph Leviend3ab6922015-03-02 14:30:53 -0800542 Builder.recycle(b);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800543 }
544
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000545 /**
546 * Used by DynamicLayout.
547 */
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700548 /* package */ StaticLayout(@Nullable CharSequence text) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700549 super(text, null, 0, null, 0, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800550
551 mColumns = COLUMNS_ELLIPSIZE;
Siyamed Siniraf398512017-07-25 19:08:42 -0700552 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2);
553 mLines = ArrayUtils.newUnpaddedIntArray(2 * mColumns);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800554 }
555
Raph Levien39b4db72015-03-25 13:18:20 -0700556 private StaticLayout(Builder b) {
557 super((b.mEllipsize == null)
558 ? b.mText
559 : (b.mText instanceof Spanned)
560 ? new SpannedEllipsizer(b.mText)
561 : new Ellipsizer(b.mText),
Roozbeh Pournader158dfaf2017-09-21 13:23:50 -0700562 b.mPaint, b.mWidth, b.mAlignment, b.mTextDir, b.mSpacingMult, b.mSpacingAdd);
Raph Levien39b4db72015-03-25 13:18:20 -0700563
564 if (b.mEllipsize != null) {
565 Ellipsizer e = (Ellipsizer) getText();
566
567 e.mLayout = this;
568 e.mWidth = b.mEllipsizedWidth;
569 e.mMethod = b.mEllipsize;
570 mEllipsizedWidth = b.mEllipsizedWidth;
571
572 mColumns = COLUMNS_ELLIPSIZE;
573 } else {
574 mColumns = COLUMNS_NORMAL;
575 mEllipsizedWidth = b.mWidth;
576 }
577
Siyamed Siniraf398512017-07-25 19:08:42 -0700578 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2);
579 mLines = ArrayUtils.newUnpaddedIntArray(2 * mColumns);
Raph Levien39b4db72015-03-25 13:18:20 -0700580 mMaximumVisibleLineCount = b.mMaxLines;
581
Raph Levien2ea52902015-07-01 14:39:31 -0700582 mLeftIndents = b.mLeftIndents;
583 mRightIndents = b.mRightIndents;
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700584 setJustificationMode(b.mJustificationMode);
Raph Levien2ea52902015-07-01 14:39:31 -0700585
Raph Levien39b4db72015-03-25 13:18:20 -0700586 generate(b, b.mIncludePad, b.mIncludePad);
587 }
588
Raph Leviend3ab6922015-03-02 14:30:53 -0800589 /* package */ void generate(Builder b, boolean includepad, boolean trackpad) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700590 final CharSequence source = b.mText;
Seigo Nonakafbe63bd2017-12-02 19:28:05 -0800591 final int bufStart = b.mStart;
592 final int bufEnd = b.mEnd;
Raph Leviend3ab6922015-03-02 14:30:53 -0800593 TextPaint paint = b.mPaint;
594 int outerWidth = b.mWidth;
595 TextDirectionHeuristic textDir = b.mTextDir;
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700596 final boolean fallbackLineSpacing = b.mFallbackLineSpacing;
Raph Leviend3ab6922015-03-02 14:30:53 -0800597 float spacingmult = b.mSpacingMult;
598 float spacingadd = b.mSpacingAdd;
599 float ellipsizedWidth = b.mEllipsizedWidth;
600 TextUtils.TruncateAt ellipsize = b.mEllipsize;
Siyamed Sinir442c1512017-07-24 12:18:27 -0700601 final boolean addLastLineSpacing = b.mAddLastLineLineSpacing;
Seigo Nonakaab9b4792018-09-24 17:37:15 -0700602
603 int lineBreakCapacity = 0;
604 int[] breaks = null;
605 float[] lineWidths = null;
606 float[] ascents = null;
607 float[] descents = null;
608 boolean[] hasTabs = null;
609 int[] hyphenEdits = null;
Anish Athalye88b5b0b2014-06-24 14:39:43 -0700610
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800611 mLineCount = 0;
Siyamed Sinira19cd512017-08-03 22:01:56 -0700612 mEllipsized = false;
Siyamed Sinira8d982d2017-08-21 13:27:47 -0700613 mMaxLineHeight = mMaximumVisibleLineCount < 1 ? 0 : DEFAULT_MAX_LINE_HEIGHT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800614
615 int v = 0;
616 boolean needMultiply = (spacingmult != 1 || spacingadd != 0);
617
Raph Leviend3ab6922015-03-02 14:30:53 -0800618 Paint.FontMetricsInt fm = b.mFontMetricsInt;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800619 int[] chooseHtv = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800620
Seigo Nonakabafe1972017-08-24 15:30:29 -0700621 final int[] indents;
622 if (mLeftIndents != null || mRightIndents != null) {
623 final int leftLen = mLeftIndents == null ? 0 : mLeftIndents.length;
624 final int rightLen = mRightIndents == null ? 0 : mRightIndents.length;
625 final int indentsLen = Math.max(leftLen, rightLen);
626 indents = new int[indentsLen];
627 for (int i = 0; i < leftLen; i++) {
628 indents[i] = mLeftIndents[i];
629 }
630 for (int i = 0; i < rightLen; i++) {
631 indents[i] += mRightIndents[i];
632 }
633 } else {
634 indents = null;
635 }
636
Seigo Nonaka41bb4fa2018-08-07 15:15:42 -0700637 final NativeLineBreaker lineBreaker = new NativeLineBreaker.Builder()
638 .setBreakStrategy(b.mBreakStrategy)
639 .setHyphenationFrequency(b.mHyphenationFrequency)
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700640 // TODO: Support more justification mode, e.g. letter spacing, stretching.
Seigo Nonaka41bb4fa2018-08-07 15:15:42 -0700641 .setJustified(b.mJustificationMode)
642 .setIndents(indents)
643 .build();
644
645 NativeLineBreaker.ParagraphConstraints constraints =
646 new NativeLineBreaker.ParagraphConstraints();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800647
Seigo Nonakac3328d62018-03-20 15:18:59 -0700648 PrecomputedText.ParagraphInfo[] paragraphInfo = null;
649 final Spanned spanned = (source instanceof Spanned) ? (Spanned) source : null;
Seigo Nonakabeafa1f2018-02-01 21:39:24 -0800650 if (source instanceof PrecomputedText) {
Seigo Nonakac3328d62018-03-20 15:18:59 -0700651 PrecomputedText precomputed = (PrecomputedText) source;
652 if (precomputed.canUseMeasuredResult(bufStart, bufEnd, textDir, paint,
653 b.mBreakStrategy, b.mHyphenationFrequency)) {
Seigo Nonakabeafa1f2018-02-01 21:39:24 -0800654 // Some parameters are different from the ones when measured text is created.
Seigo Nonakac3328d62018-03-20 15:18:59 -0700655 paragraphInfo = precomputed.getParagraphInfo();
Seigo Nonakafbe63bd2017-12-02 19:28:05 -0800656 }
Seigo Nonaka87b15472018-01-12 14:06:29 -0800657 }
658
Seigo Nonakac3328d62018-03-20 15:18:59 -0700659 if (paragraphInfo == null) {
Seigo Nonakabeafa1f2018-02-01 21:39:24 -0800660 final PrecomputedText.Params param = new PrecomputedText.Params(paint, textDir,
661 b.mBreakStrategy, b.mHyphenationFrequency);
Seigo Nonakac3328d62018-03-20 15:18:59 -0700662 paragraphInfo = PrecomputedText.createMeasuredParagraphs(source, param, bufStart,
663 bufEnd, false /* computeLayout */);
Seigo Nonaka4e90fa22018-02-13 21:40:01 +0000664 }
Seigo Nonakafbe63bd2017-12-02 19:28:05 -0800665
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700666 for (int paraIndex = 0; paraIndex < paragraphInfo.length; paraIndex++) {
667 final int paraStart = paraIndex == 0
668 ? bufStart : paragraphInfo[paraIndex - 1].paragraphEnd;
669 final int paraEnd = paragraphInfo[paraIndex].paragraphEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800670
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700671 int firstWidthLineCount = 1;
672 int firstWidth = outerWidth;
673 int restWidth = outerWidth;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800674
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700675 LineHeightSpan[] chooseHt = null;
676 if (spanned != null) {
677 LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
678 LeadingMarginSpan.class);
679 for (int i = 0; i < sp.length; i++) {
680 LeadingMarginSpan lms = sp[i];
681 firstWidth -= sp[i].getLeadingMargin(true);
682 restWidth -= sp[i].getLeadingMargin(false);
Doug Feltcb3791202011-07-07 11:57:48 -0700683
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700684 // LeadingMarginSpan2 is odd. The count affects all
685 // leading margin spans, not just this particular one
686 if (lms instanceof LeadingMarginSpan2) {
687 LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
688 firstWidthLineCount = Math.max(firstWidthLineCount,
689 lms2.getLeadingMarginLineCount());
690 }
691 }
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700692
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700693 chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
694
695 if (chooseHt.length == 0) {
696 chooseHt = null; // So that out() would not assume it has any contents
697 } else {
698 if (chooseHtv == null || chooseHtv.length < chooseHt.length) {
699 chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700700 }
701
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700702 for (int i = 0; i < chooseHt.length; i++) {
703 int o = spanned.getSpanStart(chooseHt[i]);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700704
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700705 if (o < paraStart) {
706 // starts in this layout, before the
707 // current paragraph
708
709 chooseHtv[i] = getLineTop(getLineForOffset(o));
710 } else {
711 // starts in this paragraph
712
713 chooseHtv[i] = v;
714 }
715 }
716 }
717 }
718 // tab stop locations
719 int[] variableTabStops = null;
720 if (spanned != null) {
721 TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
722 paraEnd, TabStopSpan.class);
723 if (spans.length > 0) {
724 int[] stops = new int[spans.length];
725 for (int i = 0; i < spans.length; i++) {
726 stops[i] = spans[i].getTabStop();
727 }
728 Arrays.sort(stops, 0, stops.length);
729 variableTabStops = stops;
730 }
731 }
732
733 final MeasuredParagraph measuredPara = paragraphInfo[paraIndex].measured;
734 final char[] chs = measuredPara.getChars();
735 final int[] spanEndCache = measuredPara.getSpanEndCache().getRawArray();
736 final int[] fmCache = measuredPara.getFontMetrics().getRawArray();
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700737
Seigo Nonaka41bb4fa2018-08-07 15:15:42 -0700738 constraints.setWidth(restWidth);
739 constraints.setIndent(firstWidth, firstWidthLineCount);
740 constraints.setTabStops(variableTabStops, TAB_INCREMENT);
741
Seigo Nonakaab9b4792018-09-24 17:37:15 -0700742 NativeLineBreaker.Result res = lineBreaker.computeLineBreaks(
743 measuredPara.getNativeMeasuredParagraph(), constraints, mLineCount);
744 int breakCount = res.getLineCount();
745 if (lineBreakCapacity < breakCount) {
746 lineBreakCapacity = breakCount;
747 breaks = new int[lineBreakCapacity];
748 lineWidths = new float[lineBreakCapacity];
749 ascents = new float[lineBreakCapacity];
750 descents = new float[lineBreakCapacity];
751 hasTabs = new boolean[lineBreakCapacity];
752 hyphenEdits = new int[lineBreakCapacity];
753 }
754
755 for (int i = 0; i < breakCount; ++i) {
756 breaks[i] = res.getLineBreakOffset(i);
757 lineWidths[i] = res.getLineWidth(i);
758 ascents[i] = res.getLineAscent(i);
759 descents[i] = res.getLineDescent(i);
760 hasTabs[i] = res.hasLineTab(i);
761 hyphenEdits[i] = res.getLineHyphenEdit(i);
762 }
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700763
764 final int remainingLineCount = mMaximumVisibleLineCount - mLineCount;
765 final boolean ellipsisMayBeApplied = ellipsize != null
766 && (ellipsize == TextUtils.TruncateAt.END
767 || (mMaximumVisibleLineCount == 1
768 && ellipsize != TextUtils.TruncateAt.MARQUEE));
769 if (0 < remainingLineCount && remainingLineCount < breakCount
770 && ellipsisMayBeApplied) {
771 // Calculate width
772 float width = 0;
Seigo Nonakaab9b4792018-09-24 17:37:15 -0700773 boolean hasTab = false; // XXX May need to also have starting hyphen edit
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700774 for (int i = remainingLineCount - 1; i < breakCount; i++) {
775 if (i == breakCount - 1) {
776 width += lineWidths[i];
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700777 } else {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700778 for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) {
779 width += measuredPara.getCharWidthAt(j - paraStart);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700780 }
Mark Wagner7b5676e2009-10-16 11:44:23 -0700781 }
Seigo Nonakaab9b4792018-09-24 17:37:15 -0700782 hasTab |= hasTabs[i];
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700783 }
784 // Treat the last line and overflowed lines as a single line.
785 breaks[remainingLineCount - 1] = breaks[breakCount - 1];
786 lineWidths[remainingLineCount - 1] = width;
Seigo Nonakaab9b4792018-09-24 17:37:15 -0700787 hasTabs[remainingLineCount - 1] = hasTab;
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700788
789 breakCount = remainingLineCount;
790 }
791
792 // here is the offset of the starting character of the line we are currently
793 // measuring
794 int here = paraStart;
795
796 int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
797 int fmCacheIndex = 0;
798 int spanEndCacheIndex = 0;
799 int breakIndex = 0;
800 for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
801 // retrieve end of span
802 spanEnd = spanEndCache[spanEndCacheIndex++];
803
804 // retrieve cached metrics, order matches above
805 fm.top = fmCache[fmCacheIndex * 4 + 0];
806 fm.bottom = fmCache[fmCacheIndex * 4 + 1];
807 fm.ascent = fmCache[fmCacheIndex * 4 + 2];
808 fm.descent = fmCache[fmCacheIndex * 4 + 3];
809 fmCacheIndex++;
810
811 if (fm.top < fmTop) {
812 fmTop = fm.top;
813 }
814 if (fm.ascent < fmAscent) {
815 fmAscent = fm.ascent;
816 }
817 if (fm.descent > fmDescent) {
818 fmDescent = fm.descent;
819 }
820 if (fm.bottom > fmBottom) {
821 fmBottom = fm.bottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800822 }
823
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700824 // skip breaks ending before current span range
825 while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
826 breakIndex++;
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700827 }
828
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700829 while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
830 int endPos = paraStart + breaks[breakIndex];
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800831
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700832 boolean moreChars = (endPos < bufEnd);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700833
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700834 final int ascent = fallbackLineSpacing
835 ? Math.min(fmAscent, Math.round(ascents[breakIndex]))
836 : fmAscent;
837 final int descent = fallbackLineSpacing
838 ? Math.max(fmDescent, Math.round(descents[breakIndex]))
839 : fmDescent;
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700840
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700841 v = out(source, here, endPos,
842 ascent, descent, fmTop, fmBottom,
843 v, spacingmult, spacingadd, chooseHt, chooseHtv, fm,
Seigo Nonakaab9b4792018-09-24 17:37:15 -0700844 hasTabs[breakIndex], hyphenEdits[breakIndex], needMultiply,
845 measuredPara, bufEnd, includepad, trackpad, addLastLineSpacing, chs,
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700846 paraStart, ellipsize, ellipsizedWidth, lineWidths[breakIndex],
847 paint, moreChars);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700848
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700849 if (endPos < spanEnd) {
850 // preserve metrics for current span
Anish Athalyec8f9e622014-07-21 15:26:34 -0700851 fmTop = fm.top;
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700852 fmBottom = fm.bottom;
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700853 fmAscent = fm.ascent;
854 fmDescent = fm.descent;
855 } else {
856 fmTop = fmBottom = fmAscent = fmDescent = 0;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700857 }
858
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700859 here = endPos;
860 breakIndex++;
861
862 if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) {
863 return;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700864 }
865 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800866 }
867
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700868 if (paraEnd == bufEnd) {
869 break;
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700870 }
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700871 }
872
873 if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE)
874 && mLineCount < mMaximumVisibleLineCount) {
875 final MeasuredParagraph measuredPara =
876 MeasuredParagraph.buildForBidi(source, bufEnd, bufEnd, textDir, null);
877 paint.getFontMetricsInt(fm);
878 v = out(source,
879 bufEnd, bufEnd, fm.ascent, fm.descent,
880 fm.top, fm.bottom,
881 v,
882 spacingmult, spacingadd, null,
Seigo Nonakaab9b4792018-09-24 17:37:15 -0700883 null, fm, false, 0,
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700884 needMultiply, measuredPara, bufEnd,
885 includepad, trackpad, addLastLineSpacing, null,
886 bufStart, ellipsize,
887 ellipsizedWidth, 0, paint, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800888 }
889 }
890
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700891 private int out(final CharSequence text, final int start, final int end, int above, int below,
892 int top, int bottom, int v, final float spacingmult, final float spacingadd,
893 final LineHeightSpan[] chooseHt, final int[] chooseHtv, final Paint.FontMetricsInt fm,
Seigo Nonakaab9b4792018-09-24 17:37:15 -0700894 final boolean hasTab, final int hyphenEdit, final boolean needMultiply,
895 @NonNull final MeasuredParagraph measured,
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800896 final int bufEnd, final boolean includePad, final boolean trackPad,
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700897 final boolean addLastLineLineSpacing, final char[] chs,
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700898 final int widthStart, final TextUtils.TruncateAt ellipsize, final float ellipsisWidth,
899 final float textWidth, final TextPaint paint, final boolean moreChars) {
Siyamed Siniraf398512017-07-25 19:08:42 -0700900 final int j = mLineCount;
901 final int off = j * mColumns;
902 final int want = off + mColumns + TOP;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800903 int[] lines = mLines;
Seigo Nonaka4b6bcee2017-12-11 11:39:51 -0800904 final int dir = measured.getParagraphDir();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800905
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800906 if (want >= lines.length) {
Siyamed Siniraf398512017-07-25 19:08:42 -0700907 final int[] grow = ArrayUtils.newUnpaddedIntArray(GrowingArrayUtils.growSize(want));
Adam Lesinski776abc22014-03-07 11:30:59 -0500908 System.arraycopy(lines, 0, grow, 0, lines.length);
909 mLines = grow;
910 lines = grow;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800911 }
912
Siyamed Siniraf398512017-07-25 19:08:42 -0700913 if (j >= mLineDirections.length) {
914 final Directions[] grow = ArrayUtils.newUnpaddedArray(Directions.class,
915 GrowingArrayUtils.growSize(j));
916 System.arraycopy(mLineDirections, 0, grow, 0, mLineDirections.length);
917 mLineDirections = grow;
918 }
919
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800920 if (chooseHt != null) {
921 fm.ascent = above;
922 fm.descent = below;
923 fm.top = top;
924 fm.bottom = bottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800925
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800926 for (int i = 0; i < chooseHt.length; i++) {
927 if (chooseHt[i] instanceof LineHeightSpan.WithDensity) {
928 ((LineHeightSpan.WithDensity) chooseHt[i])
929 .chooseHeight(text, start, end, chooseHtv[i], v, fm, paint);
930 } else {
931 chooseHt[i].chooseHeight(text, start, end, chooseHtv[i], v, fm);
932 }
933 }
Eric Fischera9f1dd02009-08-12 15:00:10 -0700934
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800935 above = fm.ascent;
936 below = fm.descent;
937 top = fm.top;
938 bottom = fm.bottom;
939 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800940
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800941 boolean firstLine = (j == 0);
942 boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);
Siyamed Sinir0745c722016-05-31 20:39:33 -0700943
944 if (ellipsize != null) {
945 // If there is only one line, then do any type of ellipsis except when it is MARQUEE
946 // if there are multiple lines, just allow END ellipsis on the last line
947 boolean forceEllipsis = moreChars && (mLineCount + 1 == mMaximumVisibleLineCount);
948
949 boolean doEllipsis =
950 (((mMaximumVisibleLineCount == 1 && moreChars) || (firstLine && !moreChars)) &&
951 ellipsize != TextUtils.TruncateAt.MARQUEE) ||
952 (!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
953 ellipsize == TextUtils.TruncateAt.END);
954 if (doEllipsis) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700955 calculateEllipsis(start, end, measured, widthStart,
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800956 ellipsisWidth, ellipsize, j,
957 textWidth, paint, forceEllipsis);
Siyamed Sinir0745c722016-05-31 20:39:33 -0700958 }
959 }
960
Siyamed Sinirfb0b2dc2017-07-26 22:08:24 -0700961 final boolean lastLine;
962 if (mEllipsized) {
963 lastLine = true;
964 } else {
965 final boolean lastCharIsNewLine = widthStart != bufEnd && bufEnd > 0
966 && text.charAt(bufEnd - 1) == CHAR_NEW_LINE;
967 if (end == bufEnd && !lastCharIsNewLine) {
968 lastLine = true;
969 } else if (start == bufEnd && lastCharIsNewLine) {
970 lastLine = true;
971 } else {
972 lastLine = false;
973 }
974 }
Raph Leviend97b0972014-04-24 12:51:35 -0700975
976 if (firstLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800977 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800978 mTopPadding = top - above;
979 }
980
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800981 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800982 above = top;
983 }
984 }
Raph Leviend97b0972014-04-24 12:51:35 -0700985
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800986 int extra;
987
Raph Leviend97b0972014-04-24 12:51:35 -0700988 if (lastLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800989 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800990 mBottomPadding = bottom - below;
991 }
992
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800993 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800994 below = bottom;
995 }
996 }
997
Siyamed Sinir442c1512017-07-24 12:18:27 -0700998 if (needMultiply && (addLastLineLineSpacing || !lastLine)) {
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800999 double ex = (below - above) * (spacingmult - 1) + spacingadd;
Doug Felt10657582010-02-22 11:19:01 -08001000 if (ex >= 0) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001001 extra = (int)(ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -08001002 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001003 extra = -(int)(-ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -08001004 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001005 } else {
1006 extra = 0;
1007 }
1008
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001009 lines[off + START] = start;
1010 lines[off + TOP] = v;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001011 lines[off + DESCENT] = below + extra;
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001012 lines[off + EXTRA] = extra;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001013
Siyamed Sinir0745c722016-05-31 20:39:33 -07001014 // special case for non-ellipsized last visible line when maxLines is set
1015 // store the height as if it was ellipsized
1016 if (!mEllipsized && currentLineIsTheLastVisibleOne) {
1017 // below calculation as if it was the last line
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001018 int maxLineBelow = includePad ? bottom : below;
Siyamed Sinir0745c722016-05-31 20:39:33 -07001019 // similar to the calculation of v below, without the extra.
1020 mMaxLineHeight = v + (maxLineBelow - above);
1021 }
1022
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001023 v += (below - above) + extra;
1024 lines[off + mColumns + START] = end;
1025 lines[off + mColumns + TOP] = v;
1026
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001027 // TODO: could move TAB to share same column as HYPHEN, simplifying this code and gaining
1028 // one bit for start field
Seigo Nonakaab9b4792018-09-24 17:37:15 -07001029 lines[off + TAB] |= hasTab ? TAB_MASK : 0;
1030 lines[off + HYPHEN] = hyphenEdit;
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001031 lines[off + DIR] |= dir << DIR_SHIFT;
1032 mLineDirections[j] = measured.getDirections(start - widthStart, end - widthStart);
1033
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001034 mLineCount++;
1035 return v;
1036 }
1037
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001038 private void calculateEllipsis(int lineStart, int lineEnd,
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001039 MeasuredParagraph measured, int widthStart,
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001040 float avail, TextUtils.TruncateAt where,
1041 int line, float textWidth, TextPaint paint,
1042 boolean forceEllipsis) {
1043 avail -= getTotalInsets(line);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001044 if (textWidth <= avail && !forceEllipsis) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001045 // Everything fits!
1046 mLines[mColumns * line + ELLIPSIS_START] = 0;
1047 mLines[mColumns * line + ELLIPSIS_COUNT] = 0;
1048 return;
1049 }
1050
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001051 float ellipsisWidth = paint.measureText(TextUtils.getEllipsisString(where));
1052 int ellipsisStart = 0;
1053 int ellipsisCount = 0;
1054 int len = lineEnd - lineStart;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001055
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001056 // We only support start ellipsis on a single line
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001057 if (where == TextUtils.TruncateAt.START) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001058 if (mMaximumVisibleLineCount == 1) {
1059 float sum = 0;
1060 int i;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001061
Keisuke Kuroyanagied2eea12015-04-14 18:18:35 +09001062 for (i = len; i > 0; i--) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001063 float w = measured.getCharWidthAt(i - 1 + lineStart - widthStart);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001064 if (w + sum + ellipsisWidth > avail) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001065 while (i < len
1066 && measured.getCharWidthAt(i + lineStart - widthStart) == 0.0f) {
Keisuke Kuroyanagi82d1c442016-09-14 14:30:14 +09001067 i++;
1068 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001069 break;
1070 }
1071
1072 sum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001073 }
1074
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001075 ellipsisStart = 0;
1076 ellipsisCount = i;
1077 } else {
1078 if (Log.isLoggable(TAG, Log.WARN)) {
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001079 Log.w(TAG, "Start Ellipsis only supported with one line");
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001080 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001081 }
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001082 } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE ||
1083 where == TextUtils.TruncateAt.END_SMALL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001084 float sum = 0;
1085 int i;
1086
1087 for (i = 0; i < len; i++) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001088 float w = measured.getCharWidthAt(i + lineStart - widthStart);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001089
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001090 if (w + sum + ellipsisWidth > avail) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001091 break;
1092 }
1093
1094 sum += w;
1095 }
1096
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001097 ellipsisStart = i;
1098 ellipsisCount = len - i;
1099 if (forceEllipsis && ellipsisCount == 0 && len > 0) {
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -07001100 ellipsisStart = len - 1;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001101 ellipsisCount = 1;
1102 }
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001103 } else {
1104 // where = TextUtils.TruncateAt.MIDDLE We only support middle ellipsis on a single line
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001105 if (mMaximumVisibleLineCount == 1) {
1106 float lsum = 0, rsum = 0;
1107 int left = 0, right = len;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001108
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001109 float ravail = (avail - ellipsisWidth) / 2;
Raph Levien0e3c5e82014-12-04 13:26:07 -08001110 for (right = len; right > 0; right--) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001111 float w = measured.getCharWidthAt(right - 1 + lineStart - widthStart);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001112
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001113 if (w + rsum > ravail) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001114 while (right < len
1115 && measured.getCharWidthAt(right + lineStart - widthStart)
1116 == 0.0f) {
Keisuke Kuroyanagi82d1c442016-09-14 14:30:14 +09001117 right++;
1118 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001119 break;
1120 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001121 rsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001122 }
1123
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001124 float lavail = avail - ellipsisWidth - rsum;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001125 for (left = 0; left < right; left++) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001126 float w = measured.getCharWidthAt(left + lineStart - widthStart);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001127
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001128 if (w + lsum > lavail) {
1129 break;
1130 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001131
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001132 lsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001133 }
1134
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001135 ellipsisStart = left;
1136 ellipsisCount = right - left;
1137 } else {
1138 if (Log.isLoggable(TAG, Log.WARN)) {
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001139 Log.w(TAG, "Middle Ellipsis only supported with one line");
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001140 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001141 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001142 }
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001143 mEllipsized = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001144 mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
1145 mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
1146 }
1147
Selim Cinek365ec092017-03-09 00:10:52 -08001148 private float getTotalInsets(int line) {
1149 int totalIndent = 0;
1150 if (mLeftIndents != null) {
1151 totalIndent = mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1152 }
1153 if (mRightIndents != null) {
1154 totalIndent += mRightIndents[Math.min(line, mRightIndents.length - 1)];
1155 }
1156 return totalIndent;
1157 }
1158
Doug Felte8e45f22010-03-29 14:58:40 -07001159 // Override the base class so we can directly access our members,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001160 // rather than relying on member functions.
1161 // The logic mirrors that of Layout.getLineForVertical
1162 // FIXME: It may be faster to do a linear search for layouts without many lines.
Gilles Debunne66111472010-11-19 11:04:37 -08001163 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001164 public int getLineForVertical(int vertical) {
1165 int high = mLineCount;
1166 int low = -1;
1167 int guess;
1168 int[] lines = mLines;
1169 while (high - low > 1) {
1170 guess = (high + low) >> 1;
1171 if (lines[mColumns * guess + TOP] > vertical){
1172 high = guess;
1173 } else {
1174 low = guess;
1175 }
1176 }
1177 if (low < 0) {
1178 return 0;
1179 } else {
1180 return low;
1181 }
1182 }
1183
Gilles Debunne66111472010-11-19 11:04:37 -08001184 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001185 public int getLineCount() {
1186 return mLineCount;
1187 }
1188
Gilles Debunne66111472010-11-19 11:04:37 -08001189 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001190 public int getLineTop(int line) {
Raph Levien07e6c232016-04-04 12:34:06 -07001191 return mLines[mColumns * line + TOP];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001192 }
1193
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001194 /**
1195 * @hide
1196 */
1197 @Override
1198 public int getLineExtra(int line) {
1199 return mLines[mColumns * line + EXTRA];
1200 }
1201
Gilles Debunne66111472010-11-19 11:04:37 -08001202 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001203 public int getLineDescent(int line) {
Raph Levien07e6c232016-04-04 12:34:06 -07001204 return mLines[mColumns * line + DESCENT];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001205 }
1206
Gilles Debunne66111472010-11-19 11:04:37 -08001207 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001208 public int getLineStart(int line) {
1209 return mLines[mColumns * line + START] & START_MASK;
1210 }
1211
Gilles Debunne66111472010-11-19 11:04:37 -08001212 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001213 public int getParagraphDirection(int line) {
1214 return mLines[mColumns * line + DIR] >> DIR_SHIFT;
1215 }
1216
Gilles Debunne66111472010-11-19 11:04:37 -08001217 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001218 public boolean getLineContainsTab(int line) {
1219 return (mLines[mColumns * line + TAB] & TAB_MASK) != 0;
1220 }
1221
Gilles Debunne66111472010-11-19 11:04:37 -08001222 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001223 public final Directions getLineDirections(int line) {
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001224 if (line > getLineCount()) {
1225 throw new ArrayIndexOutOfBoundsException();
1226 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001227 return mLineDirections[line];
1228 }
1229
Gilles Debunne66111472010-11-19 11:04:37 -08001230 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001231 public int getTopPadding() {
1232 return mTopPadding;
1233 }
1234
Gilles Debunne66111472010-11-19 11:04:37 -08001235 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001236 public int getBottomPadding() {
1237 return mBottomPadding;
1238 }
1239
Raph Levien26d443a2015-03-30 14:18:32 -07001240 /**
Seigo Nonaka9033e0c2018-09-06 18:42:10 -07001241 * Returns the packed hyphen edit value for this line.
1242 *
1243 * You can extract start hyphen edit and end hyphen edit by using
1244 * {@link Hyphenator#unpackStartHyphenEdit(int)} and
1245 * {@link Hyphenator#unpackEndHyphenEdit(int)}.
1246 *
1247 * @param lineNumber a line number
1248 * @return A packed hyphen edit value.
Raph Levien26d443a2015-03-30 14:18:32 -07001249 * @hide
1250 */
1251 @Override
Seigo Nonaka9033e0c2018-09-06 18:42:10 -07001252 public int getHyphen(int lineNumber) {
1253 return mLines[mColumns * lineNumber + HYPHEN] & HYPHEN_MASK;
Raph Levien26d443a2015-03-30 14:18:32 -07001254 }
1255
Raph Levien2ea52902015-07-01 14:39:31 -07001256 /**
1257 * @hide
1258 */
1259 @Override
1260 public int getIndentAdjust(int line, Alignment align) {
1261 if (align == Alignment.ALIGN_LEFT) {
1262 if (mLeftIndents == null) {
1263 return 0;
1264 } else {
1265 return mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1266 }
1267 } else if (align == Alignment.ALIGN_RIGHT) {
1268 if (mRightIndents == null) {
1269 return 0;
1270 } else {
1271 return -mRightIndents[Math.min(line, mRightIndents.length - 1)];
1272 }
1273 } else if (align == Alignment.ALIGN_CENTER) {
1274 int left = 0;
1275 if (mLeftIndents != null) {
1276 left = mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1277 }
1278 int right = 0;
1279 if (mRightIndents != null) {
1280 right = mRightIndents[Math.min(line, mRightIndents.length - 1)];
1281 }
1282 return (left - right) >> 1;
1283 } else {
1284 throw new AssertionError("unhandled alignment " + align);
1285 }
1286 }
1287
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001288 @Override
1289 public int getEllipsisCount(int line) {
1290 if (mColumns < COLUMNS_ELLIPSIZE) {
1291 return 0;
1292 }
1293
1294 return mLines[mColumns * line + ELLIPSIS_COUNT];
1295 }
1296
1297 @Override
1298 public int getEllipsisStart(int line) {
1299 if (mColumns < COLUMNS_ELLIPSIZE) {
1300 return 0;
1301 }
1302
1303 return mLines[mColumns * line + ELLIPSIS_START];
1304 }
1305
1306 @Override
1307 public int getEllipsizedWidth() {
1308 return mEllipsizedWidth;
1309 }
1310
Siyamed Sinir0745c722016-05-31 20:39:33 -07001311 /**
1312 * Return the total height of this layout.
1313 *
1314 * @param cap if true and max lines is set, returns the height of the layout at the max lines.
1315 *
1316 * @hide
1317 */
Mathew Inwood8c854f82018-09-14 12:35:36 +01001318 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
Siyamed Sinir0745c722016-05-31 20:39:33 -07001319 public int getHeight(boolean cap) {
Siyamed Sinir70ffd282018-03-08 17:19:46 -08001320 if (cap && mLineCount > mMaximumVisibleLineCount && mMaxLineHeight == -1
1321 && Log.isLoggable(TAG, Log.WARN)) {
Siyamed Sinir0745c722016-05-31 20:39:33 -07001322 Log.w(TAG, "maxLineHeight should not be -1. "
1323 + " maxLines:" + mMaximumVisibleLineCount
1324 + " lineCount:" + mLineCount);
1325 }
1326
Siyamed Sinir70ffd282018-03-08 17:19:46 -08001327 return cap && mLineCount > mMaximumVisibleLineCount && mMaxLineHeight != -1
1328 ? mMaxLineHeight : super.getHeight();
Siyamed Sinir0745c722016-05-31 20:39:33 -07001329 }
1330
Mathew Inwoodefeab842018-08-14 15:21:30 +01001331 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001332 private int mLineCount;
1333 private int mTopPadding, mBottomPadding;
Mathew Inwoodefeab842018-08-14 15:21:30 +01001334 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001335 private int mColumns;
1336 private int mEllipsizedWidth;
1337
Siyamed Sinir0745c722016-05-31 20:39:33 -07001338 /**
1339 * Keeps track if ellipsize is applied to the text.
1340 */
1341 private boolean mEllipsized;
1342
1343 /**
1344 * If maxLines is set, ellipsize is not set, and the actual line count of text is greater than
1345 * or equal to maxLine, this variable holds the ideal visual height of the maxLine'th line
1346 * starting from the top of the layout. If maxLines is not set its value will be -1.
1347 *
1348 * The value is the same as getLineTop(maxLines) for ellipsized version where structurally no
1349 * more than maxLines is contained.
1350 */
Siyamed Sinira19cd512017-08-03 22:01:56 -07001351 private int mMaxLineHeight = DEFAULT_MAX_LINE_HEIGHT;
Siyamed Sinir0745c722016-05-31 20:39:33 -07001352
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001353 private static final int COLUMNS_NORMAL = 5;
1354 private static final int COLUMNS_ELLIPSIZE = 7;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001355 private static final int START = 0;
1356 private static final int DIR = START;
1357 private static final int TAB = START;
1358 private static final int TOP = 1;
1359 private static final int DESCENT = 2;
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001360 private static final int EXTRA = 3;
1361 private static final int HYPHEN = 4;
Mathew Inwoodefeab842018-08-14 15:21:30 +01001362 @UnsupportedAppUsage
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001363 private static final int ELLIPSIS_START = 5;
1364 private static final int ELLIPSIS_COUNT = 6;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001365
Mathew Inwoodefeab842018-08-14 15:21:30 +01001366 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001367 private int[] mLines;
Mathew Inwoodefeab842018-08-14 15:21:30 +01001368 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001369 private Directions[] mLineDirections;
Mathew Inwoodefeab842018-08-14 15:21:30 +01001370 @UnsupportedAppUsage
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001371 private int mMaximumVisibleLineCount = Integer.MAX_VALUE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001372
1373 private static final int START_MASK = 0x1FFFFFFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001374 private static final int DIR_SHIFT = 30;
1375 private static final int TAB_MASK = 0x20000000;
Keisuke Kuroyanagif5af4a32016-08-31 21:40:53 +09001376 private static final int HYPHEN_MASK = 0xFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001377
Doug Feltc982f602010-05-25 11:51:40 -07001378 private static final int TAB_INCREMENT = 20; // same as Layout, but that's private
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001379
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001380 private static final char CHAR_NEW_LINE = '\n';
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001381
1382 private static final double EXTRA_ROUNDING = 0.5;
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001383
Siyamed Sinira19cd512017-08-03 22:01:56 -07001384 private static final int DEFAULT_MAX_LINE_HEIGHT = -1;
1385
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001386 // Unused, here because of gray list private API accesses.
Deepanshu Gupta70539192014-10-15 15:57:40 -07001387 /*package*/ static class LineBreaks {
Anish Athalyec8f9e622014-07-21 15:26:34 -07001388 private static final int INITIAL_SIZE = 16;
Mathew Inwoodefeab842018-08-14 15:21:30 +01001389 @UnsupportedAppUsage
Anish Athalyec8f9e622014-07-21 15:26:34 -07001390 public int[] breaks = new int[INITIAL_SIZE];
Mathew Inwoodefeab842018-08-14 15:21:30 +01001391 @UnsupportedAppUsage
Anish Athalyec8f9e622014-07-21 15:26:34 -07001392 public float[] widths = new float[INITIAL_SIZE];
Mathew Inwoodefeab842018-08-14 15:21:30 +01001393 @UnsupportedAppUsage
Roozbeh Pournader737dfea2017-08-10 11:32:24 -07001394 public float[] ascents = new float[INITIAL_SIZE];
Mathew Inwoodefeab842018-08-14 15:21:30 +01001395 @UnsupportedAppUsage
Roozbeh Pournader737dfea2017-08-10 11:32:24 -07001396 public float[] descents = new float[INITIAL_SIZE];
Mathew Inwoodefeab842018-08-14 15:21:30 +01001397 @UnsupportedAppUsage
Roozbeh Pournader112d9c72015-08-07 12:44:41 -07001398 public int[] flags = new int[INITIAL_SIZE]; // hasTab
Anish Athalyec8f9e622014-07-21 15:26:34 -07001399 // breaks, widths, and flags should all have the same length
1400 }
1401
Seigo Nonakabafe1972017-08-24 15:30:29 -07001402 @Nullable private int[] mLeftIndents;
1403 @Nullable private int[] mRightIndents;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001404}