blob: e1ffef01feae1c6f0ce36eaff8590b1f4b44f467 [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 Nonaka6f11c6e2018-07-24 11:26:18 -0700602 NativeLineBreaker.LineBreaks lineBreaks = new NativeLineBreaker.LineBreaks();
Anish Athalye88b5b0b2014-06-24 14:39:43 -0700603
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800604 mLineCount = 0;
Siyamed Sinira19cd512017-08-03 22:01:56 -0700605 mEllipsized = false;
Siyamed Sinira8d982d2017-08-21 13:27:47 -0700606 mMaxLineHeight = mMaximumVisibleLineCount < 1 ? 0 : DEFAULT_MAX_LINE_HEIGHT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800607
608 int v = 0;
609 boolean needMultiply = (spacingmult != 1 || spacingadd != 0);
610
Raph Leviend3ab6922015-03-02 14:30:53 -0800611 Paint.FontMetricsInt fm = b.mFontMetricsInt;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800612 int[] chooseHtv = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800613
Seigo Nonakabafe1972017-08-24 15:30:29 -0700614 final int[] indents;
615 if (mLeftIndents != null || mRightIndents != null) {
616 final int leftLen = mLeftIndents == null ? 0 : mLeftIndents.length;
617 final int rightLen = mRightIndents == null ? 0 : mRightIndents.length;
618 final int indentsLen = Math.max(leftLen, rightLen);
619 indents = new int[indentsLen];
620 for (int i = 0; i < leftLen; i++) {
621 indents[i] = mLeftIndents[i];
622 }
623 for (int i = 0; i < rightLen; i++) {
624 indents[i] += mRightIndents[i];
625 }
626 } else {
627 indents = null;
628 }
629
Seigo Nonaka41bb4fa2018-08-07 15:15:42 -0700630 final NativeLineBreaker lineBreaker = new NativeLineBreaker.Builder()
631 .setBreakStrategy(b.mBreakStrategy)
632 .setHyphenationFrequency(b.mHyphenationFrequency)
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700633 // TODO: Support more justification mode, e.g. letter spacing, stretching.
Seigo Nonaka41bb4fa2018-08-07 15:15:42 -0700634 .setJustified(b.mJustificationMode)
635 .setIndents(indents)
636 .build();
637
638 NativeLineBreaker.ParagraphConstraints constraints =
639 new NativeLineBreaker.ParagraphConstraints();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800640
Seigo Nonakac3328d62018-03-20 15:18:59 -0700641 PrecomputedText.ParagraphInfo[] paragraphInfo = null;
642 final Spanned spanned = (source instanceof Spanned) ? (Spanned) source : null;
Seigo Nonakabeafa1f2018-02-01 21:39:24 -0800643 if (source instanceof PrecomputedText) {
Seigo Nonakac3328d62018-03-20 15:18:59 -0700644 PrecomputedText precomputed = (PrecomputedText) source;
645 if (precomputed.canUseMeasuredResult(bufStart, bufEnd, textDir, paint,
646 b.mBreakStrategy, b.mHyphenationFrequency)) {
Seigo Nonakabeafa1f2018-02-01 21:39:24 -0800647 // Some parameters are different from the ones when measured text is created.
Seigo Nonakac3328d62018-03-20 15:18:59 -0700648 paragraphInfo = precomputed.getParagraphInfo();
Seigo Nonakafbe63bd2017-12-02 19:28:05 -0800649 }
Seigo Nonaka87b15472018-01-12 14:06:29 -0800650 }
651
Seigo Nonakac3328d62018-03-20 15:18:59 -0700652 if (paragraphInfo == null) {
Seigo Nonakabeafa1f2018-02-01 21:39:24 -0800653 final PrecomputedText.Params param = new PrecomputedText.Params(paint, textDir,
654 b.mBreakStrategy, b.mHyphenationFrequency);
Seigo Nonakac3328d62018-03-20 15:18:59 -0700655 paragraphInfo = PrecomputedText.createMeasuredParagraphs(source, param, bufStart,
656 bufEnd, false /* computeLayout */);
Seigo Nonaka4e90fa22018-02-13 21:40:01 +0000657 }
Seigo Nonakafbe63bd2017-12-02 19:28:05 -0800658
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700659 for (int paraIndex = 0; paraIndex < paragraphInfo.length; paraIndex++) {
660 final int paraStart = paraIndex == 0
661 ? bufStart : paragraphInfo[paraIndex - 1].paragraphEnd;
662 final int paraEnd = paragraphInfo[paraIndex].paragraphEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800663
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700664 int firstWidthLineCount = 1;
665 int firstWidth = outerWidth;
666 int restWidth = outerWidth;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800667
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700668 LineHeightSpan[] chooseHt = null;
669 if (spanned != null) {
670 LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
671 LeadingMarginSpan.class);
672 for (int i = 0; i < sp.length; i++) {
673 LeadingMarginSpan lms = sp[i];
674 firstWidth -= sp[i].getLeadingMargin(true);
675 restWidth -= sp[i].getLeadingMargin(false);
Doug Feltcb3791202011-07-07 11:57:48 -0700676
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700677 // LeadingMarginSpan2 is odd. The count affects all
678 // leading margin spans, not just this particular one
679 if (lms instanceof LeadingMarginSpan2) {
680 LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
681 firstWidthLineCount = Math.max(firstWidthLineCount,
682 lms2.getLeadingMarginLineCount());
683 }
684 }
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700685
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700686 chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
687
688 if (chooseHt.length == 0) {
689 chooseHt = null; // So that out() would not assume it has any contents
690 } else {
691 if (chooseHtv == null || chooseHtv.length < chooseHt.length) {
692 chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700693 }
694
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700695 for (int i = 0; i < chooseHt.length; i++) {
696 int o = spanned.getSpanStart(chooseHt[i]);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700697
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700698 if (o < paraStart) {
699 // starts in this layout, before the
700 // current paragraph
701
702 chooseHtv[i] = getLineTop(getLineForOffset(o));
703 } else {
704 // starts in this paragraph
705
706 chooseHtv[i] = v;
707 }
708 }
709 }
710 }
711 // tab stop locations
712 int[] variableTabStops = null;
713 if (spanned != null) {
714 TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
715 paraEnd, TabStopSpan.class);
716 if (spans.length > 0) {
717 int[] stops = new int[spans.length];
718 for (int i = 0; i < spans.length; i++) {
719 stops[i] = spans[i].getTabStop();
720 }
721 Arrays.sort(stops, 0, stops.length);
722 variableTabStops = stops;
723 }
724 }
725
726 final MeasuredParagraph measuredPara = paragraphInfo[paraIndex].measured;
727 final char[] chs = measuredPara.getChars();
728 final int[] spanEndCache = measuredPara.getSpanEndCache().getRawArray();
729 final int[] fmCache = measuredPara.getFontMetrics().getRawArray();
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700730
Seigo Nonaka41bb4fa2018-08-07 15:15:42 -0700731 constraints.setWidth(restWidth);
732 constraints.setIndent(firstWidth, firstWidthLineCount);
733 constraints.setTabStops(variableTabStops, TAB_INCREMENT);
734
735 lineBreaker.computeLineBreaks(measuredPara.getNativeMeasuredParagraph(),
736 constraints, mLineCount, lineBreaks);
737 int breakCount = lineBreaks.breakCount;
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700738 final int[] breaks = lineBreaks.breaks;
739 final float[] lineWidths = lineBreaks.widths;
740 final float[] ascents = lineBreaks.ascents;
741 final float[] descents = lineBreaks.descents;
742 final int[] flags = lineBreaks.flags;
743
744 final int remainingLineCount = mMaximumVisibleLineCount - mLineCount;
745 final boolean ellipsisMayBeApplied = ellipsize != null
746 && (ellipsize == TextUtils.TruncateAt.END
747 || (mMaximumVisibleLineCount == 1
748 && ellipsize != TextUtils.TruncateAt.MARQUEE));
749 if (0 < remainingLineCount && remainingLineCount < breakCount
750 && ellipsisMayBeApplied) {
751 // Calculate width
752 float width = 0;
753 int flag = 0; // XXX May need to also have starting hyphen edit
754 for (int i = remainingLineCount - 1; i < breakCount; i++) {
755 if (i == breakCount - 1) {
756 width += lineWidths[i];
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700757 } else {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700758 for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) {
759 width += measuredPara.getCharWidthAt(j - paraStart);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700760 }
Mark Wagner7b5676e2009-10-16 11:44:23 -0700761 }
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700762 flag |= flags[i] & TAB_MASK;
763 }
764 // Treat the last line and overflowed lines as a single line.
765 breaks[remainingLineCount - 1] = breaks[breakCount - 1];
766 lineWidths[remainingLineCount - 1] = width;
767 flags[remainingLineCount - 1] = flag;
768
769 breakCount = remainingLineCount;
770 }
771
772 // here is the offset of the starting character of the line we are currently
773 // measuring
774 int here = paraStart;
775
776 int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
777 int fmCacheIndex = 0;
778 int spanEndCacheIndex = 0;
779 int breakIndex = 0;
780 for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
781 // retrieve end of span
782 spanEnd = spanEndCache[spanEndCacheIndex++];
783
784 // retrieve cached metrics, order matches above
785 fm.top = fmCache[fmCacheIndex * 4 + 0];
786 fm.bottom = fmCache[fmCacheIndex * 4 + 1];
787 fm.ascent = fmCache[fmCacheIndex * 4 + 2];
788 fm.descent = fmCache[fmCacheIndex * 4 + 3];
789 fmCacheIndex++;
790
791 if (fm.top < fmTop) {
792 fmTop = fm.top;
793 }
794 if (fm.ascent < fmAscent) {
795 fmAscent = fm.ascent;
796 }
797 if (fm.descent > fmDescent) {
798 fmDescent = fm.descent;
799 }
800 if (fm.bottom > fmBottom) {
801 fmBottom = fm.bottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800802 }
803
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700804 // skip breaks ending before current span range
805 while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
806 breakIndex++;
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700807 }
808
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700809 while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
810 int endPos = paraStart + breaks[breakIndex];
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800811
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700812 boolean moreChars = (endPos < bufEnd);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700813
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700814 final int ascent = fallbackLineSpacing
815 ? Math.min(fmAscent, Math.round(ascents[breakIndex]))
816 : fmAscent;
817 final int descent = fallbackLineSpacing
818 ? Math.max(fmDescent, Math.round(descents[breakIndex]))
819 : fmDescent;
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700820
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700821 v = out(source, here, endPos,
822 ascent, descent, fmTop, fmBottom,
823 v, spacingmult, spacingadd, chooseHt, chooseHtv, fm,
824 flags[breakIndex], needMultiply, measuredPara, bufEnd,
825 includepad, trackpad, addLastLineSpacing, chs,
826 paraStart, ellipsize, ellipsizedWidth, lineWidths[breakIndex],
827 paint, moreChars);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700828
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700829 if (endPos < spanEnd) {
830 // preserve metrics for current span
Anish Athalyec8f9e622014-07-21 15:26:34 -0700831 fmTop = fm.top;
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700832 fmBottom = fm.bottom;
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700833 fmAscent = fm.ascent;
834 fmDescent = fm.descent;
835 } else {
836 fmTop = fmBottom = fmAscent = fmDescent = 0;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700837 }
838
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700839 here = endPos;
840 breakIndex++;
841
842 if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) {
843 return;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700844 }
845 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800846 }
847
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700848 if (paraEnd == bufEnd) {
849 break;
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700850 }
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700851 }
852
853 if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE)
854 && mLineCount < mMaximumVisibleLineCount) {
855 final MeasuredParagraph measuredPara =
856 MeasuredParagraph.buildForBidi(source, bufEnd, bufEnd, textDir, null);
857 paint.getFontMetricsInt(fm);
858 v = out(source,
859 bufEnd, bufEnd, fm.ascent, fm.descent,
860 fm.top, fm.bottom,
861 v,
862 spacingmult, spacingadd, null,
863 null, fm, 0,
864 needMultiply, measuredPara, bufEnd,
865 includepad, trackpad, addLastLineSpacing, null,
866 bufStart, ellipsize,
867 ellipsizedWidth, 0, paint, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800868 }
869 }
870
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700871 private int out(final CharSequence text, final int start, final int end, int above, int below,
872 int top, int bottom, int v, final float spacingmult, final float spacingadd,
873 final LineHeightSpan[] chooseHt, final int[] chooseHtv, final Paint.FontMetricsInt fm,
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800874 final int flags, final boolean needMultiply, @NonNull final MeasuredParagraph measured,
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800875 final int bufEnd, final boolean includePad, final boolean trackPad,
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700876 final boolean addLastLineLineSpacing, final char[] chs,
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700877 final int widthStart, final TextUtils.TruncateAt ellipsize, final float ellipsisWidth,
878 final float textWidth, final TextPaint paint, final boolean moreChars) {
Siyamed Siniraf398512017-07-25 19:08:42 -0700879 final int j = mLineCount;
880 final int off = j * mColumns;
881 final int want = off + mColumns + TOP;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800882 int[] lines = mLines;
Seigo Nonaka4b6bcee2017-12-11 11:39:51 -0800883 final int dir = measured.getParagraphDir();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800884
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800885 if (want >= lines.length) {
Siyamed Siniraf398512017-07-25 19:08:42 -0700886 final int[] grow = ArrayUtils.newUnpaddedIntArray(GrowingArrayUtils.growSize(want));
Adam Lesinski776abc22014-03-07 11:30:59 -0500887 System.arraycopy(lines, 0, grow, 0, lines.length);
888 mLines = grow;
889 lines = grow;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800890 }
891
Siyamed Siniraf398512017-07-25 19:08:42 -0700892 if (j >= mLineDirections.length) {
893 final Directions[] grow = ArrayUtils.newUnpaddedArray(Directions.class,
894 GrowingArrayUtils.growSize(j));
895 System.arraycopy(mLineDirections, 0, grow, 0, mLineDirections.length);
896 mLineDirections = grow;
897 }
898
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800899 if (chooseHt != null) {
900 fm.ascent = above;
901 fm.descent = below;
902 fm.top = top;
903 fm.bottom = bottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800904
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800905 for (int i = 0; i < chooseHt.length; i++) {
906 if (chooseHt[i] instanceof LineHeightSpan.WithDensity) {
907 ((LineHeightSpan.WithDensity) chooseHt[i])
908 .chooseHeight(text, start, end, chooseHtv[i], v, fm, paint);
909 } else {
910 chooseHt[i].chooseHeight(text, start, end, chooseHtv[i], v, fm);
911 }
912 }
Eric Fischera9f1dd02009-08-12 15:00:10 -0700913
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800914 above = fm.ascent;
915 below = fm.descent;
916 top = fm.top;
917 bottom = fm.bottom;
918 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800919
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800920 boolean firstLine = (j == 0);
921 boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);
Siyamed Sinir0745c722016-05-31 20:39:33 -0700922
923 if (ellipsize != null) {
924 // If there is only one line, then do any type of ellipsis except when it is MARQUEE
925 // if there are multiple lines, just allow END ellipsis on the last line
926 boolean forceEllipsis = moreChars && (mLineCount + 1 == mMaximumVisibleLineCount);
927
928 boolean doEllipsis =
929 (((mMaximumVisibleLineCount == 1 && moreChars) || (firstLine && !moreChars)) &&
930 ellipsize != TextUtils.TruncateAt.MARQUEE) ||
931 (!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
932 ellipsize == TextUtils.TruncateAt.END);
933 if (doEllipsis) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700934 calculateEllipsis(start, end, measured, widthStart,
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800935 ellipsisWidth, ellipsize, j,
936 textWidth, paint, forceEllipsis);
Siyamed Sinir0745c722016-05-31 20:39:33 -0700937 }
938 }
939
Siyamed Sinirfb0b2dc2017-07-26 22:08:24 -0700940 final boolean lastLine;
941 if (mEllipsized) {
942 lastLine = true;
943 } else {
944 final boolean lastCharIsNewLine = widthStart != bufEnd && bufEnd > 0
945 && text.charAt(bufEnd - 1) == CHAR_NEW_LINE;
946 if (end == bufEnd && !lastCharIsNewLine) {
947 lastLine = true;
948 } else if (start == bufEnd && lastCharIsNewLine) {
949 lastLine = true;
950 } else {
951 lastLine = false;
952 }
953 }
Raph Leviend97b0972014-04-24 12:51:35 -0700954
955 if (firstLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800956 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800957 mTopPadding = top - above;
958 }
959
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800960 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800961 above = top;
962 }
963 }
Raph Leviend97b0972014-04-24 12:51:35 -0700964
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800965 int extra;
966
Raph Leviend97b0972014-04-24 12:51:35 -0700967 if (lastLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800968 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800969 mBottomPadding = bottom - below;
970 }
971
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800972 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800973 below = bottom;
974 }
975 }
976
Siyamed Sinir442c1512017-07-24 12:18:27 -0700977 if (needMultiply && (addLastLineLineSpacing || !lastLine)) {
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800978 double ex = (below - above) * (spacingmult - 1) + spacingadd;
Doug Felt10657582010-02-22 11:19:01 -0800979 if (ex >= 0) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800980 extra = (int)(ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -0800981 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800982 extra = -(int)(-ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -0800983 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800984 } else {
985 extra = 0;
986 }
987
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800988 lines[off + START] = start;
989 lines[off + TOP] = v;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800990 lines[off + DESCENT] = below + extra;
Siyamed Sinir0fa89d62017-07-24 20:46:41 -0700991 lines[off + EXTRA] = extra;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800992
Siyamed Sinir0745c722016-05-31 20:39:33 -0700993 // special case for non-ellipsized last visible line when maxLines is set
994 // store the height as if it was ellipsized
995 if (!mEllipsized && currentLineIsTheLastVisibleOne) {
996 // below calculation as if it was the last line
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800997 int maxLineBelow = includePad ? bottom : below;
Siyamed Sinir0745c722016-05-31 20:39:33 -0700998 // similar to the calculation of v below, without the extra.
999 mMaxLineHeight = v + (maxLineBelow - above);
1000 }
1001
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001002 v += (below - above) + extra;
1003 lines[off + mColumns + START] = end;
1004 lines[off + mColumns + TOP] = v;
1005
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001006 // TODO: could move TAB to share same column as HYPHEN, simplifying this code and gaining
1007 // one bit for start field
1008 lines[off + TAB] |= flags & TAB_MASK;
1009 lines[off + HYPHEN] = flags;
1010 lines[off + DIR] |= dir << DIR_SHIFT;
1011 mLineDirections[j] = measured.getDirections(start - widthStart, end - widthStart);
1012
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001013 mLineCount++;
1014 return v;
1015 }
1016
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001017 private void calculateEllipsis(int lineStart, int lineEnd,
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001018 MeasuredParagraph measured, int widthStart,
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001019 float avail, TextUtils.TruncateAt where,
1020 int line, float textWidth, TextPaint paint,
1021 boolean forceEllipsis) {
1022 avail -= getTotalInsets(line);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001023 if (textWidth <= avail && !forceEllipsis) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001024 // Everything fits!
1025 mLines[mColumns * line + ELLIPSIS_START] = 0;
1026 mLines[mColumns * line + ELLIPSIS_COUNT] = 0;
1027 return;
1028 }
1029
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001030 float ellipsisWidth = paint.measureText(TextUtils.getEllipsisString(where));
1031 int ellipsisStart = 0;
1032 int ellipsisCount = 0;
1033 int len = lineEnd - lineStart;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001034
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001035 // We only support start ellipsis on a single line
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001036 if (where == TextUtils.TruncateAt.START) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001037 if (mMaximumVisibleLineCount == 1) {
1038 float sum = 0;
1039 int i;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001040
Keisuke Kuroyanagied2eea12015-04-14 18:18:35 +09001041 for (i = len; i > 0; i--) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001042 float w = measured.getCharWidthAt(i - 1 + lineStart - widthStart);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001043 if (w + sum + ellipsisWidth > avail) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001044 while (i < len
1045 && measured.getCharWidthAt(i + lineStart - widthStart) == 0.0f) {
Keisuke Kuroyanagi82d1c442016-09-14 14:30:14 +09001046 i++;
1047 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001048 break;
1049 }
1050
1051 sum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001052 }
1053
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001054 ellipsisStart = 0;
1055 ellipsisCount = i;
1056 } else {
1057 if (Log.isLoggable(TAG, Log.WARN)) {
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001058 Log.w(TAG, "Start Ellipsis only supported with one line");
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001059 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001060 }
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001061 } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE ||
1062 where == TextUtils.TruncateAt.END_SMALL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001063 float sum = 0;
1064 int i;
1065
1066 for (i = 0; i < len; i++) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001067 float w = measured.getCharWidthAt(i + lineStart - widthStart);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001068
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001069 if (w + sum + ellipsisWidth > avail) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001070 break;
1071 }
1072
1073 sum += w;
1074 }
1075
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001076 ellipsisStart = i;
1077 ellipsisCount = len - i;
1078 if (forceEllipsis && ellipsisCount == 0 && len > 0) {
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -07001079 ellipsisStart = len - 1;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001080 ellipsisCount = 1;
1081 }
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001082 } else {
1083 // where = TextUtils.TruncateAt.MIDDLE We only support middle ellipsis on a single line
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001084 if (mMaximumVisibleLineCount == 1) {
1085 float lsum = 0, rsum = 0;
1086 int left = 0, right = len;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001087
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001088 float ravail = (avail - ellipsisWidth) / 2;
Raph Levien0e3c5e82014-12-04 13:26:07 -08001089 for (right = len; right > 0; right--) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001090 float w = measured.getCharWidthAt(right - 1 + lineStart - widthStart);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001091
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001092 if (w + rsum > ravail) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001093 while (right < len
1094 && measured.getCharWidthAt(right + lineStart - widthStart)
1095 == 0.0f) {
Keisuke Kuroyanagi82d1c442016-09-14 14:30:14 +09001096 right++;
1097 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001098 break;
1099 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001100 rsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001101 }
1102
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001103 float lavail = avail - ellipsisWidth - rsum;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001104 for (left = 0; left < right; left++) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001105 float w = measured.getCharWidthAt(left + lineStart - widthStart);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001106
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001107 if (w + lsum > lavail) {
1108 break;
1109 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001110
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001111 lsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001112 }
1113
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001114 ellipsisStart = left;
1115 ellipsisCount = right - left;
1116 } else {
1117 if (Log.isLoggable(TAG, Log.WARN)) {
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001118 Log.w(TAG, "Middle Ellipsis only supported with one line");
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001119 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001120 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001121 }
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001122 mEllipsized = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001123 mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
1124 mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
1125 }
1126
Selim Cinek365ec092017-03-09 00:10:52 -08001127 private float getTotalInsets(int line) {
1128 int totalIndent = 0;
1129 if (mLeftIndents != null) {
1130 totalIndent = mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1131 }
1132 if (mRightIndents != null) {
1133 totalIndent += mRightIndents[Math.min(line, mRightIndents.length - 1)];
1134 }
1135 return totalIndent;
1136 }
1137
Doug Felte8e45f22010-03-29 14:58:40 -07001138 // Override the base class so we can directly access our members,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001139 // rather than relying on member functions.
1140 // The logic mirrors that of Layout.getLineForVertical
1141 // FIXME: It may be faster to do a linear search for layouts without many lines.
Gilles Debunne66111472010-11-19 11:04:37 -08001142 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001143 public int getLineForVertical(int vertical) {
1144 int high = mLineCount;
1145 int low = -1;
1146 int guess;
1147 int[] lines = mLines;
1148 while (high - low > 1) {
1149 guess = (high + low) >> 1;
1150 if (lines[mColumns * guess + TOP] > vertical){
1151 high = guess;
1152 } else {
1153 low = guess;
1154 }
1155 }
1156 if (low < 0) {
1157 return 0;
1158 } else {
1159 return low;
1160 }
1161 }
1162
Gilles Debunne66111472010-11-19 11:04:37 -08001163 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001164 public int getLineCount() {
1165 return mLineCount;
1166 }
1167
Gilles Debunne66111472010-11-19 11:04:37 -08001168 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001169 public int getLineTop(int line) {
Raph Levien07e6c232016-04-04 12:34:06 -07001170 return mLines[mColumns * line + TOP];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001171 }
1172
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001173 /**
1174 * @hide
1175 */
1176 @Override
1177 public int getLineExtra(int line) {
1178 return mLines[mColumns * line + EXTRA];
1179 }
1180
Gilles Debunne66111472010-11-19 11:04:37 -08001181 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001182 public int getLineDescent(int line) {
Raph Levien07e6c232016-04-04 12:34:06 -07001183 return mLines[mColumns * line + DESCENT];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001184 }
1185
Gilles Debunne66111472010-11-19 11:04:37 -08001186 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001187 public int getLineStart(int line) {
1188 return mLines[mColumns * line + START] & START_MASK;
1189 }
1190
Gilles Debunne66111472010-11-19 11:04:37 -08001191 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001192 public int getParagraphDirection(int line) {
1193 return mLines[mColumns * line + DIR] >> DIR_SHIFT;
1194 }
1195
Gilles Debunne66111472010-11-19 11:04:37 -08001196 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001197 public boolean getLineContainsTab(int line) {
1198 return (mLines[mColumns * line + TAB] & TAB_MASK) != 0;
1199 }
1200
Gilles Debunne66111472010-11-19 11:04:37 -08001201 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001202 public final Directions getLineDirections(int line) {
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001203 if (line > getLineCount()) {
1204 throw new ArrayIndexOutOfBoundsException();
1205 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001206 return mLineDirections[line];
1207 }
1208
Gilles Debunne66111472010-11-19 11:04:37 -08001209 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001210 public int getTopPadding() {
1211 return mTopPadding;
1212 }
1213
Gilles Debunne66111472010-11-19 11:04:37 -08001214 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001215 public int getBottomPadding() {
1216 return mBottomPadding;
1217 }
1218
Raph Levien26d443a2015-03-30 14:18:32 -07001219 /**
Seigo Nonaka9033e0c2018-09-06 18:42:10 -07001220 * Returns the packed hyphen edit value for this line.
1221 *
1222 * You can extract start hyphen edit and end hyphen edit by using
1223 * {@link Hyphenator#unpackStartHyphenEdit(int)} and
1224 * {@link Hyphenator#unpackEndHyphenEdit(int)}.
1225 *
1226 * @param lineNumber a line number
1227 * @return A packed hyphen edit value.
Raph Levien26d443a2015-03-30 14:18:32 -07001228 * @hide
1229 */
1230 @Override
Seigo Nonaka9033e0c2018-09-06 18:42:10 -07001231 public int getHyphen(int lineNumber) {
1232 return mLines[mColumns * lineNumber + HYPHEN] & HYPHEN_MASK;
Raph Levien26d443a2015-03-30 14:18:32 -07001233 }
1234
Raph Levien2ea52902015-07-01 14:39:31 -07001235 /**
1236 * @hide
1237 */
1238 @Override
1239 public int getIndentAdjust(int line, Alignment align) {
1240 if (align == Alignment.ALIGN_LEFT) {
1241 if (mLeftIndents == null) {
1242 return 0;
1243 } else {
1244 return mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1245 }
1246 } else if (align == Alignment.ALIGN_RIGHT) {
1247 if (mRightIndents == null) {
1248 return 0;
1249 } else {
1250 return -mRightIndents[Math.min(line, mRightIndents.length - 1)];
1251 }
1252 } else if (align == Alignment.ALIGN_CENTER) {
1253 int left = 0;
1254 if (mLeftIndents != null) {
1255 left = mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1256 }
1257 int right = 0;
1258 if (mRightIndents != null) {
1259 right = mRightIndents[Math.min(line, mRightIndents.length - 1)];
1260 }
1261 return (left - right) >> 1;
1262 } else {
1263 throw new AssertionError("unhandled alignment " + align);
1264 }
1265 }
1266
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001267 @Override
1268 public int getEllipsisCount(int line) {
1269 if (mColumns < COLUMNS_ELLIPSIZE) {
1270 return 0;
1271 }
1272
1273 return mLines[mColumns * line + ELLIPSIS_COUNT];
1274 }
1275
1276 @Override
1277 public int getEllipsisStart(int line) {
1278 if (mColumns < COLUMNS_ELLIPSIZE) {
1279 return 0;
1280 }
1281
1282 return mLines[mColumns * line + ELLIPSIS_START];
1283 }
1284
1285 @Override
1286 public int getEllipsizedWidth() {
1287 return mEllipsizedWidth;
1288 }
1289
Siyamed Sinir0745c722016-05-31 20:39:33 -07001290 /**
1291 * Return the total height of this layout.
1292 *
1293 * @param cap if true and max lines is set, returns the height of the layout at the max lines.
1294 *
1295 * @hide
1296 */
Mathew Inwood8c854f82018-09-14 12:35:36 +01001297 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
Siyamed Sinir0745c722016-05-31 20:39:33 -07001298 public int getHeight(boolean cap) {
Siyamed Sinir70ffd282018-03-08 17:19:46 -08001299 if (cap && mLineCount > mMaximumVisibleLineCount && mMaxLineHeight == -1
1300 && Log.isLoggable(TAG, Log.WARN)) {
Siyamed Sinir0745c722016-05-31 20:39:33 -07001301 Log.w(TAG, "maxLineHeight should not be -1. "
1302 + " maxLines:" + mMaximumVisibleLineCount
1303 + " lineCount:" + mLineCount);
1304 }
1305
Siyamed Sinir70ffd282018-03-08 17:19:46 -08001306 return cap && mLineCount > mMaximumVisibleLineCount && mMaxLineHeight != -1
1307 ? mMaxLineHeight : super.getHeight();
Siyamed Sinir0745c722016-05-31 20:39:33 -07001308 }
1309
Mathew Inwoodefeab842018-08-14 15:21:30 +01001310 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001311 private int mLineCount;
1312 private int mTopPadding, mBottomPadding;
Mathew Inwoodefeab842018-08-14 15:21:30 +01001313 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001314 private int mColumns;
1315 private int mEllipsizedWidth;
1316
Siyamed Sinir0745c722016-05-31 20:39:33 -07001317 /**
1318 * Keeps track if ellipsize is applied to the text.
1319 */
1320 private boolean mEllipsized;
1321
1322 /**
1323 * If maxLines is set, ellipsize is not set, and the actual line count of text is greater than
1324 * or equal to maxLine, this variable holds the ideal visual height of the maxLine'th line
1325 * starting from the top of the layout. If maxLines is not set its value will be -1.
1326 *
1327 * The value is the same as getLineTop(maxLines) for ellipsized version where structurally no
1328 * more than maxLines is contained.
1329 */
Siyamed Sinira19cd512017-08-03 22:01:56 -07001330 private int mMaxLineHeight = DEFAULT_MAX_LINE_HEIGHT;
Siyamed Sinir0745c722016-05-31 20:39:33 -07001331
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001332 private static final int COLUMNS_NORMAL = 5;
1333 private static final int COLUMNS_ELLIPSIZE = 7;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001334 private static final int START = 0;
1335 private static final int DIR = START;
1336 private static final int TAB = START;
1337 private static final int TOP = 1;
1338 private static final int DESCENT = 2;
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001339 private static final int EXTRA = 3;
1340 private static final int HYPHEN = 4;
Mathew Inwoodefeab842018-08-14 15:21:30 +01001341 @UnsupportedAppUsage
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001342 private static final int ELLIPSIS_START = 5;
1343 private static final int ELLIPSIS_COUNT = 6;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001344
Mathew Inwoodefeab842018-08-14 15:21:30 +01001345 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001346 private int[] mLines;
Mathew Inwoodefeab842018-08-14 15:21:30 +01001347 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001348 private Directions[] mLineDirections;
Mathew Inwoodefeab842018-08-14 15:21:30 +01001349 @UnsupportedAppUsage
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001350 private int mMaximumVisibleLineCount = Integer.MAX_VALUE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001351
1352 private static final int START_MASK = 0x1FFFFFFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001353 private static final int DIR_SHIFT = 30;
1354 private static final int TAB_MASK = 0x20000000;
Keisuke Kuroyanagif5af4a32016-08-31 21:40:53 +09001355 private static final int HYPHEN_MASK = 0xFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001356
Doug Feltc982f602010-05-25 11:51:40 -07001357 private static final int TAB_INCREMENT = 20; // same as Layout, but that's private
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001358
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001359 private static final char CHAR_NEW_LINE = '\n';
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001360
1361 private static final double EXTRA_ROUNDING = 0.5;
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001362
Siyamed Sinira19cd512017-08-03 22:01:56 -07001363 private static final int DEFAULT_MAX_LINE_HEIGHT = -1;
1364
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001365 // Unused, here because of gray list private API accesses.
Deepanshu Gupta70539192014-10-15 15:57:40 -07001366 /*package*/ static class LineBreaks {
Anish Athalyec8f9e622014-07-21 15:26:34 -07001367 private static final int INITIAL_SIZE = 16;
Mathew Inwoodefeab842018-08-14 15:21:30 +01001368 @UnsupportedAppUsage
Anish Athalyec8f9e622014-07-21 15:26:34 -07001369 public int[] breaks = new int[INITIAL_SIZE];
Mathew Inwoodefeab842018-08-14 15:21:30 +01001370 @UnsupportedAppUsage
Anish Athalyec8f9e622014-07-21 15:26:34 -07001371 public float[] widths = new float[INITIAL_SIZE];
Mathew Inwoodefeab842018-08-14 15:21:30 +01001372 @UnsupportedAppUsage
Roozbeh Pournader737dfea2017-08-10 11:32:24 -07001373 public float[] ascents = new float[INITIAL_SIZE];
Mathew Inwoodefeab842018-08-14 15:21:30 +01001374 @UnsupportedAppUsage
Roozbeh Pournader737dfea2017-08-10 11:32:24 -07001375 public float[] descents = new float[INITIAL_SIZE];
Mathew Inwoodefeab842018-08-14 15:21:30 +01001376 @UnsupportedAppUsage
Roozbeh Pournader112d9c72015-08-07 12:44:41 -07001377 public int[] flags = new int[INITIAL_SIZE]; // hasTab
Anish Athalyec8f9e622014-07-21 15:26:34 -07001378 // breaks, widths, and flags should all have the same length
1379 }
1380
Seigo Nonakabafe1972017-08-24 15:30:29 -07001381 @Nullable private int[] mLeftIndents;
1382 @Nullable private int[] mRightIndents;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001383}