blob: 128f860d48382b757d5454b85f1ae4d33e880892 [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 Nonaka41bb4fa2018-08-07 15:15:42 -0700629 final NativeLineBreaker lineBreaker = new NativeLineBreaker.Builder()
630 .setBreakStrategy(b.mBreakStrategy)
631 .setHyphenationFrequency(b.mHyphenationFrequency)
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700632 // TODO: Support more justification mode, e.g. letter spacing, stretching.
Seigo Nonaka41bb4fa2018-08-07 15:15:42 -0700633 .setJustified(b.mJustificationMode)
634 .setIndents(indents)
635 .build();
636
637 NativeLineBreaker.ParagraphConstraints constraints =
638 new NativeLineBreaker.ParagraphConstraints();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800639
Seigo Nonakac3328d62018-03-20 15:18:59 -0700640 PrecomputedText.ParagraphInfo[] paragraphInfo = null;
641 final Spanned spanned = (source instanceof Spanned) ? (Spanned) source : null;
Seigo Nonakabeafa1f2018-02-01 21:39:24 -0800642 if (source instanceof PrecomputedText) {
Seigo Nonakac3328d62018-03-20 15:18:59 -0700643 PrecomputedText precomputed = (PrecomputedText) source;
644 if (precomputed.canUseMeasuredResult(bufStart, bufEnd, textDir, paint,
645 b.mBreakStrategy, b.mHyphenationFrequency)) {
Seigo Nonakabeafa1f2018-02-01 21:39:24 -0800646 // Some parameters are different from the ones when measured text is created.
Seigo Nonakac3328d62018-03-20 15:18:59 -0700647 paragraphInfo = precomputed.getParagraphInfo();
Seigo Nonakafbe63bd2017-12-02 19:28:05 -0800648 }
Seigo Nonaka87b15472018-01-12 14:06:29 -0800649 }
650
Seigo Nonakac3328d62018-03-20 15:18:59 -0700651 if (paragraphInfo == null) {
Seigo Nonakabeafa1f2018-02-01 21:39:24 -0800652 final PrecomputedText.Params param = new PrecomputedText.Params(paint, textDir,
653 b.mBreakStrategy, b.mHyphenationFrequency);
Seigo Nonakac3328d62018-03-20 15:18:59 -0700654 paragraphInfo = PrecomputedText.createMeasuredParagraphs(source, param, bufStart,
655 bufEnd, false /* computeLayout */);
Seigo Nonaka4e90fa22018-02-13 21:40:01 +0000656 }
Seigo Nonakafbe63bd2017-12-02 19:28:05 -0800657
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700658 for (int paraIndex = 0; paraIndex < paragraphInfo.length; paraIndex++) {
659 final int paraStart = paraIndex == 0
660 ? bufStart : paragraphInfo[paraIndex - 1].paragraphEnd;
661 final int paraEnd = paragraphInfo[paraIndex].paragraphEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800662
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700663 int firstWidthLineCount = 1;
664 int firstWidth = outerWidth;
665 int restWidth = outerWidth;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800666
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700667 LineHeightSpan[] chooseHt = null;
668 if (spanned != null) {
669 LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
670 LeadingMarginSpan.class);
671 for (int i = 0; i < sp.length; i++) {
672 LeadingMarginSpan lms = sp[i];
673 firstWidth -= sp[i].getLeadingMargin(true);
674 restWidth -= sp[i].getLeadingMargin(false);
Doug Feltcb3791202011-07-07 11:57:48 -0700675
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700676 // LeadingMarginSpan2 is odd. The count affects all
677 // leading margin spans, not just this particular one
678 if (lms instanceof LeadingMarginSpan2) {
679 LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
680 firstWidthLineCount = Math.max(firstWidthLineCount,
681 lms2.getLeadingMarginLineCount());
682 }
683 }
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700684
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700685 chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
686
687 if (chooseHt.length == 0) {
688 chooseHt = null; // So that out() would not assume it has any contents
689 } else {
690 if (chooseHtv == null || chooseHtv.length < chooseHt.length) {
691 chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700692 }
693
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700694 for (int i = 0; i < chooseHt.length; i++) {
695 int o = spanned.getSpanStart(chooseHt[i]);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700696
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700697 if (o < paraStart) {
698 // starts in this layout, before the
699 // current paragraph
700
701 chooseHtv[i] = getLineTop(getLineForOffset(o));
702 } else {
703 // starts in this paragraph
704
705 chooseHtv[i] = v;
706 }
707 }
708 }
709 }
710 // tab stop locations
711 int[] variableTabStops = null;
712 if (spanned != null) {
713 TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
714 paraEnd, TabStopSpan.class);
715 if (spans.length > 0) {
716 int[] stops = new int[spans.length];
717 for (int i = 0; i < spans.length; i++) {
718 stops[i] = spans[i].getTabStop();
719 }
720 Arrays.sort(stops, 0, stops.length);
721 variableTabStops = stops;
722 }
723 }
724
725 final MeasuredParagraph measuredPara = paragraphInfo[paraIndex].measured;
726 final char[] chs = measuredPara.getChars();
727 final int[] spanEndCache = measuredPara.getSpanEndCache().getRawArray();
728 final int[] fmCache = measuredPara.getFontMetrics().getRawArray();
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700729
Seigo Nonaka41bb4fa2018-08-07 15:15:42 -0700730 constraints.setWidth(restWidth);
731 constraints.setIndent(firstWidth, firstWidthLineCount);
732 constraints.setTabStops(variableTabStops, TAB_INCREMENT);
733
734 lineBreaker.computeLineBreaks(measuredPara.getNativeMeasuredParagraph(),
735 constraints, mLineCount, lineBreaks);
736 int breakCount = lineBreaks.breakCount;
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700737 final int[] breaks = lineBreaks.breaks;
738 final float[] lineWidths = lineBreaks.widths;
739 final float[] ascents = lineBreaks.ascents;
740 final float[] descents = lineBreaks.descents;
741 final int[] flags = lineBreaks.flags;
742
743 final int remainingLineCount = mMaximumVisibleLineCount - mLineCount;
744 final boolean ellipsisMayBeApplied = ellipsize != null
745 && (ellipsize == TextUtils.TruncateAt.END
746 || (mMaximumVisibleLineCount == 1
747 && ellipsize != TextUtils.TruncateAt.MARQUEE));
748 if (0 < remainingLineCount && remainingLineCount < breakCount
749 && ellipsisMayBeApplied) {
750 // Calculate width
751 float width = 0;
752 int flag = 0; // XXX May need to also have starting hyphen edit
753 for (int i = remainingLineCount - 1; i < breakCount; i++) {
754 if (i == breakCount - 1) {
755 width += lineWidths[i];
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700756 } else {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700757 for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) {
758 width += measuredPara.getCharWidthAt(j - paraStart);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700759 }
Mark Wagner7b5676e2009-10-16 11:44:23 -0700760 }
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700761 flag |= flags[i] & TAB_MASK;
762 }
763 // Treat the last line and overflowed lines as a single line.
764 breaks[remainingLineCount - 1] = breaks[breakCount - 1];
765 lineWidths[remainingLineCount - 1] = width;
766 flags[remainingLineCount - 1] = flag;
767
768 breakCount = remainingLineCount;
769 }
770
771 // here is the offset of the starting character of the line we are currently
772 // measuring
773 int here = paraStart;
774
775 int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
776 int fmCacheIndex = 0;
777 int spanEndCacheIndex = 0;
778 int breakIndex = 0;
779 for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
780 // retrieve end of span
781 spanEnd = spanEndCache[spanEndCacheIndex++];
782
783 // retrieve cached metrics, order matches above
784 fm.top = fmCache[fmCacheIndex * 4 + 0];
785 fm.bottom = fmCache[fmCacheIndex * 4 + 1];
786 fm.ascent = fmCache[fmCacheIndex * 4 + 2];
787 fm.descent = fmCache[fmCacheIndex * 4 + 3];
788 fmCacheIndex++;
789
790 if (fm.top < fmTop) {
791 fmTop = fm.top;
792 }
793 if (fm.ascent < fmAscent) {
794 fmAscent = fm.ascent;
795 }
796 if (fm.descent > fmDescent) {
797 fmDescent = fm.descent;
798 }
799 if (fm.bottom > fmBottom) {
800 fmBottom = fm.bottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800801 }
802
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700803 // skip breaks ending before current span range
804 while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
805 breakIndex++;
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700806 }
807
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700808 while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
809 int endPos = paraStart + breaks[breakIndex];
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800810
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700811 boolean moreChars = (endPos < bufEnd);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700812
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700813 final int ascent = fallbackLineSpacing
814 ? Math.min(fmAscent, Math.round(ascents[breakIndex]))
815 : fmAscent;
816 final int descent = fallbackLineSpacing
817 ? Math.max(fmDescent, Math.round(descents[breakIndex]))
818 : fmDescent;
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700819
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700820 v = out(source, here, endPos,
821 ascent, descent, fmTop, fmBottom,
822 v, spacingmult, spacingadd, chooseHt, chooseHtv, fm,
823 flags[breakIndex], needMultiply, measuredPara, bufEnd,
824 includepad, trackpad, addLastLineSpacing, chs,
825 paraStart, ellipsize, ellipsizedWidth, lineWidths[breakIndex],
826 paint, moreChars);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700827
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700828 if (endPos < spanEnd) {
829 // preserve metrics for current span
Anish Athalyec8f9e622014-07-21 15:26:34 -0700830 fmTop = fm.top;
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700831 fmBottom = fm.bottom;
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700832 fmAscent = fm.ascent;
833 fmDescent = fm.descent;
834 } else {
835 fmTop = fmBottom = fmAscent = fmDescent = 0;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700836 }
837
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700838 here = endPos;
839 breakIndex++;
840
841 if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) {
842 return;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700843 }
844 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800845 }
846
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700847 if (paraEnd == bufEnd) {
848 break;
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700849 }
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700850 }
851
852 if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE)
853 && mLineCount < mMaximumVisibleLineCount) {
854 final MeasuredParagraph measuredPara =
855 MeasuredParagraph.buildForBidi(source, bufEnd, bufEnd, textDir, null);
856 paint.getFontMetricsInt(fm);
857 v = out(source,
858 bufEnd, bufEnd, fm.ascent, fm.descent,
859 fm.top, fm.bottom,
860 v,
861 spacingmult, spacingadd, null,
862 null, fm, 0,
863 needMultiply, measuredPara, bufEnd,
864 includepad, trackpad, addLastLineSpacing, null,
865 bufStart, ellipsize,
866 ellipsizedWidth, 0, paint, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800867 }
868 }
869
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700870 private int out(final CharSequence text, final int start, final int end, int above, int below,
871 int top, int bottom, int v, final float spacingmult, final float spacingadd,
872 final LineHeightSpan[] chooseHt, final int[] chooseHtv, final Paint.FontMetricsInt fm,
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800873 final int flags, final boolean needMultiply, @NonNull final MeasuredParagraph measured,
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800874 final int bufEnd, final boolean includePad, final boolean trackPad,
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700875 final boolean addLastLineLineSpacing, final char[] chs,
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700876 final int widthStart, final TextUtils.TruncateAt ellipsize, final float ellipsisWidth,
877 final float textWidth, final TextPaint paint, final boolean moreChars) {
Siyamed Siniraf398512017-07-25 19:08:42 -0700878 final int j = mLineCount;
879 final int off = j * mColumns;
880 final int want = off + mColumns + TOP;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800881 int[] lines = mLines;
Seigo Nonaka4b6bcee2017-12-11 11:39:51 -0800882 final int dir = measured.getParagraphDir();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800883
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800884 if (want >= lines.length) {
Siyamed Siniraf398512017-07-25 19:08:42 -0700885 final int[] grow = ArrayUtils.newUnpaddedIntArray(GrowingArrayUtils.growSize(want));
Adam Lesinski776abc22014-03-07 11:30:59 -0500886 System.arraycopy(lines, 0, grow, 0, lines.length);
887 mLines = grow;
888 lines = grow;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800889 }
890
Siyamed Siniraf398512017-07-25 19:08:42 -0700891 if (j >= mLineDirections.length) {
892 final Directions[] grow = ArrayUtils.newUnpaddedArray(Directions.class,
893 GrowingArrayUtils.growSize(j));
894 System.arraycopy(mLineDirections, 0, grow, 0, mLineDirections.length);
895 mLineDirections = grow;
896 }
897
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800898 if (chooseHt != null) {
899 fm.ascent = above;
900 fm.descent = below;
901 fm.top = top;
902 fm.bottom = bottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800903
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800904 for (int i = 0; i < chooseHt.length; i++) {
905 if (chooseHt[i] instanceof LineHeightSpan.WithDensity) {
906 ((LineHeightSpan.WithDensity) chooseHt[i])
907 .chooseHeight(text, start, end, chooseHtv[i], v, fm, paint);
908 } else {
909 chooseHt[i].chooseHeight(text, start, end, chooseHtv[i], v, fm);
910 }
911 }
Eric Fischera9f1dd02009-08-12 15:00:10 -0700912
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800913 above = fm.ascent;
914 below = fm.descent;
915 top = fm.top;
916 bottom = fm.bottom;
917 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800918
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800919 boolean firstLine = (j == 0);
920 boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);
Siyamed Sinir0745c722016-05-31 20:39:33 -0700921
922 if (ellipsize != null) {
923 // If there is only one line, then do any type of ellipsis except when it is MARQUEE
924 // if there are multiple lines, just allow END ellipsis on the last line
925 boolean forceEllipsis = moreChars && (mLineCount + 1 == mMaximumVisibleLineCount);
926
927 boolean doEllipsis =
928 (((mMaximumVisibleLineCount == 1 && moreChars) || (firstLine && !moreChars)) &&
929 ellipsize != TextUtils.TruncateAt.MARQUEE) ||
930 (!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
931 ellipsize == TextUtils.TruncateAt.END);
932 if (doEllipsis) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700933 calculateEllipsis(start, end, measured, widthStart,
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800934 ellipsisWidth, ellipsize, j,
935 textWidth, paint, forceEllipsis);
Siyamed Sinir0745c722016-05-31 20:39:33 -0700936 }
937 }
938
Siyamed Sinirfb0b2dc2017-07-26 22:08:24 -0700939 final boolean lastLine;
940 if (mEllipsized) {
941 lastLine = true;
942 } else {
943 final boolean lastCharIsNewLine = widthStart != bufEnd && bufEnd > 0
944 && text.charAt(bufEnd - 1) == CHAR_NEW_LINE;
945 if (end == bufEnd && !lastCharIsNewLine) {
946 lastLine = true;
947 } else if (start == bufEnd && lastCharIsNewLine) {
948 lastLine = true;
949 } else {
950 lastLine = false;
951 }
952 }
Raph Leviend97b0972014-04-24 12:51:35 -0700953
954 if (firstLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800955 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800956 mTopPadding = top - above;
957 }
958
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800959 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800960 above = top;
961 }
962 }
Raph Leviend97b0972014-04-24 12:51:35 -0700963
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800964 int extra;
965
Raph Leviend97b0972014-04-24 12:51:35 -0700966 if (lastLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800967 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800968 mBottomPadding = bottom - below;
969 }
970
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800971 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800972 below = bottom;
973 }
974 }
975
Siyamed Sinir442c1512017-07-24 12:18:27 -0700976 if (needMultiply && (addLastLineLineSpacing || !lastLine)) {
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800977 double ex = (below - above) * (spacingmult - 1) + spacingadd;
Doug Felt10657582010-02-22 11:19:01 -0800978 if (ex >= 0) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800979 extra = (int)(ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -0800980 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800981 extra = -(int)(-ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -0800982 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800983 } else {
984 extra = 0;
985 }
986
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800987 lines[off + START] = start;
988 lines[off + TOP] = v;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800989 lines[off + DESCENT] = below + extra;
Siyamed Sinir0fa89d62017-07-24 20:46:41 -0700990 lines[off + EXTRA] = extra;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800991
Siyamed Sinir0745c722016-05-31 20:39:33 -0700992 // special case for non-ellipsized last visible line when maxLines is set
993 // store the height as if it was ellipsized
994 if (!mEllipsized && currentLineIsTheLastVisibleOne) {
995 // below calculation as if it was the last line
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800996 int maxLineBelow = includePad ? bottom : below;
Siyamed Sinir0745c722016-05-31 20:39:33 -0700997 // similar to the calculation of v below, without the extra.
998 mMaxLineHeight = v + (maxLineBelow - above);
999 }
1000
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001001 v += (below - above) + extra;
1002 lines[off + mColumns + START] = end;
1003 lines[off + mColumns + TOP] = v;
1004
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001005 // TODO: could move TAB to share same column as HYPHEN, simplifying this code and gaining
1006 // one bit for start field
1007 lines[off + TAB] |= flags & TAB_MASK;
1008 lines[off + HYPHEN] = flags;
1009 lines[off + DIR] |= dir << DIR_SHIFT;
1010 mLineDirections[j] = measured.getDirections(start - widthStart, end - widthStart);
1011
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001012 mLineCount++;
1013 return v;
1014 }
1015
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001016 private void calculateEllipsis(int lineStart, int lineEnd,
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001017 MeasuredParagraph measured, int widthStart,
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001018 float avail, TextUtils.TruncateAt where,
1019 int line, float textWidth, TextPaint paint,
1020 boolean forceEllipsis) {
1021 avail -= getTotalInsets(line);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001022 if (textWidth <= avail && !forceEllipsis) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001023 // Everything fits!
1024 mLines[mColumns * line + ELLIPSIS_START] = 0;
1025 mLines[mColumns * line + ELLIPSIS_COUNT] = 0;
1026 return;
1027 }
1028
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001029 float ellipsisWidth = paint.measureText(TextUtils.getEllipsisString(where));
1030 int ellipsisStart = 0;
1031 int ellipsisCount = 0;
1032 int len = lineEnd - lineStart;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001033
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001034 // We only support start ellipsis on a single line
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001035 if (where == TextUtils.TruncateAt.START) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001036 if (mMaximumVisibleLineCount == 1) {
1037 float sum = 0;
1038 int i;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001039
Keisuke Kuroyanagied2eea12015-04-14 18:18:35 +09001040 for (i = len; i > 0; i--) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001041 float w = measured.getCharWidthAt(i - 1 + lineStart - widthStart);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001042 if (w + sum + ellipsisWidth > avail) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001043 while (i < len
1044 && measured.getCharWidthAt(i + lineStart - widthStart) == 0.0f) {
Keisuke Kuroyanagi82d1c442016-09-14 14:30:14 +09001045 i++;
1046 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001047 break;
1048 }
1049
1050 sum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001051 }
1052
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001053 ellipsisStart = 0;
1054 ellipsisCount = i;
1055 } else {
1056 if (Log.isLoggable(TAG, Log.WARN)) {
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001057 Log.w(TAG, "Start Ellipsis only supported with one line");
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001058 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001059 }
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001060 } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE ||
1061 where == TextUtils.TruncateAt.END_SMALL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001062 float sum = 0;
1063 int i;
1064
1065 for (i = 0; i < len; i++) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001066 float w = measured.getCharWidthAt(i + lineStart - widthStart);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001067
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001068 if (w + sum + ellipsisWidth > avail) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001069 break;
1070 }
1071
1072 sum += w;
1073 }
1074
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001075 ellipsisStart = i;
1076 ellipsisCount = len - i;
1077 if (forceEllipsis && ellipsisCount == 0 && len > 0) {
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -07001078 ellipsisStart = len - 1;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001079 ellipsisCount = 1;
1080 }
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001081 } else {
1082 // where = TextUtils.TruncateAt.MIDDLE We only support middle ellipsis on a single line
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001083 if (mMaximumVisibleLineCount == 1) {
1084 float lsum = 0, rsum = 0;
1085 int left = 0, right = len;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001086
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001087 float ravail = (avail - ellipsisWidth) / 2;
Raph Levien0e3c5e82014-12-04 13:26:07 -08001088 for (right = len; right > 0; right--) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001089 float w = measured.getCharWidthAt(right - 1 + lineStart - widthStart);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001090
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001091 if (w + rsum > ravail) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001092 while (right < len
1093 && measured.getCharWidthAt(right + lineStart - widthStart)
1094 == 0.0f) {
Keisuke Kuroyanagi82d1c442016-09-14 14:30:14 +09001095 right++;
1096 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001097 break;
1098 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001099 rsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001100 }
1101
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001102 float lavail = avail - ellipsisWidth - rsum;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001103 for (left = 0; left < right; left++) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001104 float w = measured.getCharWidthAt(left + lineStart - widthStart);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001105
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001106 if (w + lsum > lavail) {
1107 break;
1108 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001109
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001110 lsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001111 }
1112
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001113 ellipsisStart = left;
1114 ellipsisCount = right - left;
1115 } else {
1116 if (Log.isLoggable(TAG, Log.WARN)) {
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001117 Log.w(TAG, "Middle Ellipsis only supported with one line");
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001118 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001119 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001120 }
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001121 mEllipsized = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001122 mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
1123 mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
1124 }
1125
Selim Cinek365ec092017-03-09 00:10:52 -08001126 private float getTotalInsets(int line) {
1127 int totalIndent = 0;
1128 if (mLeftIndents != null) {
1129 totalIndent = mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1130 }
1131 if (mRightIndents != null) {
1132 totalIndent += mRightIndents[Math.min(line, mRightIndents.length - 1)];
1133 }
1134 return totalIndent;
1135 }
1136
Doug Felte8e45f22010-03-29 14:58:40 -07001137 // Override the base class so we can directly access our members,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001138 // rather than relying on member functions.
1139 // The logic mirrors that of Layout.getLineForVertical
1140 // FIXME: It may be faster to do a linear search for layouts without many lines.
Gilles Debunne66111472010-11-19 11:04:37 -08001141 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001142 public int getLineForVertical(int vertical) {
1143 int high = mLineCount;
1144 int low = -1;
1145 int guess;
1146 int[] lines = mLines;
1147 while (high - low > 1) {
1148 guess = (high + low) >> 1;
1149 if (lines[mColumns * guess + TOP] > vertical){
1150 high = guess;
1151 } else {
1152 low = guess;
1153 }
1154 }
1155 if (low < 0) {
1156 return 0;
1157 } else {
1158 return low;
1159 }
1160 }
1161
Gilles Debunne66111472010-11-19 11:04:37 -08001162 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001163 public int getLineCount() {
1164 return mLineCount;
1165 }
1166
Gilles Debunne66111472010-11-19 11:04:37 -08001167 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001168 public int getLineTop(int line) {
Raph Levien07e6c232016-04-04 12:34:06 -07001169 return mLines[mColumns * line + TOP];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001170 }
1171
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001172 /**
1173 * @hide
1174 */
1175 @Override
1176 public int getLineExtra(int line) {
1177 return mLines[mColumns * line + EXTRA];
1178 }
1179
Gilles Debunne66111472010-11-19 11:04:37 -08001180 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001181 public int getLineDescent(int line) {
Raph Levien07e6c232016-04-04 12:34:06 -07001182 return mLines[mColumns * line + DESCENT];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001183 }
1184
Gilles Debunne66111472010-11-19 11:04:37 -08001185 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001186 public int getLineStart(int line) {
1187 return mLines[mColumns * line + START] & START_MASK;
1188 }
1189
Gilles Debunne66111472010-11-19 11:04:37 -08001190 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001191 public int getParagraphDirection(int line) {
1192 return mLines[mColumns * line + DIR] >> DIR_SHIFT;
1193 }
1194
Gilles Debunne66111472010-11-19 11:04:37 -08001195 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001196 public boolean getLineContainsTab(int line) {
1197 return (mLines[mColumns * line + TAB] & TAB_MASK) != 0;
1198 }
1199
Gilles Debunne66111472010-11-19 11:04:37 -08001200 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001201 public final Directions getLineDirections(int line) {
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001202 if (line > getLineCount()) {
1203 throw new ArrayIndexOutOfBoundsException();
1204 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001205 return mLineDirections[line];
1206 }
1207
Gilles Debunne66111472010-11-19 11:04:37 -08001208 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001209 public int getTopPadding() {
1210 return mTopPadding;
1211 }
1212
Gilles Debunne66111472010-11-19 11:04:37 -08001213 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001214 public int getBottomPadding() {
1215 return mBottomPadding;
1216 }
1217
Raph Levien26d443a2015-03-30 14:18:32 -07001218 /**
1219 * @hide
1220 */
1221 @Override
1222 public int getHyphen(int line) {
Keisuke Kuroyanagif5af4a32016-08-31 21:40:53 +09001223 return mLines[mColumns * line + HYPHEN] & HYPHEN_MASK;
Raph Levien26d443a2015-03-30 14:18:32 -07001224 }
1225
Raph Levien2ea52902015-07-01 14:39:31 -07001226 /**
1227 * @hide
1228 */
1229 @Override
1230 public int getIndentAdjust(int line, Alignment align) {
1231 if (align == Alignment.ALIGN_LEFT) {
1232 if (mLeftIndents == null) {
1233 return 0;
1234 } else {
1235 return mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1236 }
1237 } else if (align == Alignment.ALIGN_RIGHT) {
1238 if (mRightIndents == null) {
1239 return 0;
1240 } else {
1241 return -mRightIndents[Math.min(line, mRightIndents.length - 1)];
1242 }
1243 } else if (align == Alignment.ALIGN_CENTER) {
1244 int left = 0;
1245 if (mLeftIndents != null) {
1246 left = mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1247 }
1248 int right = 0;
1249 if (mRightIndents != null) {
1250 right = mRightIndents[Math.min(line, mRightIndents.length - 1)];
1251 }
1252 return (left - right) >> 1;
1253 } else {
1254 throw new AssertionError("unhandled alignment " + align);
1255 }
1256 }
1257
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001258 @Override
1259 public int getEllipsisCount(int line) {
1260 if (mColumns < COLUMNS_ELLIPSIZE) {
1261 return 0;
1262 }
1263
1264 return mLines[mColumns * line + ELLIPSIS_COUNT];
1265 }
1266
1267 @Override
1268 public int getEllipsisStart(int line) {
1269 if (mColumns < COLUMNS_ELLIPSIZE) {
1270 return 0;
1271 }
1272
1273 return mLines[mColumns * line + ELLIPSIS_START];
1274 }
1275
1276 @Override
1277 public int getEllipsizedWidth() {
1278 return mEllipsizedWidth;
1279 }
1280
Siyamed Sinir0745c722016-05-31 20:39:33 -07001281 /**
1282 * Return the total height of this layout.
1283 *
1284 * @param cap if true and max lines is set, returns the height of the layout at the max lines.
1285 *
1286 * @hide
1287 */
Mathew Inwoodefeab842018-08-14 15:21:30 +01001288 @UnsupportedAppUsage
Siyamed Sinir0745c722016-05-31 20:39:33 -07001289 public int getHeight(boolean cap) {
Siyamed Sinir70ffd282018-03-08 17:19:46 -08001290 if (cap && mLineCount > mMaximumVisibleLineCount && mMaxLineHeight == -1
1291 && Log.isLoggable(TAG, Log.WARN)) {
Siyamed Sinir0745c722016-05-31 20:39:33 -07001292 Log.w(TAG, "maxLineHeight should not be -1. "
1293 + " maxLines:" + mMaximumVisibleLineCount
1294 + " lineCount:" + mLineCount);
1295 }
1296
Siyamed Sinir70ffd282018-03-08 17:19:46 -08001297 return cap && mLineCount > mMaximumVisibleLineCount && mMaxLineHeight != -1
1298 ? mMaxLineHeight : super.getHeight();
Siyamed Sinir0745c722016-05-31 20:39:33 -07001299 }
1300
Mathew Inwoodefeab842018-08-14 15:21:30 +01001301 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001302 private int mLineCount;
1303 private int mTopPadding, mBottomPadding;
Mathew Inwoodefeab842018-08-14 15:21:30 +01001304 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001305 private int mColumns;
1306 private int mEllipsizedWidth;
1307
Siyamed Sinir0745c722016-05-31 20:39:33 -07001308 /**
1309 * Keeps track if ellipsize is applied to the text.
1310 */
1311 private boolean mEllipsized;
1312
1313 /**
1314 * If maxLines is set, ellipsize is not set, and the actual line count of text is greater than
1315 * or equal to maxLine, this variable holds the ideal visual height of the maxLine'th line
1316 * starting from the top of the layout. If maxLines is not set its value will be -1.
1317 *
1318 * The value is the same as getLineTop(maxLines) for ellipsized version where structurally no
1319 * more than maxLines is contained.
1320 */
Siyamed Sinira19cd512017-08-03 22:01:56 -07001321 private int mMaxLineHeight = DEFAULT_MAX_LINE_HEIGHT;
Siyamed Sinir0745c722016-05-31 20:39:33 -07001322
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001323 private static final int COLUMNS_NORMAL = 5;
1324 private static final int COLUMNS_ELLIPSIZE = 7;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001325 private static final int START = 0;
1326 private static final int DIR = START;
1327 private static final int TAB = START;
1328 private static final int TOP = 1;
1329 private static final int DESCENT = 2;
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001330 private static final int EXTRA = 3;
1331 private static final int HYPHEN = 4;
Mathew Inwoodefeab842018-08-14 15:21:30 +01001332 @UnsupportedAppUsage
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001333 private static final int ELLIPSIS_START = 5;
1334 private static final int ELLIPSIS_COUNT = 6;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001335
Mathew Inwoodefeab842018-08-14 15:21:30 +01001336 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001337 private int[] mLines;
Mathew Inwoodefeab842018-08-14 15:21:30 +01001338 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001339 private Directions[] mLineDirections;
Mathew Inwoodefeab842018-08-14 15:21:30 +01001340 @UnsupportedAppUsage
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001341 private int mMaximumVisibleLineCount = Integer.MAX_VALUE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001342
1343 private static final int START_MASK = 0x1FFFFFFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001344 private static final int DIR_SHIFT = 30;
1345 private static final int TAB_MASK = 0x20000000;
Keisuke Kuroyanagif5af4a32016-08-31 21:40:53 +09001346 private static final int HYPHEN_MASK = 0xFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001347
Doug Feltc982f602010-05-25 11:51:40 -07001348 private static final int TAB_INCREMENT = 20; // same as Layout, but that's private
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001349
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001350 private static final char CHAR_NEW_LINE = '\n';
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001351
1352 private static final double EXTRA_ROUNDING = 0.5;
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001353
Siyamed Sinira19cd512017-08-03 22:01:56 -07001354 private static final int DEFAULT_MAX_LINE_HEIGHT = -1;
1355
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001356 // Unused, here because of gray list private API accesses.
Deepanshu Gupta70539192014-10-15 15:57:40 -07001357 /*package*/ static class LineBreaks {
Anish Athalyec8f9e622014-07-21 15:26:34 -07001358 private static final int INITIAL_SIZE = 16;
Mathew Inwoodefeab842018-08-14 15:21:30 +01001359 @UnsupportedAppUsage
Anish Athalyec8f9e622014-07-21 15:26:34 -07001360 public int[] breaks = new int[INITIAL_SIZE];
Mathew Inwoodefeab842018-08-14 15:21:30 +01001361 @UnsupportedAppUsage
Anish Athalyec8f9e622014-07-21 15:26:34 -07001362 public float[] widths = new float[INITIAL_SIZE];
Mathew Inwoodefeab842018-08-14 15:21:30 +01001363 @UnsupportedAppUsage
Roozbeh Pournader737dfea2017-08-10 11:32:24 -07001364 public float[] ascents = new float[INITIAL_SIZE];
Mathew Inwoodefeab842018-08-14 15:21:30 +01001365 @UnsupportedAppUsage
Roozbeh Pournader737dfea2017-08-10 11:32:24 -07001366 public float[] descents = new float[INITIAL_SIZE];
Mathew Inwoodefeab842018-08-14 15:21:30 +01001367 @UnsupportedAppUsage
Roozbeh Pournader112d9c72015-08-07 12:44:41 -07001368 public int[] flags = new int[INITIAL_SIZE]; // hasTab
Anish Athalyec8f9e622014-07-21 15:26:34 -07001369 // breaks, widths, and flags should all have the same length
1370 }
1371
Seigo Nonakabafe1972017-08-24 15:30:29 -07001372 @Nullable private int[] mLeftIndents;
1373 @Nullable private int[] mRightIndents;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001374}