blob: dc01178395e261e56896b25cf704261221a13579 [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;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080025import android.text.style.LeadingMarginSpan;
Gilles Debunne66111472010-11-19 11:04:37 -080026import android.text.style.LeadingMarginSpan.LeadingMarginSpan2;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080027import android.text.style.LineHeightSpan;
Doug Feltc982f602010-05-25 11:51:40 -070028import android.text.style.TabStopSpan;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -070029import android.util.Log;
Raph Levien39b4db72015-03-25 13:18:20 -070030import android.util.Pools.SynchronizedPool;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080031
Doug Feltcb3791202011-07-07 11:57:48 -070032import com.android.internal.util.ArrayUtils;
Adam Lesinski776abc22014-03-07 11:30:59 -050033import com.android.internal.util.GrowingArrayUtils;
Doug Feltcb3791202011-07-07 11:57:48 -070034
Anish Athalyec8f9e622014-07-21 15:26:34 -070035import java.util.Arrays;
36
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080037/**
38 * StaticLayout is a Layout for text that will not be edited after it
39 * is laid out. Use {@link DynamicLayout} for text that may change.
40 * <p>This is used by widgets to control text layout. You should not need
41 * to use this class directly unless you are implementing your own widget
42 * or custom display object, or would be tempted to call
Doug Felt4e0c5e52010-03-15 16:56:02 -070043 * {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int,
44 * float, float, android.graphics.Paint)
45 * Canvas.drawText()} directly.</p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080046 */
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -080047public class StaticLayout extends Layout {
Seigo Nonaka6f1868b2017-12-01 16:24:19 -080048 /*
49 * The break iteration is done in native code. The protocol for using the native code is as
50 * follows.
51 *
52 * First, call nInit to setup native line breaker object. Then, for each paragraph, do the
53 * following:
54 *
Seigo Nonaka9d3bd082018-01-11 10:02:12 -080055 * - Create MeasuredParagraph by MeasuredParagraph.buildForStaticLayout which measures in
56 * native.
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -070057 * - Run NativeLineBreaker.computeLineBreaks() to obtain line breaks for the paragraph.
Seigo Nonaka6f1868b2017-12-01 16:24:19 -080058 *
59 * After all paragraphs, call finish() to release expensive buffers.
60 */
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -080061
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -070062 static final String TAG = "StaticLayout";
63
Raph Leviend3ab6922015-03-02 14:30:53 -080064 /**
Roozbeh Pournader22a167c2017-08-21 12:53:44 -070065 * Builder for static layouts. The builder is the preferred pattern for constructing
66 * StaticLayout objects and should be preferred over the constructors, particularly to access
67 * newer features. To build a static layout, first call {@link #obtain} with the required
68 * arguments (text, paint, and width), then call setters for optional parameters, and finally
69 * {@link #build} to build the StaticLayout object. Parameters not explicitly set will get
Raph Levien531c30c2015-04-30 16:29:59 -070070 * default values.
Raph Leviend3ab6922015-03-02 14:30:53 -080071 */
72 public final static class Builder {
Seigo Nonakab90e08f2017-10-20 15:23:51 -070073 private Builder() {}
Raph Levien4c1f12e2015-03-02 16:29:23 -080074
Raph Levien531c30c2015-04-30 16:29:59 -070075 /**
Roozbeh Pournader22a167c2017-08-21 12:53:44 -070076 * Obtain a builder for constructing StaticLayout objects.
Raph Levien531c30c2015-04-30 16:29:59 -070077 *
78 * @param source The text to be laid out, optionally with spans
79 * @param start The index of the start of the text
80 * @param end The index + 1 of the end of the text
81 * @param paint The base paint used for layout
82 * @param width The width in pixels
83 * @return a builder object used for constructing the StaticLayout
84 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -070085 @NonNull
86 public static Builder obtain(@NonNull CharSequence source, @IntRange(from = 0) int start,
87 @IntRange(from = 0) int end, @NonNull TextPaint paint,
88 @IntRange(from = 0) int width) {
Raph Levien39b4db72015-03-25 13:18:20 -070089 Builder b = sPool.acquire();
Raph Leviend3ab6922015-03-02 14:30:53 -080090 if (b == null) {
91 b = new Builder();
92 }
93
94 // set default initial values
Raph Levien39b4db72015-03-25 13:18:20 -070095 b.mText = source;
96 b.mStart = start;
97 b.mEnd = end;
Raph Levienebd66ca2015-04-30 15:27:57 -070098 b.mPaint = paint;
Raph Levien39b4db72015-03-25 13:18:20 -070099 b.mWidth = width;
100 b.mAlignment = Alignment.ALIGN_NORMAL;
Raph Leviend3ab6922015-03-02 14:30:53 -0800101 b.mTextDir = TextDirectionHeuristics.FIRSTSTRONG_LTR;
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700102 b.mSpacingMult = DEFAULT_LINESPACING_MULTIPLIER;
103 b.mSpacingAdd = DEFAULT_LINESPACING_ADDITION;
Raph Leviend3ab6922015-03-02 14:30:53 -0800104 b.mIncludePad = true;
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700105 b.mFallbackLineSpacing = false;
Raph Levien39b4db72015-03-25 13:18:20 -0700106 b.mEllipsizedWidth = width;
Raph Leviend3ab6922015-03-02 14:30:53 -0800107 b.mEllipsize = null;
108 b.mMaxLines = Integer.MAX_VALUE;
Raph Levien3bd60c72015-05-06 14:26:35 -0700109 b.mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700110 b.mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700111 b.mJustificationMode = Layout.JUSTIFICATION_MODE_NONE;
Raph Leviend3ab6922015-03-02 14:30:53 -0800112 return b;
113 }
114
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700115 /**
116 * This method should be called after the layout is finished getting constructed and the
117 * builder needs to be cleaned up and returned to the pool.
118 */
119 private static void recycle(@NonNull Builder b) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800120 b.mPaint = null;
121 b.mText = null;
Raph Levien2ea52902015-07-01 14:39:31 -0700122 b.mLeftIndents = null;
123 b.mRightIndents = null;
Raph Levien39b4db72015-03-25 13:18:20 -0700124 sPool.release(b);
Raph Leviend3ab6922015-03-02 14:30:53 -0800125 }
126
127 // release any expensive state
128 /* package */ void finish() {
Raph Levien22ba7862015-07-29 12:34:13 -0700129 mText = null;
130 mPaint = null;
131 mLeftIndents = null;
132 mRightIndents = null;
Raph Leviend3ab6922015-03-02 14:30:53 -0800133 }
134
135 public Builder setText(CharSequence source) {
136 return setText(source, 0, source.length());
137 }
138
Raph Levien531c30c2015-04-30 16:29:59 -0700139 /**
140 * Set the text. Only useful when re-using the builder, which is done for
141 * the internal implementation of {@link DynamicLayout} but not as part
142 * of normal {@link StaticLayout} usage.
143 *
144 * @param source The text to be laid out, optionally with spans
145 * @param start The index of the start of the text
146 * @param end The index + 1 of the end of the text
147 * @return this builder, useful for chaining
148 *
149 * @hide
150 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700151 @NonNull
152 public Builder setText(@NonNull CharSequence source, int start, int end) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800153 mText = source;
154 mStart = start;
155 mEnd = end;
156 return this;
157 }
158
Raph Levien531c30c2015-04-30 16:29:59 -0700159 /**
160 * Set the paint. Internal for reuse cases only.
161 *
162 * @param paint The base paint used for layout
163 * @return this builder, useful for chaining
164 *
165 * @hide
166 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700167 @NonNull
168 public Builder setPaint(@NonNull TextPaint paint) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800169 mPaint = paint;
170 return this;
171 }
172
Raph Levien531c30c2015-04-30 16:29:59 -0700173 /**
174 * Set the width. Internal for reuse cases only.
175 *
176 * @param width The width in pixels
177 * @return this builder, useful for chaining
178 *
179 * @hide
180 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700181 @NonNull
182 public Builder setWidth(@IntRange(from = 0) int width) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800183 mWidth = width;
184 if (mEllipsize == null) {
185 mEllipsizedWidth = width;
186 }
187 return this;
188 }
189
Raph Levien531c30c2015-04-30 16:29:59 -0700190 /**
191 * Set the alignment. The default is {@link Layout.Alignment#ALIGN_NORMAL}.
192 *
193 * @param alignment Alignment for the resulting {@link StaticLayout}
194 * @return this builder, useful for chaining
195 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700196 @NonNull
197 public Builder setAlignment(@NonNull Alignment alignment) {
Raph Levien39b4db72015-03-25 13:18:20 -0700198 mAlignment = alignment;
199 return this;
200 }
201
Raph Levien531c30c2015-04-30 16:29:59 -0700202 /**
203 * Set the text direction heuristic. The text direction heuristic is used to
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700204 * resolve text direction per-paragraph based on the input text. The default is
Raph Levien531c30c2015-04-30 16:29:59 -0700205 * {@link TextDirectionHeuristics#FIRSTSTRONG_LTR}.
206 *
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700207 * @param textDir text direction heuristic for resolving bidi behavior.
Raph Levien531c30c2015-04-30 16:29:59 -0700208 * @return this builder, useful for chaining
209 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700210 @NonNull
211 public Builder setTextDirection(@NonNull TextDirectionHeuristic textDir) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800212 mTextDir = textDir;
213 return this;
214 }
215
Raph Levien531c30c2015-04-30 16:29:59 -0700216 /**
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700217 * Set line spacing parameters. Each line will have its line spacing multiplied by
218 * {@code spacingMult} and then increased by {@code spacingAdd}. The default is 0.0 for
219 * {@code spacingAdd} and 1.0 for {@code spacingMult}.
Raph Levien531c30c2015-04-30 16:29:59 -0700220 *
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700221 * @param spacingAdd the amount of line spacing addition
222 * @param spacingMult the line spacing multiplier
Raph Levien531c30c2015-04-30 16:29:59 -0700223 * @return this builder, useful for chaining
224 * @see android.widget.TextView#setLineSpacing
225 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700226 @NonNull
227 public Builder setLineSpacing(float spacingAdd, @FloatRange(from = 0.0) float spacingMult) {
Raph Levien531c30c2015-04-30 16:29:59 -0700228 mSpacingAdd = spacingAdd;
Raph Leviend3ab6922015-03-02 14:30:53 -0800229 mSpacingMult = spacingMult;
230 return this;
231 }
232
Raph Levien531c30c2015-04-30 16:29:59 -0700233 /**
234 * Set whether to include extra space beyond font ascent and descent (which is
235 * needed to avoid clipping in some languages, such as Arabic and Kannada). The
236 * default is {@code true}.
237 *
238 * @param includePad whether to include padding
239 * @return this builder, useful for chaining
240 * @see android.widget.TextView#setIncludeFontPadding
241 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700242 @NonNull
Raph Leviend3ab6922015-03-02 14:30:53 -0800243 public Builder setIncludePad(boolean includePad) {
244 mIncludePad = includePad;
245 return this;
246 }
247
Raph Levien531c30c2015-04-30 16:29:59 -0700248 /**
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700249 * Set whether to respect the ascent and descent of the fallback fonts that are used in
250 * displaying the text (which is needed to avoid text from consecutive lines running into
251 * each other). If set, fallback fonts that end up getting used can increase the ascent
252 * and descent of the lines that they are used on.
253 *
254 * <p>For backward compatibility reasons, the default is {@code false}, but setting this to
255 * true is strongly recommended. It is required to be true if text could be in languages
256 * like Burmese or Tibetan where text is typically much taller or deeper than Latin text.
257 *
258 * @param useLineSpacingFromFallbacks whether to expand linespacing based on fallback fonts
259 * @return this builder, useful for chaining
260 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700261 @NonNull
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700262 public Builder setUseLineSpacingFromFallbacks(boolean useLineSpacingFromFallbacks) {
263 mFallbackLineSpacing = useLineSpacingFromFallbacks;
264 return this;
265 }
266
267 /**
Raph Levien531c30c2015-04-30 16:29:59 -0700268 * Set the width as used for ellipsizing purposes, if it differs from the
269 * normal layout width. The default is the {@code width}
270 * passed to {@link #obtain}.
271 *
272 * @param ellipsizedWidth width used for ellipsizing, in pixels
273 * @return this builder, useful for chaining
274 * @see android.widget.TextView#setEllipsize
275 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700276 @NonNull
277 public Builder setEllipsizedWidth(@IntRange(from = 0) int ellipsizedWidth) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800278 mEllipsizedWidth = ellipsizedWidth;
279 return this;
280 }
281
Raph Levien531c30c2015-04-30 16:29:59 -0700282 /**
283 * Set ellipsizing on the layout. Causes words that are longer than the view
284 * is wide, or exceeding the number of lines (see #setMaxLines) in the case
285 * of {@link android.text.TextUtils.TruncateAt#END} or
286 * {@link android.text.TextUtils.TruncateAt#MARQUEE}, to be ellipsized instead
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700287 * of broken. The default is {@code null}, indicating no ellipsis is to be applied.
Raph Levien531c30c2015-04-30 16:29:59 -0700288 *
289 * @param ellipsize type of ellipsis behavior
290 * @return this builder, useful for chaining
291 * @see android.widget.TextView#setEllipsize
292 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700293 @NonNull
Raph Levien531c30c2015-04-30 16:29:59 -0700294 public Builder setEllipsize(@Nullable TextUtils.TruncateAt ellipsize) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800295 mEllipsize = ellipsize;
296 return this;
297 }
298
Raph Levien531c30c2015-04-30 16:29:59 -0700299 /**
300 * Set maximum number of lines. This is particularly useful in the case of
301 * ellipsizing, where it changes the layout of the last line. The default is
302 * unlimited.
303 *
304 * @param maxLines maximum number of lines in the layout
305 * @return this builder, useful for chaining
306 * @see android.widget.TextView#setMaxLines
307 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700308 @NonNull
309 public Builder setMaxLines(@IntRange(from = 0) int maxLines) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800310 mMaxLines = maxLines;
311 return this;
312 }
313
Raph Levien531c30c2015-04-30 16:29:59 -0700314 /**
315 * Set break strategy, useful for selecting high quality or balanced paragraph
316 * layout options. The default is {@link Layout#BREAK_STRATEGY_SIMPLE}.
Siyamed Sinirc0dab362018-07-22 13:48:51 -0700317 * <p/>
318 * Enabling hyphenation with either using {@link Layout#HYPHENATION_FREQUENCY_NORMAL} or
319 * {@link Layout#HYPHENATION_FREQUENCY_FULL} while line breaking is set to one of
320 * {@link Layout#BREAK_STRATEGY_BALANCED}, {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}
321 * improves the structure of text layout however has performance impact and requires more
322 * time to do the text layout.
Raph Levien531c30c2015-04-30 16:29:59 -0700323 *
324 * @param breakStrategy break strategy for paragraph layout
325 * @return this builder, useful for chaining
326 * @see android.widget.TextView#setBreakStrategy
Siyamed Sinirc0dab362018-07-22 13:48:51 -0700327 * @see #setHyphenationFrequency(int)
Raph Levien531c30c2015-04-30 16:29:59 -0700328 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700329 @NonNull
Raph Levien39b4db72015-03-25 13:18:20 -0700330 public Builder setBreakStrategy(@BreakStrategy int breakStrategy) {
331 mBreakStrategy = breakStrategy;
332 return this;
333 }
334
Raph Levien531c30c2015-04-30 16:29:59 -0700335 /**
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700336 * Set hyphenation frequency, to control the amount of automatic hyphenation used. The
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700337 * possible values are defined in {@link Layout}, by constants named with the pattern
338 * {@code HYPHENATION_FREQUENCY_*}. The default is
339 * {@link Layout#HYPHENATION_FREQUENCY_NONE}.
Siyamed Sinirc0dab362018-07-22 13:48:51 -0700340 * <p/>
341 * Enabling hyphenation with either using {@link Layout#HYPHENATION_FREQUENCY_NORMAL} or
342 * {@link Layout#HYPHENATION_FREQUENCY_FULL} while line breaking is set to one of
343 * {@link Layout#BREAK_STRATEGY_BALANCED}, {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}
344 * improves the structure of text layout however has performance impact and requires more
345 * time to do the text layout.
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700346 *
347 * @param hyphenationFrequency hyphenation frequency for the paragraph
348 * @return this builder, useful for chaining
349 * @see android.widget.TextView#setHyphenationFrequency
Siyamed Sinirc0dab362018-07-22 13:48:51 -0700350 * @see #setBreakStrategy(int)
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700351 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700352 @NonNull
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700353 public Builder setHyphenationFrequency(@HyphenationFrequency int hyphenationFrequency) {
354 mHyphenationFrequency = hyphenationFrequency;
355 return this;
356 }
357
358 /**
Raph Levien531c30c2015-04-30 16:29:59 -0700359 * Set indents. Arguments are arrays holding an indent amount, one per line, measured in
360 * pixels. For lines past the last element in the array, the last element repeats.
361 *
362 * @param leftIndents array of indent values for left margin, in pixels
363 * @param rightIndents array of indent values for right margin, in pixels
364 * @return this builder, useful for chaining
Raph Levien531c30c2015-04-30 16:29:59 -0700365 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700366 @NonNull
367 public Builder setIndents(@Nullable int[] leftIndents, @Nullable int[] rightIndents) {
Raph Levien2ea52902015-07-01 14:39:31 -0700368 mLeftIndents = leftIndents;
369 mRightIndents = rightIndents;
Raph Leviene319d5a2015-04-14 23:51:07 -0700370 return this;
371 }
372
Raph Levien70616ec2015-03-04 10:41:30 -0800373 /**
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700374 * Set paragraph justification mode. The default value is
375 * {@link Layout#JUSTIFICATION_MODE_NONE}. If the last line is too short for justification,
376 * the last line will be displayed with the alignment set by {@link #setAlignment}.
Seigo Nonaka09da71a2016-11-28 16:24:14 +0900377 *
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700378 * @param justificationMode justification mode for the paragraph.
Seigo Nonaka09da71a2016-11-28 16:24:14 +0900379 * @return this builder, useful for chaining.
380 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700381 @NonNull
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700382 public Builder setJustificationMode(@JustificationMode int justificationMode) {
383 mJustificationMode = justificationMode;
Seigo Nonaka09da71a2016-11-28 16:24:14 +0900384 return this;
385 }
386
Siyamed Sinir442c1512017-07-24 12:18:27 -0700387 /**
388 * Sets whether the line spacing should be applied for the last line. Default value is
389 * {@code false}.
390 *
391 * @hide
392 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700393 @NonNull
Siyamed Sinir442c1512017-07-24 12:18:27 -0700394 /* package */ Builder setAddLastLineLineSpacing(boolean value) {
395 mAddLastLineLineSpacing = value;
396 return this;
397 }
398
Seigo Nonaka09da71a2016-11-28 16:24:14 +0900399 /**
Raph Levien531c30c2015-04-30 16:29:59 -0700400 * Build the {@link StaticLayout} after options have been set.
401 *
402 * <p>Note: the builder object must not be reused in any way after calling this
403 * method. Setting parameters after calling this method, or calling it a second
404 * time on the same builder object, will likely lead to unexpected results.
405 *
406 * @return the newly constructed {@link StaticLayout} object
407 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700408 @NonNull
Raph Leviend3ab6922015-03-02 14:30:53 -0800409 public StaticLayout build() {
Raph Levien39b4db72015-03-25 13:18:20 -0700410 StaticLayout result = new StaticLayout(this);
411 Builder.recycle(this);
Raph Leviend3ab6922015-03-02 14:30:53 -0800412 return result;
413 }
414
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700415 private CharSequence mText;
416 private int mStart;
417 private int mEnd;
418 private TextPaint mPaint;
419 private int mWidth;
420 private Alignment mAlignment;
421 private TextDirectionHeuristic mTextDir;
422 private float mSpacingMult;
423 private float mSpacingAdd;
424 private boolean mIncludePad;
425 private boolean mFallbackLineSpacing;
426 private int mEllipsizedWidth;
427 private TextUtils.TruncateAt mEllipsize;
428 private int mMaxLines;
429 private int mBreakStrategy;
430 private int mHyphenationFrequency;
Seigo Nonakabafe1972017-08-24 15:30:29 -0700431 @Nullable private int[] mLeftIndents;
432 @Nullable private int[] mRightIndents;
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700433 private int mJustificationMode;
434 private boolean mAddLastLineLineSpacing;
Raph Leviend3ab6922015-03-02 14:30:53 -0800435
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700436 private final Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
Raph Leviend3ab6922015-03-02 14:30:53 -0800437
Siyamed Sinira273a702017-10-05 11:22:12 -0700438 private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<>(3);
Raph Leviend3ab6922015-03-02 14:30:53 -0800439 }
440
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000441 /**
442 * @deprecated Use {@link Builder} instead.
443 */
444 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800445 public StaticLayout(CharSequence source, TextPaint paint,
446 int width,
447 Alignment align, float spacingmult, float spacingadd,
448 boolean includepad) {
449 this(source, 0, source.length(), paint, width, align,
450 spacingmult, spacingadd, includepad);
451 }
452
Doug Feltcb3791202011-07-07 11:57:48 -0700453 /**
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000454 * @deprecated Use {@link Builder} instead.
Doug Feltcb3791202011-07-07 11:57:48 -0700455 */
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000456 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800457 public StaticLayout(CharSequence source, int bufstart, int bufend,
458 TextPaint paint, int outerwidth,
459 Alignment align,
460 float spacingmult, float spacingadd,
461 boolean includepad) {
462 this(source, bufstart, bufend, paint, outerwidth, align,
463 spacingmult, spacingadd, includepad, null, 0);
464 }
465
Doug Feltcb3791202011-07-07 11:57:48 -0700466 /**
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000467 * @deprecated Use {@link Builder} instead.
Doug Feltcb3791202011-07-07 11:57:48 -0700468 */
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000469 @Deprecated
Doug Feltcb3791202011-07-07 11:57:48 -0700470 public StaticLayout(CharSequence source, int bufstart, int bufend,
471 TextPaint paint, int outerwidth,
472 Alignment align,
473 float spacingmult, float spacingadd,
474 boolean includepad,
475 TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
Seigo Nonaka4e90fa22018-02-13 21:40:01 +0000476 this(source, bufstart, bufend, paint, outerwidth, align,
Doug Feltcb3791202011-07-07 11:57:48 -0700477 TextDirectionHeuristics.FIRSTSTRONG_LTR,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700478 spacingmult, spacingadd, includepad, ellipsize, ellipsizedWidth, Integer.MAX_VALUE);
Doug Feltcb3791202011-07-07 11:57:48 -0700479 }
480
481 /**
482 * @hide
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000483 * @deprecated Use {@link Builder} instead.
Doug Feltcb3791202011-07-07 11:57:48 -0700484 */
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000485 @Deprecated
Mathew Inwoodefeab842018-08-14 15:21:30 +0100486 @UnsupportedAppUsage
Seigo Nonaka4e90fa22018-02-13 21:40:01 +0000487 public StaticLayout(CharSequence source, int bufstart, int bufend,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800488 TextPaint paint, int outerwidth,
Doug Feltcb3791202011-07-07 11:57:48 -0700489 Alignment align, TextDirectionHeuristic textDir,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800490 float spacingmult, float spacingadd,
491 boolean includepad,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700492 TextUtils.TruncateAt ellipsize, int ellipsizedWidth, int maxLines) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800493 super((ellipsize == null)
Doug Felt4e0c5e52010-03-15 16:56:02 -0700494 ? source
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800495 : (source instanceof Spanned)
496 ? new SpannedEllipsizer(source)
497 : new Ellipsizer(source),
Doug Feltcb3791202011-07-07 11:57:48 -0700498 paint, outerwidth, align, textDir, spacingmult, spacingadd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800499
Raph Levienebd66ca2015-04-30 15:27:57 -0700500 Builder b = Builder.obtain(source, bufstart, bufend, paint, outerwidth)
Raph Levien39b4db72015-03-25 13:18:20 -0700501 .setAlignment(align)
Raph Leviena6a08282015-06-03 13:20:45 -0700502 .setTextDirection(textDir)
Raph Levien531c30c2015-04-30 16:29:59 -0700503 .setLineSpacing(spacingadd, spacingmult)
Raph Leviend3ab6922015-03-02 14:30:53 -0800504 .setIncludePad(includepad)
505 .setEllipsizedWidth(ellipsizedWidth)
506 .setEllipsize(ellipsize)
507 .setMaxLines(maxLines);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800508 /*
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700509 * This is annoying, but we can't refer to the layout until superclass construction is
510 * finished, and the superclass constructor wants the reference to the display text.
Doug Felt4e0c5e52010-03-15 16:56:02 -0700511 *
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700512 * In other words, the two Ellipsizer classes in Layout.java need a (Dynamic|Static)Layout
513 * as a parameter to do their calculations, but the Ellipsizers also need to be the input
514 * to the superclass's constructor (Layout). In order to go around the circular
515 * dependency, we construct the Ellipsizer with only one of the parameters, the text. And
516 * we fill in the rest of the needed information (layout, width, and method) later, here.
517 *
518 * This will break if the superclass constructor ever actually cares about the content
519 * instead of just holding the reference.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800520 */
521 if (ellipsize != null) {
522 Ellipsizer e = (Ellipsizer) getText();
523
524 e.mLayout = this;
525 e.mWidth = ellipsizedWidth;
526 e.mMethod = ellipsize;
527 mEllipsizedWidth = ellipsizedWidth;
528
529 mColumns = COLUMNS_ELLIPSIZE;
530 } else {
531 mColumns = COLUMNS_NORMAL;
532 mEllipsizedWidth = outerwidth;
533 }
534
Siyamed Siniraf398512017-07-25 19:08:42 -0700535 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2);
536 mLines = ArrayUtils.newUnpaddedIntArray(2 * mColumns);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700537 mMaximumVisibleLineCount = maxLines;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800538
Raph Levien70616ec2015-03-04 10:41:30 -0800539 generate(b, b.mIncludePad, b.mIncludePad);
Doug Felte8e45f22010-03-29 14:58:40 -0700540
Raph Leviend3ab6922015-03-02 14:30:53 -0800541 Builder.recycle(b);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800542 }
543
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000544 /**
545 * Used by DynamicLayout.
546 */
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700547 /* package */ StaticLayout(@Nullable CharSequence text) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700548 super(text, null, 0, null, 0, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800549
550 mColumns = COLUMNS_ELLIPSIZE;
Siyamed Siniraf398512017-07-25 19:08:42 -0700551 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2);
552 mLines = ArrayUtils.newUnpaddedIntArray(2 * mColumns);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800553 }
554
Raph Levien39b4db72015-03-25 13:18:20 -0700555 private StaticLayout(Builder b) {
556 super((b.mEllipsize == null)
557 ? b.mText
558 : (b.mText instanceof Spanned)
559 ? new SpannedEllipsizer(b.mText)
560 : new Ellipsizer(b.mText),
Roozbeh Pournader158dfaf2017-09-21 13:23:50 -0700561 b.mPaint, b.mWidth, b.mAlignment, b.mTextDir, b.mSpacingMult, b.mSpacingAdd);
Raph Levien39b4db72015-03-25 13:18:20 -0700562
563 if (b.mEllipsize != null) {
564 Ellipsizer e = (Ellipsizer) getText();
565
566 e.mLayout = this;
567 e.mWidth = b.mEllipsizedWidth;
568 e.mMethod = b.mEllipsize;
569 mEllipsizedWidth = b.mEllipsizedWidth;
570
571 mColumns = COLUMNS_ELLIPSIZE;
572 } else {
573 mColumns = COLUMNS_NORMAL;
574 mEllipsizedWidth = b.mWidth;
575 }
576
Siyamed Siniraf398512017-07-25 19:08:42 -0700577 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2);
578 mLines = ArrayUtils.newUnpaddedIntArray(2 * mColumns);
Raph Levien39b4db72015-03-25 13:18:20 -0700579 mMaximumVisibleLineCount = b.mMaxLines;
580
Raph Levien2ea52902015-07-01 14:39:31 -0700581 mLeftIndents = b.mLeftIndents;
582 mRightIndents = b.mRightIndents;
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700583 setJustificationMode(b.mJustificationMode);
Raph Levien2ea52902015-07-01 14:39:31 -0700584
Raph Levien39b4db72015-03-25 13:18:20 -0700585 generate(b, b.mIncludePad, b.mIncludePad);
586 }
587
Raph Leviend3ab6922015-03-02 14:30:53 -0800588 /* package */ void generate(Builder b, boolean includepad, boolean trackpad) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700589 final CharSequence source = b.mText;
Seigo Nonakafbe63bd2017-12-02 19:28:05 -0800590 final int bufStart = b.mStart;
591 final int bufEnd = b.mEnd;
Raph Leviend3ab6922015-03-02 14:30:53 -0800592 TextPaint paint = b.mPaint;
593 int outerWidth = b.mWidth;
594 TextDirectionHeuristic textDir = b.mTextDir;
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700595 final boolean fallbackLineSpacing = b.mFallbackLineSpacing;
Raph Leviend3ab6922015-03-02 14:30:53 -0800596 float spacingmult = b.mSpacingMult;
597 float spacingadd = b.mSpacingAdd;
598 float ellipsizedWidth = b.mEllipsizedWidth;
599 TextUtils.TruncateAt ellipsize = b.mEllipsize;
Siyamed Sinir442c1512017-07-24 12:18:27 -0700600 final boolean addLastLineSpacing = b.mAddLastLineLineSpacing;
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700601 NativeLineBreaker.LineBreaks lineBreaks = new NativeLineBreaker.LineBreaks();
Anish Athalye88b5b0b2014-06-24 14:39:43 -0700602
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800603 mLineCount = 0;
Siyamed Sinira19cd512017-08-03 22:01:56 -0700604 mEllipsized = false;
Siyamed Sinira8d982d2017-08-21 13:27:47 -0700605 mMaxLineHeight = mMaximumVisibleLineCount < 1 ? 0 : DEFAULT_MAX_LINE_HEIGHT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800606
607 int v = 0;
608 boolean needMultiply = (spacingmult != 1 || spacingadd != 0);
609
Raph Leviend3ab6922015-03-02 14:30:53 -0800610 Paint.FontMetricsInt fm = b.mFontMetricsInt;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800611 int[] chooseHtv = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800612
Seigo Nonakabafe1972017-08-24 15:30:29 -0700613 final int[] indents;
614 if (mLeftIndents != null || mRightIndents != null) {
615 final int leftLen = mLeftIndents == null ? 0 : mLeftIndents.length;
616 final int rightLen = mRightIndents == null ? 0 : mRightIndents.length;
617 final int indentsLen = Math.max(leftLen, rightLen);
618 indents = new int[indentsLen];
619 for (int i = 0; i < leftLen; i++) {
620 indents[i] = mLeftIndents[i];
621 }
622 for (int i = 0; i < rightLen; i++) {
623 indents[i] += mRightIndents[i];
624 }
625 } else {
626 indents = null;
627 }
628
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700629 final NativeLineBreaker lineBreaker = new NativeLineBreaker(
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700630 b.mBreakStrategy, b.mHyphenationFrequency,
631 // TODO: Support more justification mode, e.g. letter spacing, stretching.
632 b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE,
Seigo Nonaka9155fa62018-06-05 18:22:03 -0700633 indents);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800634
Seigo Nonakac3328d62018-03-20 15:18:59 -0700635 PrecomputedText.ParagraphInfo[] paragraphInfo = null;
636 final Spanned spanned = (source instanceof Spanned) ? (Spanned) source : null;
Seigo Nonakabeafa1f2018-02-01 21:39:24 -0800637 if (source instanceof PrecomputedText) {
Seigo Nonakac3328d62018-03-20 15:18:59 -0700638 PrecomputedText precomputed = (PrecomputedText) source;
639 if (precomputed.canUseMeasuredResult(bufStart, bufEnd, textDir, paint,
640 b.mBreakStrategy, b.mHyphenationFrequency)) {
Seigo Nonakabeafa1f2018-02-01 21:39:24 -0800641 // Some parameters are different from the ones when measured text is created.
Seigo Nonakac3328d62018-03-20 15:18:59 -0700642 paragraphInfo = precomputed.getParagraphInfo();
Seigo Nonakafbe63bd2017-12-02 19:28:05 -0800643 }
Seigo Nonaka87b15472018-01-12 14:06:29 -0800644 }
645
Seigo Nonakac3328d62018-03-20 15:18:59 -0700646 if (paragraphInfo == null) {
Seigo Nonakabeafa1f2018-02-01 21:39:24 -0800647 final PrecomputedText.Params param = new PrecomputedText.Params(paint, textDir,
648 b.mBreakStrategy, b.mHyphenationFrequency);
Seigo Nonakac3328d62018-03-20 15:18:59 -0700649 paragraphInfo = PrecomputedText.createMeasuredParagraphs(source, param, bufStart,
650 bufEnd, false /* computeLayout */);
Seigo Nonaka4e90fa22018-02-13 21:40:01 +0000651 }
Seigo Nonakafbe63bd2017-12-02 19:28:05 -0800652
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700653 for (int paraIndex = 0; paraIndex < paragraphInfo.length; paraIndex++) {
654 final int paraStart = paraIndex == 0
655 ? bufStart : paragraphInfo[paraIndex - 1].paragraphEnd;
656 final int paraEnd = paragraphInfo[paraIndex].paragraphEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800657
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700658 int firstWidthLineCount = 1;
659 int firstWidth = outerWidth;
660 int restWidth = outerWidth;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800661
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700662 LineHeightSpan[] chooseHt = null;
663 if (spanned != null) {
664 LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
665 LeadingMarginSpan.class);
666 for (int i = 0; i < sp.length; i++) {
667 LeadingMarginSpan lms = sp[i];
668 firstWidth -= sp[i].getLeadingMargin(true);
669 restWidth -= sp[i].getLeadingMargin(false);
Doug Feltcb3791202011-07-07 11:57:48 -0700670
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700671 // LeadingMarginSpan2 is odd. The count affects all
672 // leading margin spans, not just this particular one
673 if (lms instanceof LeadingMarginSpan2) {
674 LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
675 firstWidthLineCount = Math.max(firstWidthLineCount,
676 lms2.getLeadingMarginLineCount());
677 }
678 }
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700679
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700680 chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
681
682 if (chooseHt.length == 0) {
683 chooseHt = null; // So that out() would not assume it has any contents
684 } else {
685 if (chooseHtv == null || chooseHtv.length < chooseHt.length) {
686 chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700687 }
688
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700689 for (int i = 0; i < chooseHt.length; i++) {
690 int o = spanned.getSpanStart(chooseHt[i]);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700691
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700692 if (o < paraStart) {
693 // starts in this layout, before the
694 // current paragraph
695
696 chooseHtv[i] = getLineTop(getLineForOffset(o));
697 } else {
698 // starts in this paragraph
699
700 chooseHtv[i] = v;
701 }
702 }
703 }
704 }
705 // tab stop locations
706 int[] variableTabStops = null;
707 if (spanned != null) {
708 TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
709 paraEnd, TabStopSpan.class);
710 if (spans.length > 0) {
711 int[] stops = new int[spans.length];
712 for (int i = 0; i < spans.length; i++) {
713 stops[i] = spans[i].getTabStop();
714 }
715 Arrays.sort(stops, 0, stops.length);
716 variableTabStops = stops;
717 }
718 }
719
720 final MeasuredParagraph measuredPara = paragraphInfo[paraIndex].measured;
721 final char[] chs = measuredPara.getChars();
722 final int[] spanEndCache = measuredPara.getSpanEndCache().getRawArray();
723 final int[] fmCache = measuredPara.getFontMetrics().getRawArray();
724 int breakCount = lineBreaker.computeLineBreaks(
725 measuredPara.getChars(),
726 measuredPara.getNativeMeasuredParagraph(),
727 paraEnd - paraStart,
728 firstWidth,
729 firstWidthLineCount,
730 restWidth,
731 variableTabStops,
732 TAB_INCREMENT,
733 mLineCount,
734 lineBreaks);
735
736 final int[] breaks = lineBreaks.breaks;
737 final float[] lineWidths = lineBreaks.widths;
738 final float[] ascents = lineBreaks.ascents;
739 final float[] descents = lineBreaks.descents;
740 final int[] flags = lineBreaks.flags;
741
742 final int remainingLineCount = mMaximumVisibleLineCount - mLineCount;
743 final boolean ellipsisMayBeApplied = ellipsize != null
744 && (ellipsize == TextUtils.TruncateAt.END
745 || (mMaximumVisibleLineCount == 1
746 && ellipsize != TextUtils.TruncateAt.MARQUEE));
747 if (0 < remainingLineCount && remainingLineCount < breakCount
748 && ellipsisMayBeApplied) {
749 // Calculate width
750 float width = 0;
751 int flag = 0; // XXX May need to also have starting hyphen edit
752 for (int i = remainingLineCount - 1; i < breakCount; i++) {
753 if (i == breakCount - 1) {
754 width += lineWidths[i];
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700755 } else {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700756 for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) {
757 width += measuredPara.getCharWidthAt(j - paraStart);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700758 }
Mark Wagner7b5676e2009-10-16 11:44:23 -0700759 }
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700760 flag |= flags[i] & TAB_MASK;
761 }
762 // Treat the last line and overflowed lines as a single line.
763 breaks[remainingLineCount - 1] = breaks[breakCount - 1];
764 lineWidths[remainingLineCount - 1] = width;
765 flags[remainingLineCount - 1] = flag;
766
767 breakCount = remainingLineCount;
768 }
769
770 // here is the offset of the starting character of the line we are currently
771 // measuring
772 int here = paraStart;
773
774 int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
775 int fmCacheIndex = 0;
776 int spanEndCacheIndex = 0;
777 int breakIndex = 0;
778 for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
779 // retrieve end of span
780 spanEnd = spanEndCache[spanEndCacheIndex++];
781
782 // retrieve cached metrics, order matches above
783 fm.top = fmCache[fmCacheIndex * 4 + 0];
784 fm.bottom = fmCache[fmCacheIndex * 4 + 1];
785 fm.ascent = fmCache[fmCacheIndex * 4 + 2];
786 fm.descent = fmCache[fmCacheIndex * 4 + 3];
787 fmCacheIndex++;
788
789 if (fm.top < fmTop) {
790 fmTop = fm.top;
791 }
792 if (fm.ascent < fmAscent) {
793 fmAscent = fm.ascent;
794 }
795 if (fm.descent > fmDescent) {
796 fmDescent = fm.descent;
797 }
798 if (fm.bottom > fmBottom) {
799 fmBottom = fm.bottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800800 }
801
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700802 // skip breaks ending before current span range
803 while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
804 breakIndex++;
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700805 }
806
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700807 while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
808 int endPos = paraStart + breaks[breakIndex];
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800809
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700810 boolean moreChars = (endPos < bufEnd);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700811
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700812 final int ascent = fallbackLineSpacing
813 ? Math.min(fmAscent, Math.round(ascents[breakIndex]))
814 : fmAscent;
815 final int descent = fallbackLineSpacing
816 ? Math.max(fmDescent, Math.round(descents[breakIndex]))
817 : fmDescent;
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700818
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700819 v = out(source, here, endPos,
820 ascent, descent, fmTop, fmBottom,
821 v, spacingmult, spacingadd, chooseHt, chooseHtv, fm,
822 flags[breakIndex], needMultiply, measuredPara, bufEnd,
823 includepad, trackpad, addLastLineSpacing, chs,
824 paraStart, ellipsize, ellipsizedWidth, lineWidths[breakIndex],
825 paint, moreChars);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700826
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700827 if (endPos < spanEnd) {
828 // preserve metrics for current span
Anish Athalyec8f9e622014-07-21 15:26:34 -0700829 fmTop = fm.top;
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700830 fmBottom = fm.bottom;
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700831 fmAscent = fm.ascent;
832 fmDescent = fm.descent;
833 } else {
834 fmTop = fmBottom = fmAscent = fmDescent = 0;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700835 }
836
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700837 here = endPos;
838 breakIndex++;
839
840 if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) {
841 return;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700842 }
843 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800844 }
845
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700846 if (paraEnd == bufEnd) {
847 break;
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700848 }
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700849 }
850
851 if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE)
852 && mLineCount < mMaximumVisibleLineCount) {
853 final MeasuredParagraph measuredPara =
854 MeasuredParagraph.buildForBidi(source, bufEnd, bufEnd, textDir, null);
855 paint.getFontMetricsInt(fm);
856 v = out(source,
857 bufEnd, bufEnd, fm.ascent, fm.descent,
858 fm.top, fm.bottom,
859 v,
860 spacingmult, spacingadd, null,
861 null, fm, 0,
862 needMultiply, measuredPara, bufEnd,
863 includepad, trackpad, addLastLineSpacing, null,
864 bufStart, ellipsize,
865 ellipsizedWidth, 0, paint, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800866 }
867 }
868
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700869 private int out(final CharSequence text, final int start, final int end, int above, int below,
870 int top, int bottom, int v, final float spacingmult, final float spacingadd,
871 final LineHeightSpan[] chooseHt, final int[] chooseHtv, final Paint.FontMetricsInt fm,
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800872 final int flags, final boolean needMultiply, @NonNull final MeasuredParagraph measured,
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800873 final int bufEnd, final boolean includePad, final boolean trackPad,
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700874 final boolean addLastLineLineSpacing, final char[] chs,
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700875 final int widthStart, final TextUtils.TruncateAt ellipsize, final float ellipsisWidth,
876 final float textWidth, final TextPaint paint, final boolean moreChars) {
Siyamed Siniraf398512017-07-25 19:08:42 -0700877 final int j = mLineCount;
878 final int off = j * mColumns;
879 final int want = off + mColumns + TOP;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800880 int[] lines = mLines;
Seigo Nonaka4b6bcee2017-12-11 11:39:51 -0800881 final int dir = measured.getParagraphDir();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800882
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800883 if (want >= lines.length) {
Siyamed Siniraf398512017-07-25 19:08:42 -0700884 final int[] grow = ArrayUtils.newUnpaddedIntArray(GrowingArrayUtils.growSize(want));
Adam Lesinski776abc22014-03-07 11:30:59 -0500885 System.arraycopy(lines, 0, grow, 0, lines.length);
886 mLines = grow;
887 lines = grow;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800888 }
889
Siyamed Siniraf398512017-07-25 19:08:42 -0700890 if (j >= mLineDirections.length) {
891 final Directions[] grow = ArrayUtils.newUnpaddedArray(Directions.class,
892 GrowingArrayUtils.growSize(j));
893 System.arraycopy(mLineDirections, 0, grow, 0, mLineDirections.length);
894 mLineDirections = grow;
895 }
896
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800897 if (chooseHt != null) {
898 fm.ascent = above;
899 fm.descent = below;
900 fm.top = top;
901 fm.bottom = bottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800902
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800903 for (int i = 0; i < chooseHt.length; i++) {
904 if (chooseHt[i] instanceof LineHeightSpan.WithDensity) {
905 ((LineHeightSpan.WithDensity) chooseHt[i])
906 .chooseHeight(text, start, end, chooseHtv[i], v, fm, paint);
907 } else {
908 chooseHt[i].chooseHeight(text, start, end, chooseHtv[i], v, fm);
909 }
910 }
Eric Fischera9f1dd02009-08-12 15:00:10 -0700911
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800912 above = fm.ascent;
913 below = fm.descent;
914 top = fm.top;
915 bottom = fm.bottom;
916 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800917
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800918 boolean firstLine = (j == 0);
919 boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);
Siyamed Sinir0745c722016-05-31 20:39:33 -0700920
921 if (ellipsize != null) {
922 // If there is only one line, then do any type of ellipsis except when it is MARQUEE
923 // if there are multiple lines, just allow END ellipsis on the last line
924 boolean forceEllipsis = moreChars && (mLineCount + 1 == mMaximumVisibleLineCount);
925
926 boolean doEllipsis =
927 (((mMaximumVisibleLineCount == 1 && moreChars) || (firstLine && !moreChars)) &&
928 ellipsize != TextUtils.TruncateAt.MARQUEE) ||
929 (!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
930 ellipsize == TextUtils.TruncateAt.END);
931 if (doEllipsis) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700932 calculateEllipsis(start, end, measured, widthStart,
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800933 ellipsisWidth, ellipsize, j,
934 textWidth, paint, forceEllipsis);
Siyamed Sinir0745c722016-05-31 20:39:33 -0700935 }
936 }
937
Siyamed Sinirfb0b2dc2017-07-26 22:08:24 -0700938 final boolean lastLine;
939 if (mEllipsized) {
940 lastLine = true;
941 } else {
942 final boolean lastCharIsNewLine = widthStart != bufEnd && bufEnd > 0
943 && text.charAt(bufEnd - 1) == CHAR_NEW_LINE;
944 if (end == bufEnd && !lastCharIsNewLine) {
945 lastLine = true;
946 } else if (start == bufEnd && lastCharIsNewLine) {
947 lastLine = true;
948 } else {
949 lastLine = false;
950 }
951 }
Raph Leviend97b0972014-04-24 12:51:35 -0700952
953 if (firstLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800954 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800955 mTopPadding = top - above;
956 }
957
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800958 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800959 above = top;
960 }
961 }
Raph Leviend97b0972014-04-24 12:51:35 -0700962
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800963 int extra;
964
Raph Leviend97b0972014-04-24 12:51:35 -0700965 if (lastLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800966 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800967 mBottomPadding = bottom - below;
968 }
969
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800970 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800971 below = bottom;
972 }
973 }
974
Siyamed Sinir442c1512017-07-24 12:18:27 -0700975 if (needMultiply && (addLastLineLineSpacing || !lastLine)) {
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800976 double ex = (below - above) * (spacingmult - 1) + spacingadd;
Doug Felt10657582010-02-22 11:19:01 -0800977 if (ex >= 0) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800978 extra = (int)(ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -0800979 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800980 extra = -(int)(-ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -0800981 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800982 } else {
983 extra = 0;
984 }
985
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800986 lines[off + START] = start;
987 lines[off + TOP] = v;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800988 lines[off + DESCENT] = below + extra;
Siyamed Sinir0fa89d62017-07-24 20:46:41 -0700989 lines[off + EXTRA] = extra;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800990
Siyamed Sinir0745c722016-05-31 20:39:33 -0700991 // special case for non-ellipsized last visible line when maxLines is set
992 // store the height as if it was ellipsized
993 if (!mEllipsized && currentLineIsTheLastVisibleOne) {
994 // below calculation as if it was the last line
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800995 int maxLineBelow = includePad ? bottom : below;
Siyamed Sinir0745c722016-05-31 20:39:33 -0700996 // similar to the calculation of v below, without the extra.
997 mMaxLineHeight = v + (maxLineBelow - above);
998 }
999
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001000 v += (below - above) + extra;
1001 lines[off + mColumns + START] = end;
1002 lines[off + mColumns + TOP] = v;
1003
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001004 // TODO: could move TAB to share same column as HYPHEN, simplifying this code and gaining
1005 // one bit for start field
1006 lines[off + TAB] |= flags & TAB_MASK;
1007 lines[off + HYPHEN] = flags;
1008 lines[off + DIR] |= dir << DIR_SHIFT;
1009 mLineDirections[j] = measured.getDirections(start - widthStart, end - widthStart);
1010
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001011 mLineCount++;
1012 return v;
1013 }
1014
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001015 private void calculateEllipsis(int lineStart, int lineEnd,
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001016 MeasuredParagraph measured, int widthStart,
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001017 float avail, TextUtils.TruncateAt where,
1018 int line, float textWidth, TextPaint paint,
1019 boolean forceEllipsis) {
1020 avail -= getTotalInsets(line);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001021 if (textWidth <= avail && !forceEllipsis) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001022 // Everything fits!
1023 mLines[mColumns * line + ELLIPSIS_START] = 0;
1024 mLines[mColumns * line + ELLIPSIS_COUNT] = 0;
1025 return;
1026 }
1027
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001028 float ellipsisWidth = paint.measureText(TextUtils.getEllipsisString(where));
1029 int ellipsisStart = 0;
1030 int ellipsisCount = 0;
1031 int len = lineEnd - lineStart;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001032
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001033 // We only support start ellipsis on a single line
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001034 if (where == TextUtils.TruncateAt.START) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001035 if (mMaximumVisibleLineCount == 1) {
1036 float sum = 0;
1037 int i;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001038
Keisuke Kuroyanagied2eea12015-04-14 18:18:35 +09001039 for (i = len; i > 0; i--) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001040 float w = measured.getCharWidthAt(i - 1 + lineStart - widthStart);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001041 if (w + sum + ellipsisWidth > avail) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001042 while (i < len
1043 && measured.getCharWidthAt(i + lineStart - widthStart) == 0.0f) {
Keisuke Kuroyanagi82d1c442016-09-14 14:30:14 +09001044 i++;
1045 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001046 break;
1047 }
1048
1049 sum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001050 }
1051
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001052 ellipsisStart = 0;
1053 ellipsisCount = i;
1054 } else {
1055 if (Log.isLoggable(TAG, Log.WARN)) {
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001056 Log.w(TAG, "Start Ellipsis only supported with one line");
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001057 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001058 }
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001059 } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE ||
1060 where == TextUtils.TruncateAt.END_SMALL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001061 float sum = 0;
1062 int i;
1063
1064 for (i = 0; i < len; i++) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001065 float w = measured.getCharWidthAt(i + lineStart - widthStart);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001066
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001067 if (w + sum + ellipsisWidth > avail) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001068 break;
1069 }
1070
1071 sum += w;
1072 }
1073
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001074 ellipsisStart = i;
1075 ellipsisCount = len - i;
1076 if (forceEllipsis && ellipsisCount == 0 && len > 0) {
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -07001077 ellipsisStart = len - 1;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001078 ellipsisCount = 1;
1079 }
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001080 } else {
1081 // where = TextUtils.TruncateAt.MIDDLE We only support middle ellipsis on a single line
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001082 if (mMaximumVisibleLineCount == 1) {
1083 float lsum = 0, rsum = 0;
1084 int left = 0, right = len;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001085
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001086 float ravail = (avail - ellipsisWidth) / 2;
Raph Levien0e3c5e82014-12-04 13:26:07 -08001087 for (right = len; right > 0; right--) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001088 float w = measured.getCharWidthAt(right - 1 + lineStart - widthStart);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001089
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001090 if (w + rsum > ravail) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001091 while (right < len
1092 && measured.getCharWidthAt(right + lineStart - widthStart)
1093 == 0.0f) {
Keisuke Kuroyanagi82d1c442016-09-14 14:30:14 +09001094 right++;
1095 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001096 break;
1097 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001098 rsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001099 }
1100
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001101 float lavail = avail - ellipsisWidth - rsum;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001102 for (left = 0; left < right; left++) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001103 float w = measured.getCharWidthAt(left + lineStart - widthStart);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001104
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001105 if (w + lsum > lavail) {
1106 break;
1107 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001108
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001109 lsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001110 }
1111
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001112 ellipsisStart = left;
1113 ellipsisCount = right - left;
1114 } else {
1115 if (Log.isLoggable(TAG, Log.WARN)) {
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001116 Log.w(TAG, "Middle Ellipsis only supported with one line");
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001117 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001118 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001119 }
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001120 mEllipsized = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001121 mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
1122 mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
1123 }
1124
Selim Cinek365ec092017-03-09 00:10:52 -08001125 private float getTotalInsets(int line) {
1126 int totalIndent = 0;
1127 if (mLeftIndents != null) {
1128 totalIndent = mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1129 }
1130 if (mRightIndents != null) {
1131 totalIndent += mRightIndents[Math.min(line, mRightIndents.length - 1)];
1132 }
1133 return totalIndent;
1134 }
1135
Doug Felte8e45f22010-03-29 14:58:40 -07001136 // Override the base class so we can directly access our members,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001137 // rather than relying on member functions.
1138 // The logic mirrors that of Layout.getLineForVertical
1139 // FIXME: It may be faster to do a linear search for layouts without many lines.
Gilles Debunne66111472010-11-19 11:04:37 -08001140 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001141 public int getLineForVertical(int vertical) {
1142 int high = mLineCount;
1143 int low = -1;
1144 int guess;
1145 int[] lines = mLines;
1146 while (high - low > 1) {
1147 guess = (high + low) >> 1;
1148 if (lines[mColumns * guess + TOP] > vertical){
1149 high = guess;
1150 } else {
1151 low = guess;
1152 }
1153 }
1154 if (low < 0) {
1155 return 0;
1156 } else {
1157 return low;
1158 }
1159 }
1160
Gilles Debunne66111472010-11-19 11:04:37 -08001161 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001162 public int getLineCount() {
1163 return mLineCount;
1164 }
1165
Gilles Debunne66111472010-11-19 11:04:37 -08001166 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001167 public int getLineTop(int line) {
Raph Levien07e6c232016-04-04 12:34:06 -07001168 return mLines[mColumns * line + TOP];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001169 }
1170
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001171 /**
1172 * @hide
1173 */
1174 @Override
1175 public int getLineExtra(int line) {
1176 return mLines[mColumns * line + EXTRA];
1177 }
1178
Gilles Debunne66111472010-11-19 11:04:37 -08001179 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001180 public int getLineDescent(int line) {
Raph Levien07e6c232016-04-04 12:34:06 -07001181 return mLines[mColumns * line + DESCENT];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001182 }
1183
Gilles Debunne66111472010-11-19 11:04:37 -08001184 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001185 public int getLineStart(int line) {
1186 return mLines[mColumns * line + START] & START_MASK;
1187 }
1188
Gilles Debunne66111472010-11-19 11:04:37 -08001189 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001190 public int getParagraphDirection(int line) {
1191 return mLines[mColumns * line + DIR] >> DIR_SHIFT;
1192 }
1193
Gilles Debunne66111472010-11-19 11:04:37 -08001194 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001195 public boolean getLineContainsTab(int line) {
1196 return (mLines[mColumns * line + TAB] & TAB_MASK) != 0;
1197 }
1198
Gilles Debunne66111472010-11-19 11:04:37 -08001199 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001200 public final Directions getLineDirections(int line) {
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001201 if (line > getLineCount()) {
1202 throw new ArrayIndexOutOfBoundsException();
1203 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001204 return mLineDirections[line];
1205 }
1206
Gilles Debunne66111472010-11-19 11:04:37 -08001207 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001208 public int getTopPadding() {
1209 return mTopPadding;
1210 }
1211
Gilles Debunne66111472010-11-19 11:04:37 -08001212 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001213 public int getBottomPadding() {
1214 return mBottomPadding;
1215 }
1216
Raph Levien26d443a2015-03-30 14:18:32 -07001217 /**
1218 * @hide
1219 */
1220 @Override
1221 public int getHyphen(int line) {
Keisuke Kuroyanagif5af4a32016-08-31 21:40:53 +09001222 return mLines[mColumns * line + HYPHEN] & HYPHEN_MASK;
Raph Levien26d443a2015-03-30 14:18:32 -07001223 }
1224
Raph Levien2ea52902015-07-01 14:39:31 -07001225 /**
1226 * @hide
1227 */
1228 @Override
1229 public int getIndentAdjust(int line, Alignment align) {
1230 if (align == Alignment.ALIGN_LEFT) {
1231 if (mLeftIndents == null) {
1232 return 0;
1233 } else {
1234 return mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1235 }
1236 } else if (align == Alignment.ALIGN_RIGHT) {
1237 if (mRightIndents == null) {
1238 return 0;
1239 } else {
1240 return -mRightIndents[Math.min(line, mRightIndents.length - 1)];
1241 }
1242 } else if (align == Alignment.ALIGN_CENTER) {
1243 int left = 0;
1244 if (mLeftIndents != null) {
1245 left = mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1246 }
1247 int right = 0;
1248 if (mRightIndents != null) {
1249 right = mRightIndents[Math.min(line, mRightIndents.length - 1)];
1250 }
1251 return (left - right) >> 1;
1252 } else {
1253 throw new AssertionError("unhandled alignment " + align);
1254 }
1255 }
1256
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001257 @Override
1258 public int getEllipsisCount(int line) {
1259 if (mColumns < COLUMNS_ELLIPSIZE) {
1260 return 0;
1261 }
1262
1263 return mLines[mColumns * line + ELLIPSIS_COUNT];
1264 }
1265
1266 @Override
1267 public int getEllipsisStart(int line) {
1268 if (mColumns < COLUMNS_ELLIPSIZE) {
1269 return 0;
1270 }
1271
1272 return mLines[mColumns * line + ELLIPSIS_START];
1273 }
1274
1275 @Override
1276 public int getEllipsizedWidth() {
1277 return mEllipsizedWidth;
1278 }
1279
Siyamed Sinir0745c722016-05-31 20:39:33 -07001280 /**
1281 * Return the total height of this layout.
1282 *
1283 * @param cap if true and max lines is set, returns the height of the layout at the max lines.
1284 *
1285 * @hide
1286 */
Mathew Inwoodefeab842018-08-14 15:21:30 +01001287 @UnsupportedAppUsage
Siyamed Sinir0745c722016-05-31 20:39:33 -07001288 public int getHeight(boolean cap) {
Siyamed Sinir70ffd282018-03-08 17:19:46 -08001289 if (cap && mLineCount > mMaximumVisibleLineCount && mMaxLineHeight == -1
1290 && Log.isLoggable(TAG, Log.WARN)) {
Siyamed Sinir0745c722016-05-31 20:39:33 -07001291 Log.w(TAG, "maxLineHeight should not be -1. "
1292 + " maxLines:" + mMaximumVisibleLineCount
1293 + " lineCount:" + mLineCount);
1294 }
1295
Siyamed Sinir70ffd282018-03-08 17:19:46 -08001296 return cap && mLineCount > mMaximumVisibleLineCount && mMaxLineHeight != -1
1297 ? mMaxLineHeight : super.getHeight();
Siyamed Sinir0745c722016-05-31 20:39:33 -07001298 }
1299
Mathew Inwoodefeab842018-08-14 15:21:30 +01001300 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001301 private int mLineCount;
1302 private int mTopPadding, mBottomPadding;
Mathew Inwoodefeab842018-08-14 15:21:30 +01001303 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001304 private int mColumns;
1305 private int mEllipsizedWidth;
1306
Siyamed Sinir0745c722016-05-31 20:39:33 -07001307 /**
1308 * Keeps track if ellipsize is applied to the text.
1309 */
1310 private boolean mEllipsized;
1311
1312 /**
1313 * If maxLines is set, ellipsize is not set, and the actual line count of text is greater than
1314 * or equal to maxLine, this variable holds the ideal visual height of the maxLine'th line
1315 * starting from the top of the layout. If maxLines is not set its value will be -1.
1316 *
1317 * The value is the same as getLineTop(maxLines) for ellipsized version where structurally no
1318 * more than maxLines is contained.
1319 */
Siyamed Sinira19cd512017-08-03 22:01:56 -07001320 private int mMaxLineHeight = DEFAULT_MAX_LINE_HEIGHT;
Siyamed Sinir0745c722016-05-31 20:39:33 -07001321
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001322 private static final int COLUMNS_NORMAL = 5;
1323 private static final int COLUMNS_ELLIPSIZE = 7;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001324 private static final int START = 0;
1325 private static final int DIR = START;
1326 private static final int TAB = START;
1327 private static final int TOP = 1;
1328 private static final int DESCENT = 2;
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001329 private static final int EXTRA = 3;
1330 private static final int HYPHEN = 4;
Mathew Inwoodefeab842018-08-14 15:21:30 +01001331 @UnsupportedAppUsage
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001332 private static final int ELLIPSIS_START = 5;
1333 private static final int ELLIPSIS_COUNT = 6;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001334
Mathew Inwoodefeab842018-08-14 15:21:30 +01001335 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001336 private int[] mLines;
Mathew Inwoodefeab842018-08-14 15:21:30 +01001337 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001338 private Directions[] mLineDirections;
Mathew Inwoodefeab842018-08-14 15:21:30 +01001339 @UnsupportedAppUsage
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001340 private int mMaximumVisibleLineCount = Integer.MAX_VALUE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001341
1342 private static final int START_MASK = 0x1FFFFFFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001343 private static final int DIR_SHIFT = 30;
1344 private static final int TAB_MASK = 0x20000000;
Keisuke Kuroyanagif5af4a32016-08-31 21:40:53 +09001345 private static final int HYPHEN_MASK = 0xFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001346
Doug Feltc982f602010-05-25 11:51:40 -07001347 private static final int TAB_INCREMENT = 20; // same as Layout, but that's private
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001348
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001349 private static final char CHAR_NEW_LINE = '\n';
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001350
1351 private static final double EXTRA_ROUNDING = 0.5;
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001352
Siyamed Sinira19cd512017-08-03 22:01:56 -07001353 private static final int DEFAULT_MAX_LINE_HEIGHT = -1;
1354
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001355 // Unused, here because of gray list private API accesses.
Deepanshu Gupta70539192014-10-15 15:57:40 -07001356 /*package*/ static class LineBreaks {
Anish Athalyec8f9e622014-07-21 15:26:34 -07001357 private static final int INITIAL_SIZE = 16;
Mathew Inwoodefeab842018-08-14 15:21:30 +01001358 @UnsupportedAppUsage
Anish Athalyec8f9e622014-07-21 15:26:34 -07001359 public int[] breaks = new int[INITIAL_SIZE];
Mathew Inwoodefeab842018-08-14 15:21:30 +01001360 @UnsupportedAppUsage
Anish Athalyec8f9e622014-07-21 15:26:34 -07001361 public float[] widths = new float[INITIAL_SIZE];
Mathew Inwoodefeab842018-08-14 15:21:30 +01001362 @UnsupportedAppUsage
Roozbeh Pournader737dfea2017-08-10 11:32:24 -07001363 public float[] ascents = new float[INITIAL_SIZE];
Mathew Inwoodefeab842018-08-14 15:21:30 +01001364 @UnsupportedAppUsage
Roozbeh Pournader737dfea2017-08-10 11:32:24 -07001365 public float[] descents = new float[INITIAL_SIZE];
Mathew Inwoodefeab842018-08-14 15:21:30 +01001366 @UnsupportedAppUsage
Roozbeh Pournader112d9c72015-08-07 12:44:41 -07001367 public int[] flags = new int[INITIAL_SIZE]; // hasTab
Anish Athalyec8f9e622014-07-21 15:26:34 -07001368 // breaks, widths, and flags should all have the same length
1369 }
1370
Seigo Nonakabafe1972017-08-24 15:30:29 -07001371 @Nullable private int[] mLeftIndents;
1372 @Nullable private int[] mRightIndents;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001373}