blob: 6dad23815bef9fd9c62c96b565e2df92de3291c7 [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;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080023import android.graphics.Paint;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080024import android.text.style.LeadingMarginSpan;
Gilles Debunne66111472010-11-19 11:04:37 -080025import android.text.style.LeadingMarginSpan.LeadingMarginSpan2;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080026import android.text.style.LineHeightSpan;
Doug Feltc982f602010-05-25 11:51:40 -070027import android.text.style.TabStopSpan;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -070028import android.util.Log;
Raph Levien39b4db72015-03-25 13:18:20 -070029import android.util.Pools.SynchronizedPool;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080030
Doug Feltcb3791202011-07-07 11:57:48 -070031import com.android.internal.util.ArrayUtils;
Adam Lesinski776abc22014-03-07 11:30:59 -050032import com.android.internal.util.GrowingArrayUtils;
Doug Feltcb3791202011-07-07 11:57:48 -070033
Anish Athalyec8f9e622014-07-21 15:26:34 -070034import java.util.Arrays;
35
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080036/**
37 * StaticLayout is a Layout for text that will not be edited after it
38 * is laid out. Use {@link DynamicLayout} for text that may change.
39 * <p>This is used by widgets to control text layout. You should not need
40 * to use this class directly unless you are implementing your own widget
41 * or custom display object, or would be tempted to call
Doug Felt4e0c5e52010-03-15 16:56:02 -070042 * {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int,
43 * float, float, android.graphics.Paint)
44 * Canvas.drawText()} directly.</p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080045 */
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -080046public class StaticLayout extends Layout {
Seigo Nonaka6f1868b2017-12-01 16:24:19 -080047 /*
48 * The break iteration is done in native code. The protocol for using the native code is as
49 * follows.
50 *
51 * First, call nInit to setup native line breaker object. Then, for each paragraph, do the
52 * following:
53 *
Seigo Nonaka9d3bd082018-01-11 10:02:12 -080054 * - Create MeasuredParagraph by MeasuredParagraph.buildForStaticLayout which measures in
55 * native.
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -070056 * - Run NativeLineBreaker.computeLineBreaks() to obtain line breaks for the paragraph.
Seigo Nonaka6f1868b2017-12-01 16:24:19 -080057 *
58 * After all paragraphs, call finish() to release expensive buffers.
59 */
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -080060
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -070061 static final String TAG = "StaticLayout";
62
Raph Leviend3ab6922015-03-02 14:30:53 -080063 /**
Roozbeh Pournader22a167c2017-08-21 12:53:44 -070064 * Builder for static layouts. The builder is the preferred pattern for constructing
65 * StaticLayout objects and should be preferred over the constructors, particularly to access
66 * newer features. To build a static layout, first call {@link #obtain} with the required
67 * arguments (text, paint, and width), then call setters for optional parameters, and finally
68 * {@link #build} to build the StaticLayout object. Parameters not explicitly set will get
Raph Levien531c30c2015-04-30 16:29:59 -070069 * default values.
Raph Leviend3ab6922015-03-02 14:30:53 -080070 */
71 public final static class Builder {
Seigo Nonakab90e08f2017-10-20 15:23:51 -070072 private Builder() {}
Raph Levien4c1f12e2015-03-02 16:29:23 -080073
Raph Levien531c30c2015-04-30 16:29:59 -070074 /**
Roozbeh Pournader22a167c2017-08-21 12:53:44 -070075 * Obtain a builder for constructing StaticLayout objects.
Raph Levien531c30c2015-04-30 16:29:59 -070076 *
77 * @param source The text to be laid out, optionally with spans
78 * @param start The index of the start of the text
79 * @param end The index + 1 of the end of the text
80 * @param paint The base paint used for layout
81 * @param width The width in pixels
82 * @return a builder object used for constructing the StaticLayout
83 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -070084 @NonNull
85 public static Builder obtain(@NonNull CharSequence source, @IntRange(from = 0) int start,
86 @IntRange(from = 0) int end, @NonNull TextPaint paint,
87 @IntRange(from = 0) int width) {
Raph Levien39b4db72015-03-25 13:18:20 -070088 Builder b = sPool.acquire();
Raph Leviend3ab6922015-03-02 14:30:53 -080089 if (b == null) {
90 b = new Builder();
91 }
92
93 // set default initial values
Raph Levien39b4db72015-03-25 13:18:20 -070094 b.mText = source;
95 b.mStart = start;
96 b.mEnd = end;
Raph Levienebd66ca2015-04-30 15:27:57 -070097 b.mPaint = paint;
Raph Levien39b4db72015-03-25 13:18:20 -070098 b.mWidth = width;
99 b.mAlignment = Alignment.ALIGN_NORMAL;
Raph Leviend3ab6922015-03-02 14:30:53 -0800100 b.mTextDir = TextDirectionHeuristics.FIRSTSTRONG_LTR;
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700101 b.mSpacingMult = DEFAULT_LINESPACING_MULTIPLIER;
102 b.mSpacingAdd = DEFAULT_LINESPACING_ADDITION;
Raph Leviend3ab6922015-03-02 14:30:53 -0800103 b.mIncludePad = true;
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700104 b.mFallbackLineSpacing = false;
Raph Levien39b4db72015-03-25 13:18:20 -0700105 b.mEllipsizedWidth = width;
Raph Leviend3ab6922015-03-02 14:30:53 -0800106 b.mEllipsize = null;
107 b.mMaxLines = Integer.MAX_VALUE;
Raph Levien3bd60c72015-05-06 14:26:35 -0700108 b.mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700109 b.mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700110 b.mJustificationMode = Layout.JUSTIFICATION_MODE_NONE;
Raph Leviend3ab6922015-03-02 14:30:53 -0800111 return b;
112 }
113
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700114 /**
115 * This method should be called after the layout is finished getting constructed and the
116 * builder needs to be cleaned up and returned to the pool.
117 */
118 private static void recycle(@NonNull Builder b) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800119 b.mPaint = null;
120 b.mText = null;
Raph Levien2ea52902015-07-01 14:39:31 -0700121 b.mLeftIndents = null;
122 b.mRightIndents = null;
Raph Levien39b4db72015-03-25 13:18:20 -0700123 sPool.release(b);
Raph Leviend3ab6922015-03-02 14:30:53 -0800124 }
125
126 // release any expensive state
127 /* package */ void finish() {
Raph Levien22ba7862015-07-29 12:34:13 -0700128 mText = null;
129 mPaint = null;
130 mLeftIndents = null;
131 mRightIndents = null;
Raph Leviend3ab6922015-03-02 14:30:53 -0800132 }
133
134 public Builder setText(CharSequence source) {
135 return setText(source, 0, source.length());
136 }
137
Raph Levien531c30c2015-04-30 16:29:59 -0700138 /**
139 * Set the text. Only useful when re-using the builder, which is done for
140 * the internal implementation of {@link DynamicLayout} but not as part
141 * of normal {@link StaticLayout} usage.
142 *
143 * @param source The text to be laid out, optionally with spans
144 * @param start The index of the start of the text
145 * @param end The index + 1 of the end of the text
146 * @return this builder, useful for chaining
147 *
148 * @hide
149 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700150 @NonNull
151 public Builder setText(@NonNull CharSequence source, int start, int end) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800152 mText = source;
153 mStart = start;
154 mEnd = end;
155 return this;
156 }
157
Raph Levien531c30c2015-04-30 16:29:59 -0700158 /**
159 * Set the paint. Internal for reuse cases only.
160 *
161 * @param paint The base paint used for layout
162 * @return this builder, useful for chaining
163 *
164 * @hide
165 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700166 @NonNull
167 public Builder setPaint(@NonNull TextPaint paint) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800168 mPaint = paint;
169 return this;
170 }
171
Raph Levien531c30c2015-04-30 16:29:59 -0700172 /**
173 * Set the width. Internal for reuse cases only.
174 *
175 * @param width The width in pixels
176 * @return this builder, useful for chaining
177 *
178 * @hide
179 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700180 @NonNull
181 public Builder setWidth(@IntRange(from = 0) int width) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800182 mWidth = width;
183 if (mEllipsize == null) {
184 mEllipsizedWidth = width;
185 }
186 return this;
187 }
188
Raph Levien531c30c2015-04-30 16:29:59 -0700189 /**
190 * Set the alignment. The default is {@link Layout.Alignment#ALIGN_NORMAL}.
191 *
192 * @param alignment Alignment for the resulting {@link StaticLayout}
193 * @return this builder, useful for chaining
194 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700195 @NonNull
196 public Builder setAlignment(@NonNull Alignment alignment) {
Raph Levien39b4db72015-03-25 13:18:20 -0700197 mAlignment = alignment;
198 return this;
199 }
200
Raph Levien531c30c2015-04-30 16:29:59 -0700201 /**
202 * Set the text direction heuristic. The text direction heuristic is used to
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700203 * resolve text direction per-paragraph based on the input text. The default is
Raph Levien531c30c2015-04-30 16:29:59 -0700204 * {@link TextDirectionHeuristics#FIRSTSTRONG_LTR}.
205 *
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700206 * @param textDir text direction heuristic for resolving bidi behavior.
Raph Levien531c30c2015-04-30 16:29:59 -0700207 * @return this builder, useful for chaining
208 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700209 @NonNull
210 public Builder setTextDirection(@NonNull TextDirectionHeuristic textDir) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800211 mTextDir = textDir;
212 return this;
213 }
214
Raph Levien531c30c2015-04-30 16:29:59 -0700215 /**
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700216 * Set line spacing parameters. Each line will have its line spacing multiplied by
217 * {@code spacingMult} and then increased by {@code spacingAdd}. The default is 0.0 for
218 * {@code spacingAdd} and 1.0 for {@code spacingMult}.
Raph Levien531c30c2015-04-30 16:29:59 -0700219 *
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700220 * @param spacingAdd the amount of line spacing addition
221 * @param spacingMult the line spacing multiplier
Raph Levien531c30c2015-04-30 16:29:59 -0700222 * @return this builder, useful for chaining
223 * @see android.widget.TextView#setLineSpacing
224 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700225 @NonNull
226 public Builder setLineSpacing(float spacingAdd, @FloatRange(from = 0.0) float spacingMult) {
Raph Levien531c30c2015-04-30 16:29:59 -0700227 mSpacingAdd = spacingAdd;
Raph Leviend3ab6922015-03-02 14:30:53 -0800228 mSpacingMult = spacingMult;
229 return this;
230 }
231
Raph Levien531c30c2015-04-30 16:29:59 -0700232 /**
233 * Set whether to include extra space beyond font ascent and descent (which is
234 * needed to avoid clipping in some languages, such as Arabic and Kannada). The
235 * default is {@code true}.
236 *
237 * @param includePad whether to include padding
238 * @return this builder, useful for chaining
239 * @see android.widget.TextView#setIncludeFontPadding
240 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700241 @NonNull
Raph Leviend3ab6922015-03-02 14:30:53 -0800242 public Builder setIncludePad(boolean includePad) {
243 mIncludePad = includePad;
244 return this;
245 }
246
Raph Levien531c30c2015-04-30 16:29:59 -0700247 /**
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700248 * Set whether to respect the ascent and descent of the fallback fonts that are used in
249 * displaying the text (which is needed to avoid text from consecutive lines running into
250 * each other). If set, fallback fonts that end up getting used can increase the ascent
251 * and descent of the lines that they are used on.
252 *
253 * <p>For backward compatibility reasons, the default is {@code false}, but setting this to
254 * true is strongly recommended. It is required to be true if text could be in languages
255 * like Burmese or Tibetan where text is typically much taller or deeper than Latin text.
256 *
257 * @param useLineSpacingFromFallbacks whether to expand linespacing based on fallback fonts
258 * @return this builder, useful for chaining
259 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700260 @NonNull
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700261 public Builder setUseLineSpacingFromFallbacks(boolean useLineSpacingFromFallbacks) {
262 mFallbackLineSpacing = useLineSpacingFromFallbacks;
263 return this;
264 }
265
266 /**
Raph Levien531c30c2015-04-30 16:29:59 -0700267 * Set the width as used for ellipsizing purposes, if it differs from the
268 * normal layout width. The default is the {@code width}
269 * passed to {@link #obtain}.
270 *
271 * @param ellipsizedWidth width used for ellipsizing, in pixels
272 * @return this builder, useful for chaining
273 * @see android.widget.TextView#setEllipsize
274 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700275 @NonNull
276 public Builder setEllipsizedWidth(@IntRange(from = 0) int ellipsizedWidth) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800277 mEllipsizedWidth = ellipsizedWidth;
278 return this;
279 }
280
Raph Levien531c30c2015-04-30 16:29:59 -0700281 /**
282 * Set ellipsizing on the layout. Causes words that are longer than the view
283 * is wide, or exceeding the number of lines (see #setMaxLines) in the case
284 * of {@link android.text.TextUtils.TruncateAt#END} or
285 * {@link android.text.TextUtils.TruncateAt#MARQUEE}, to be ellipsized instead
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700286 * of broken. The default is {@code null}, indicating no ellipsis is to be applied.
Raph Levien531c30c2015-04-30 16:29:59 -0700287 *
288 * @param ellipsize type of ellipsis behavior
289 * @return this builder, useful for chaining
290 * @see android.widget.TextView#setEllipsize
291 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700292 @NonNull
Raph Levien531c30c2015-04-30 16:29:59 -0700293 public Builder setEllipsize(@Nullable TextUtils.TruncateAt ellipsize) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800294 mEllipsize = ellipsize;
295 return this;
296 }
297
Raph Levien531c30c2015-04-30 16:29:59 -0700298 /**
299 * Set maximum number of lines. This is particularly useful in the case of
300 * ellipsizing, where it changes the layout of the last line. The default is
301 * unlimited.
302 *
303 * @param maxLines maximum number of lines in the layout
304 * @return this builder, useful for chaining
305 * @see android.widget.TextView#setMaxLines
306 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700307 @NonNull
308 public Builder setMaxLines(@IntRange(from = 0) int maxLines) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800309 mMaxLines = maxLines;
310 return this;
311 }
312
Raph Levien531c30c2015-04-30 16:29:59 -0700313 /**
314 * Set break strategy, useful for selecting high quality or balanced paragraph
315 * layout options. The default is {@link Layout#BREAK_STRATEGY_SIMPLE}.
316 *
317 * @param breakStrategy break strategy for paragraph layout
318 * @return this builder, useful for chaining
319 * @see android.widget.TextView#setBreakStrategy
320 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700321 @NonNull
Raph Levien39b4db72015-03-25 13:18:20 -0700322 public Builder setBreakStrategy(@BreakStrategy int breakStrategy) {
323 mBreakStrategy = breakStrategy;
324 return this;
325 }
326
Raph Levien531c30c2015-04-30 16:29:59 -0700327 /**
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700328 * Set hyphenation frequency, to control the amount of automatic hyphenation used. The
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700329 * possible values are defined in {@link Layout}, by constants named with the pattern
330 * {@code HYPHENATION_FREQUENCY_*}. The default is
331 * {@link Layout#HYPHENATION_FREQUENCY_NONE}.
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700332 *
333 * @param hyphenationFrequency hyphenation frequency for the paragraph
334 * @return this builder, useful for chaining
335 * @see android.widget.TextView#setHyphenationFrequency
336 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700337 @NonNull
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700338 public Builder setHyphenationFrequency(@HyphenationFrequency int hyphenationFrequency) {
339 mHyphenationFrequency = hyphenationFrequency;
340 return this;
341 }
342
343 /**
Raph Levien531c30c2015-04-30 16:29:59 -0700344 * Set indents. Arguments are arrays holding an indent amount, one per line, measured in
345 * pixels. For lines past the last element in the array, the last element repeats.
346 *
347 * @param leftIndents array of indent values for left margin, in pixels
348 * @param rightIndents array of indent values for right margin, in pixels
349 * @return this builder, useful for chaining
Raph Levien531c30c2015-04-30 16:29:59 -0700350 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700351 @NonNull
352 public Builder setIndents(@Nullable int[] leftIndents, @Nullable int[] rightIndents) {
Raph Levien2ea52902015-07-01 14:39:31 -0700353 mLeftIndents = leftIndents;
354 mRightIndents = rightIndents;
Raph Leviene319d5a2015-04-14 23:51:07 -0700355 return this;
356 }
357
Raph Levien70616ec2015-03-04 10:41:30 -0800358 /**
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700359 * Set paragraph justification mode. The default value is
360 * {@link Layout#JUSTIFICATION_MODE_NONE}. If the last line is too short for justification,
361 * the last line will be displayed with the alignment set by {@link #setAlignment}.
Seigo Nonaka09da71a2016-11-28 16:24:14 +0900362 *
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700363 * @param justificationMode justification mode for the paragraph.
Seigo Nonaka09da71a2016-11-28 16:24:14 +0900364 * @return this builder, useful for chaining.
365 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700366 @NonNull
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700367 public Builder setJustificationMode(@JustificationMode int justificationMode) {
368 mJustificationMode = justificationMode;
Seigo Nonaka09da71a2016-11-28 16:24:14 +0900369 return this;
370 }
371
Siyamed Sinir442c1512017-07-24 12:18:27 -0700372 /**
373 * Sets whether the line spacing should be applied for the last line. Default value is
374 * {@code false}.
375 *
376 * @hide
377 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700378 @NonNull
Siyamed Sinir442c1512017-07-24 12:18:27 -0700379 /* package */ Builder setAddLastLineLineSpacing(boolean value) {
380 mAddLastLineLineSpacing = value;
381 return this;
382 }
383
Seigo Nonaka09da71a2016-11-28 16:24:14 +0900384 /**
Raph Levien531c30c2015-04-30 16:29:59 -0700385 * Build the {@link StaticLayout} after options have been set.
386 *
387 * <p>Note: the builder object must not be reused in any way after calling this
388 * method. Setting parameters after calling this method, or calling it a second
389 * time on the same builder object, will likely lead to unexpected results.
390 *
391 * @return the newly constructed {@link StaticLayout} object
392 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700393 @NonNull
Raph Leviend3ab6922015-03-02 14:30:53 -0800394 public StaticLayout build() {
Raph Levien39b4db72015-03-25 13:18:20 -0700395 StaticLayout result = new StaticLayout(this);
396 Builder.recycle(this);
Raph Leviend3ab6922015-03-02 14:30:53 -0800397 return result;
398 }
399
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700400 private CharSequence mText;
401 private int mStart;
402 private int mEnd;
403 private TextPaint mPaint;
404 private int mWidth;
405 private Alignment mAlignment;
406 private TextDirectionHeuristic mTextDir;
407 private float mSpacingMult;
408 private float mSpacingAdd;
409 private boolean mIncludePad;
410 private boolean mFallbackLineSpacing;
411 private int mEllipsizedWidth;
412 private TextUtils.TruncateAt mEllipsize;
413 private int mMaxLines;
414 private int mBreakStrategy;
415 private int mHyphenationFrequency;
Seigo Nonakabafe1972017-08-24 15:30:29 -0700416 @Nullable private int[] mLeftIndents;
417 @Nullable private int[] mRightIndents;
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700418 private int mJustificationMode;
419 private boolean mAddLastLineLineSpacing;
Raph Leviend3ab6922015-03-02 14:30:53 -0800420
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700421 private final Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
Raph Leviend3ab6922015-03-02 14:30:53 -0800422
Siyamed Sinira273a702017-10-05 11:22:12 -0700423 private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<>(3);
Raph Leviend3ab6922015-03-02 14:30:53 -0800424 }
425
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000426 /**
427 * @deprecated Use {@link Builder} instead.
428 */
429 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800430 public StaticLayout(CharSequence source, TextPaint paint,
431 int width,
432 Alignment align, float spacingmult, float spacingadd,
433 boolean includepad) {
434 this(source, 0, source.length(), paint, width, align,
435 spacingmult, spacingadd, includepad);
436 }
437
Doug Feltcb3791202011-07-07 11:57:48 -0700438 /**
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000439 * @deprecated Use {@link Builder} instead.
Doug Feltcb3791202011-07-07 11:57:48 -0700440 */
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000441 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800442 public StaticLayout(CharSequence source, int bufstart, int bufend,
443 TextPaint paint, int outerwidth,
444 Alignment align,
445 float spacingmult, float spacingadd,
446 boolean includepad) {
447 this(source, bufstart, bufend, paint, outerwidth, align,
448 spacingmult, spacingadd, includepad, null, 0);
449 }
450
Doug Feltcb3791202011-07-07 11:57:48 -0700451 /**
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000452 * @deprecated Use {@link Builder} instead.
Doug Feltcb3791202011-07-07 11:57:48 -0700453 */
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000454 @Deprecated
Doug Feltcb3791202011-07-07 11:57:48 -0700455 public StaticLayout(CharSequence source, int bufstart, int bufend,
456 TextPaint paint, int outerwidth,
457 Alignment align,
458 float spacingmult, float spacingadd,
459 boolean includepad,
460 TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
Seigo Nonaka4e90fa22018-02-13 21:40:01 +0000461 this(source, bufstart, bufend, paint, outerwidth, align,
Doug Feltcb3791202011-07-07 11:57:48 -0700462 TextDirectionHeuristics.FIRSTSTRONG_LTR,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700463 spacingmult, spacingadd, includepad, ellipsize, ellipsizedWidth, Integer.MAX_VALUE);
Doug Feltcb3791202011-07-07 11:57:48 -0700464 }
465
466 /**
467 * @hide
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000468 * @deprecated Use {@link Builder} instead.
Doug Feltcb3791202011-07-07 11:57:48 -0700469 */
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000470 @Deprecated
Seigo Nonaka4e90fa22018-02-13 21:40:01 +0000471 public StaticLayout(CharSequence source, int bufstart, int bufend,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800472 TextPaint paint, int outerwidth,
Doug Feltcb3791202011-07-07 11:57:48 -0700473 Alignment align, TextDirectionHeuristic textDir,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800474 float spacingmult, float spacingadd,
475 boolean includepad,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700476 TextUtils.TruncateAt ellipsize, int ellipsizedWidth, int maxLines) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800477 super((ellipsize == null)
Doug Felt4e0c5e52010-03-15 16:56:02 -0700478 ? source
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800479 : (source instanceof Spanned)
480 ? new SpannedEllipsizer(source)
481 : new Ellipsizer(source),
Doug Feltcb3791202011-07-07 11:57:48 -0700482 paint, outerwidth, align, textDir, spacingmult, spacingadd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800483
Raph Levienebd66ca2015-04-30 15:27:57 -0700484 Builder b = Builder.obtain(source, bufstart, bufend, paint, outerwidth)
Raph Levien39b4db72015-03-25 13:18:20 -0700485 .setAlignment(align)
Raph Leviena6a08282015-06-03 13:20:45 -0700486 .setTextDirection(textDir)
Raph Levien531c30c2015-04-30 16:29:59 -0700487 .setLineSpacing(spacingadd, spacingmult)
Raph Leviend3ab6922015-03-02 14:30:53 -0800488 .setIncludePad(includepad)
489 .setEllipsizedWidth(ellipsizedWidth)
490 .setEllipsize(ellipsize)
491 .setMaxLines(maxLines);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800492 /*
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700493 * This is annoying, but we can't refer to the layout until superclass construction is
494 * finished, and the superclass constructor wants the reference to the display text.
Doug Felt4e0c5e52010-03-15 16:56:02 -0700495 *
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700496 * In other words, the two Ellipsizer classes in Layout.java need a (Dynamic|Static)Layout
497 * as a parameter to do their calculations, but the Ellipsizers also need to be the input
498 * to the superclass's constructor (Layout). In order to go around the circular
499 * dependency, we construct the Ellipsizer with only one of the parameters, the text. And
500 * we fill in the rest of the needed information (layout, width, and method) later, here.
501 *
502 * This will break if the superclass constructor ever actually cares about the content
503 * instead of just holding the reference.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800504 */
505 if (ellipsize != null) {
506 Ellipsizer e = (Ellipsizer) getText();
507
508 e.mLayout = this;
509 e.mWidth = ellipsizedWidth;
510 e.mMethod = ellipsize;
511 mEllipsizedWidth = ellipsizedWidth;
512
513 mColumns = COLUMNS_ELLIPSIZE;
514 } else {
515 mColumns = COLUMNS_NORMAL;
516 mEllipsizedWidth = outerwidth;
517 }
518
Siyamed Siniraf398512017-07-25 19:08:42 -0700519 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2);
520 mLines = ArrayUtils.newUnpaddedIntArray(2 * mColumns);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700521 mMaximumVisibleLineCount = maxLines;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800522
Raph Levien70616ec2015-03-04 10:41:30 -0800523 generate(b, b.mIncludePad, b.mIncludePad);
Doug Felte8e45f22010-03-29 14:58:40 -0700524
Raph Leviend3ab6922015-03-02 14:30:53 -0800525 Builder.recycle(b);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800526 }
527
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000528 /**
529 * Used by DynamicLayout.
530 */
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700531 /* package */ StaticLayout(@Nullable CharSequence text) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700532 super(text, null, 0, null, 0, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800533
534 mColumns = COLUMNS_ELLIPSIZE;
Siyamed Siniraf398512017-07-25 19:08:42 -0700535 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2);
536 mLines = ArrayUtils.newUnpaddedIntArray(2 * mColumns);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800537 }
538
Raph Levien39b4db72015-03-25 13:18:20 -0700539 private StaticLayout(Builder b) {
540 super((b.mEllipsize == null)
541 ? b.mText
542 : (b.mText instanceof Spanned)
543 ? new SpannedEllipsizer(b.mText)
544 : new Ellipsizer(b.mText),
Roozbeh Pournader158dfaf2017-09-21 13:23:50 -0700545 b.mPaint, b.mWidth, b.mAlignment, b.mTextDir, b.mSpacingMult, b.mSpacingAdd);
Raph Levien39b4db72015-03-25 13:18:20 -0700546
547 if (b.mEllipsize != null) {
548 Ellipsizer e = (Ellipsizer) getText();
549
550 e.mLayout = this;
551 e.mWidth = b.mEllipsizedWidth;
552 e.mMethod = b.mEllipsize;
553 mEllipsizedWidth = b.mEllipsizedWidth;
554
555 mColumns = COLUMNS_ELLIPSIZE;
556 } else {
557 mColumns = COLUMNS_NORMAL;
558 mEllipsizedWidth = b.mWidth;
559 }
560
Siyamed Siniraf398512017-07-25 19:08:42 -0700561 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2);
562 mLines = ArrayUtils.newUnpaddedIntArray(2 * mColumns);
Raph Levien39b4db72015-03-25 13:18:20 -0700563 mMaximumVisibleLineCount = b.mMaxLines;
564
Raph Levien2ea52902015-07-01 14:39:31 -0700565 mLeftIndents = b.mLeftIndents;
566 mRightIndents = b.mRightIndents;
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700567 setJustificationMode(b.mJustificationMode);
Raph Levien2ea52902015-07-01 14:39:31 -0700568
Raph Levien39b4db72015-03-25 13:18:20 -0700569 generate(b, b.mIncludePad, b.mIncludePad);
570 }
571
Raph Leviend3ab6922015-03-02 14:30:53 -0800572 /* package */ void generate(Builder b, boolean includepad, boolean trackpad) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700573 final CharSequence source = b.mText;
Seigo Nonakafbe63bd2017-12-02 19:28:05 -0800574 final int bufStart = b.mStart;
575 final int bufEnd = b.mEnd;
Raph Leviend3ab6922015-03-02 14:30:53 -0800576 TextPaint paint = b.mPaint;
577 int outerWidth = b.mWidth;
578 TextDirectionHeuristic textDir = b.mTextDir;
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700579 final boolean fallbackLineSpacing = b.mFallbackLineSpacing;
Raph Leviend3ab6922015-03-02 14:30:53 -0800580 float spacingmult = b.mSpacingMult;
581 float spacingadd = b.mSpacingAdd;
582 float ellipsizedWidth = b.mEllipsizedWidth;
583 TextUtils.TruncateAt ellipsize = b.mEllipsize;
Siyamed Sinir442c1512017-07-24 12:18:27 -0700584 final boolean addLastLineSpacing = b.mAddLastLineLineSpacing;
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700585 NativeLineBreaker.LineBreaks lineBreaks = new NativeLineBreaker.LineBreaks();
Anish Athalye88b5b0b2014-06-24 14:39:43 -0700586
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800587 mLineCount = 0;
Siyamed Sinira19cd512017-08-03 22:01:56 -0700588 mEllipsized = false;
Siyamed Sinira8d982d2017-08-21 13:27:47 -0700589 mMaxLineHeight = mMaximumVisibleLineCount < 1 ? 0 : DEFAULT_MAX_LINE_HEIGHT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800590
591 int v = 0;
592 boolean needMultiply = (spacingmult != 1 || spacingadd != 0);
593
Raph Leviend3ab6922015-03-02 14:30:53 -0800594 Paint.FontMetricsInt fm = b.mFontMetricsInt;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800595 int[] chooseHtv = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800596
Seigo Nonakabafe1972017-08-24 15:30:29 -0700597 final int[] indents;
598 if (mLeftIndents != null || mRightIndents != null) {
599 final int leftLen = mLeftIndents == null ? 0 : mLeftIndents.length;
600 final int rightLen = mRightIndents == null ? 0 : mRightIndents.length;
601 final int indentsLen = Math.max(leftLen, rightLen);
602 indents = new int[indentsLen];
603 for (int i = 0; i < leftLen; i++) {
604 indents[i] = mLeftIndents[i];
605 }
606 for (int i = 0; i < rightLen; i++) {
607 indents[i] += mRightIndents[i];
608 }
609 } else {
610 indents = null;
611 }
612
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700613 final NativeLineBreaker lineBreaker = new NativeLineBreaker(
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700614 b.mBreakStrategy, b.mHyphenationFrequency,
615 // TODO: Support more justification mode, e.g. letter spacing, stretching.
616 b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE,
Seigo Nonaka9155fa62018-06-05 18:22:03 -0700617 indents);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800618
Seigo Nonakac3328d62018-03-20 15:18:59 -0700619 PrecomputedText.ParagraphInfo[] paragraphInfo = null;
620 final Spanned spanned = (source instanceof Spanned) ? (Spanned) source : null;
Seigo Nonakabeafa1f2018-02-01 21:39:24 -0800621 if (source instanceof PrecomputedText) {
Seigo Nonakac3328d62018-03-20 15:18:59 -0700622 PrecomputedText precomputed = (PrecomputedText) source;
623 if (precomputed.canUseMeasuredResult(bufStart, bufEnd, textDir, paint,
624 b.mBreakStrategy, b.mHyphenationFrequency)) {
Seigo Nonakabeafa1f2018-02-01 21:39:24 -0800625 // Some parameters are different from the ones when measured text is created.
Seigo Nonakac3328d62018-03-20 15:18:59 -0700626 paragraphInfo = precomputed.getParagraphInfo();
Seigo Nonakafbe63bd2017-12-02 19:28:05 -0800627 }
Seigo Nonaka87b15472018-01-12 14:06:29 -0800628 }
629
Seigo Nonakac3328d62018-03-20 15:18:59 -0700630 if (paragraphInfo == null) {
Seigo Nonakabeafa1f2018-02-01 21:39:24 -0800631 final PrecomputedText.Params param = new PrecomputedText.Params(paint, textDir,
632 b.mBreakStrategy, b.mHyphenationFrequency);
Seigo Nonakac3328d62018-03-20 15:18:59 -0700633 paragraphInfo = PrecomputedText.createMeasuredParagraphs(source, param, bufStart,
634 bufEnd, false /* computeLayout */);
Seigo Nonaka4e90fa22018-02-13 21:40:01 +0000635 }
Seigo Nonakafbe63bd2017-12-02 19:28:05 -0800636
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700637 for (int paraIndex = 0; paraIndex < paragraphInfo.length; paraIndex++) {
638 final int paraStart = paraIndex == 0
639 ? bufStart : paragraphInfo[paraIndex - 1].paragraphEnd;
640 final int paraEnd = paragraphInfo[paraIndex].paragraphEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800641
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700642 int firstWidthLineCount = 1;
643 int firstWidth = outerWidth;
644 int restWidth = outerWidth;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800645
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700646 LineHeightSpan[] chooseHt = null;
647 if (spanned != null) {
648 LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
649 LeadingMarginSpan.class);
650 for (int i = 0; i < sp.length; i++) {
651 LeadingMarginSpan lms = sp[i];
652 firstWidth -= sp[i].getLeadingMargin(true);
653 restWidth -= sp[i].getLeadingMargin(false);
Doug Feltcb3791202011-07-07 11:57:48 -0700654
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700655 // LeadingMarginSpan2 is odd. The count affects all
656 // leading margin spans, not just this particular one
657 if (lms instanceof LeadingMarginSpan2) {
658 LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
659 firstWidthLineCount = Math.max(firstWidthLineCount,
660 lms2.getLeadingMarginLineCount());
661 }
662 }
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700663
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700664 chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
665
666 if (chooseHt.length == 0) {
667 chooseHt = null; // So that out() would not assume it has any contents
668 } else {
669 if (chooseHtv == null || chooseHtv.length < chooseHt.length) {
670 chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700671 }
672
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700673 for (int i = 0; i < chooseHt.length; i++) {
674 int o = spanned.getSpanStart(chooseHt[i]);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700675
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700676 if (o < paraStart) {
677 // starts in this layout, before the
678 // current paragraph
679
680 chooseHtv[i] = getLineTop(getLineForOffset(o));
681 } else {
682 // starts in this paragraph
683
684 chooseHtv[i] = v;
685 }
686 }
687 }
688 }
689 // tab stop locations
690 int[] variableTabStops = null;
691 if (spanned != null) {
692 TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
693 paraEnd, TabStopSpan.class);
694 if (spans.length > 0) {
695 int[] stops = new int[spans.length];
696 for (int i = 0; i < spans.length; i++) {
697 stops[i] = spans[i].getTabStop();
698 }
699 Arrays.sort(stops, 0, stops.length);
700 variableTabStops = stops;
701 }
702 }
703
704 final MeasuredParagraph measuredPara = paragraphInfo[paraIndex].measured;
705 final char[] chs = measuredPara.getChars();
706 final int[] spanEndCache = measuredPara.getSpanEndCache().getRawArray();
707 final int[] fmCache = measuredPara.getFontMetrics().getRawArray();
708 int breakCount = lineBreaker.computeLineBreaks(
709 measuredPara.getChars(),
710 measuredPara.getNativeMeasuredParagraph(),
711 paraEnd - paraStart,
712 firstWidth,
713 firstWidthLineCount,
714 restWidth,
715 variableTabStops,
716 TAB_INCREMENT,
717 mLineCount,
718 lineBreaks);
719
720 final int[] breaks = lineBreaks.breaks;
721 final float[] lineWidths = lineBreaks.widths;
722 final float[] ascents = lineBreaks.ascents;
723 final float[] descents = lineBreaks.descents;
724 final int[] flags = lineBreaks.flags;
725
726 final int remainingLineCount = mMaximumVisibleLineCount - mLineCount;
727 final boolean ellipsisMayBeApplied = ellipsize != null
728 && (ellipsize == TextUtils.TruncateAt.END
729 || (mMaximumVisibleLineCount == 1
730 && ellipsize != TextUtils.TruncateAt.MARQUEE));
731 if (0 < remainingLineCount && remainingLineCount < breakCount
732 && ellipsisMayBeApplied) {
733 // Calculate width
734 float width = 0;
735 int flag = 0; // XXX May need to also have starting hyphen edit
736 for (int i = remainingLineCount - 1; i < breakCount; i++) {
737 if (i == breakCount - 1) {
738 width += lineWidths[i];
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700739 } else {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700740 for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) {
741 width += measuredPara.getCharWidthAt(j - paraStart);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700742 }
Mark Wagner7b5676e2009-10-16 11:44:23 -0700743 }
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700744 flag |= flags[i] & TAB_MASK;
745 }
746 // Treat the last line and overflowed lines as a single line.
747 breaks[remainingLineCount - 1] = breaks[breakCount - 1];
748 lineWidths[remainingLineCount - 1] = width;
749 flags[remainingLineCount - 1] = flag;
750
751 breakCount = remainingLineCount;
752 }
753
754 // here is the offset of the starting character of the line we are currently
755 // measuring
756 int here = paraStart;
757
758 int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
759 int fmCacheIndex = 0;
760 int spanEndCacheIndex = 0;
761 int breakIndex = 0;
762 for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
763 // retrieve end of span
764 spanEnd = spanEndCache[spanEndCacheIndex++];
765
766 // retrieve cached metrics, order matches above
767 fm.top = fmCache[fmCacheIndex * 4 + 0];
768 fm.bottom = fmCache[fmCacheIndex * 4 + 1];
769 fm.ascent = fmCache[fmCacheIndex * 4 + 2];
770 fm.descent = fmCache[fmCacheIndex * 4 + 3];
771 fmCacheIndex++;
772
773 if (fm.top < fmTop) {
774 fmTop = fm.top;
775 }
776 if (fm.ascent < fmAscent) {
777 fmAscent = fm.ascent;
778 }
779 if (fm.descent > fmDescent) {
780 fmDescent = fm.descent;
781 }
782 if (fm.bottom > fmBottom) {
783 fmBottom = fm.bottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800784 }
785
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700786 // skip breaks ending before current span range
787 while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
788 breakIndex++;
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700789 }
790
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700791 while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
792 int endPos = paraStart + breaks[breakIndex];
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800793
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700794 boolean moreChars = (endPos < bufEnd);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700795
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700796 final int ascent = fallbackLineSpacing
797 ? Math.min(fmAscent, Math.round(ascents[breakIndex]))
798 : fmAscent;
799 final int descent = fallbackLineSpacing
800 ? Math.max(fmDescent, Math.round(descents[breakIndex]))
801 : fmDescent;
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700802
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700803 v = out(source, here, endPos,
804 ascent, descent, fmTop, fmBottom,
805 v, spacingmult, spacingadd, chooseHt, chooseHtv, fm,
806 flags[breakIndex], needMultiply, measuredPara, bufEnd,
807 includepad, trackpad, addLastLineSpacing, chs,
808 paraStart, ellipsize, ellipsizedWidth, lineWidths[breakIndex],
809 paint, moreChars);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700810
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700811 if (endPos < spanEnd) {
812 // preserve metrics for current span
Anish Athalyec8f9e622014-07-21 15:26:34 -0700813 fmTop = fm.top;
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700814 fmBottom = fm.bottom;
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700815 fmAscent = fm.ascent;
816 fmDescent = fm.descent;
817 } else {
818 fmTop = fmBottom = fmAscent = fmDescent = 0;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700819 }
820
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700821 here = endPos;
822 breakIndex++;
823
824 if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) {
825 return;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700826 }
827 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800828 }
829
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700830 if (paraEnd == bufEnd) {
831 break;
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700832 }
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700833 }
834
835 if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE)
836 && mLineCount < mMaximumVisibleLineCount) {
837 final MeasuredParagraph measuredPara =
838 MeasuredParagraph.buildForBidi(source, bufEnd, bufEnd, textDir, null);
839 paint.getFontMetricsInt(fm);
840 v = out(source,
841 bufEnd, bufEnd, fm.ascent, fm.descent,
842 fm.top, fm.bottom,
843 v,
844 spacingmult, spacingadd, null,
845 null, fm, 0,
846 needMultiply, measuredPara, bufEnd,
847 includepad, trackpad, addLastLineSpacing, null,
848 bufStart, ellipsize,
849 ellipsizedWidth, 0, paint, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800850 }
851 }
852
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700853 private int out(final CharSequence text, final int start, final int end, int above, int below,
854 int top, int bottom, int v, final float spacingmult, final float spacingadd,
855 final LineHeightSpan[] chooseHt, final int[] chooseHtv, final Paint.FontMetricsInt fm,
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800856 final int flags, final boolean needMultiply, @NonNull final MeasuredParagraph measured,
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800857 final int bufEnd, final boolean includePad, final boolean trackPad,
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700858 final boolean addLastLineLineSpacing, final char[] chs,
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700859 final int widthStart, final TextUtils.TruncateAt ellipsize, final float ellipsisWidth,
860 final float textWidth, final TextPaint paint, final boolean moreChars) {
Siyamed Siniraf398512017-07-25 19:08:42 -0700861 final int j = mLineCount;
862 final int off = j * mColumns;
863 final int want = off + mColumns + TOP;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800864 int[] lines = mLines;
Seigo Nonaka4b6bcee2017-12-11 11:39:51 -0800865 final int dir = measured.getParagraphDir();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800866
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800867 if (want >= lines.length) {
Siyamed Siniraf398512017-07-25 19:08:42 -0700868 final int[] grow = ArrayUtils.newUnpaddedIntArray(GrowingArrayUtils.growSize(want));
Adam Lesinski776abc22014-03-07 11:30:59 -0500869 System.arraycopy(lines, 0, grow, 0, lines.length);
870 mLines = grow;
871 lines = grow;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800872 }
873
Siyamed Siniraf398512017-07-25 19:08:42 -0700874 if (j >= mLineDirections.length) {
875 final Directions[] grow = ArrayUtils.newUnpaddedArray(Directions.class,
876 GrowingArrayUtils.growSize(j));
877 System.arraycopy(mLineDirections, 0, grow, 0, mLineDirections.length);
878 mLineDirections = grow;
879 }
880
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800881 if (chooseHt != null) {
882 fm.ascent = above;
883 fm.descent = below;
884 fm.top = top;
885 fm.bottom = bottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800886
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800887 for (int i = 0; i < chooseHt.length; i++) {
888 if (chooseHt[i] instanceof LineHeightSpan.WithDensity) {
889 ((LineHeightSpan.WithDensity) chooseHt[i])
890 .chooseHeight(text, start, end, chooseHtv[i], v, fm, paint);
891 } else {
892 chooseHt[i].chooseHeight(text, start, end, chooseHtv[i], v, fm);
893 }
894 }
Eric Fischera9f1dd02009-08-12 15:00:10 -0700895
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800896 above = fm.ascent;
897 below = fm.descent;
898 top = fm.top;
899 bottom = fm.bottom;
900 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800901
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800902 boolean firstLine = (j == 0);
903 boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);
Siyamed Sinir0745c722016-05-31 20:39:33 -0700904
905 if (ellipsize != null) {
906 // If there is only one line, then do any type of ellipsis except when it is MARQUEE
907 // if there are multiple lines, just allow END ellipsis on the last line
908 boolean forceEllipsis = moreChars && (mLineCount + 1 == mMaximumVisibleLineCount);
909
910 boolean doEllipsis =
911 (((mMaximumVisibleLineCount == 1 && moreChars) || (firstLine && !moreChars)) &&
912 ellipsize != TextUtils.TruncateAt.MARQUEE) ||
913 (!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
914 ellipsize == TextUtils.TruncateAt.END);
915 if (doEllipsis) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700916 calculateEllipsis(start, end, measured, widthStart,
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800917 ellipsisWidth, ellipsize, j,
918 textWidth, paint, forceEllipsis);
Siyamed Sinir0745c722016-05-31 20:39:33 -0700919 }
920 }
921
Siyamed Sinirfb0b2dc2017-07-26 22:08:24 -0700922 final boolean lastLine;
923 if (mEllipsized) {
924 lastLine = true;
925 } else {
926 final boolean lastCharIsNewLine = widthStart != bufEnd && bufEnd > 0
927 && text.charAt(bufEnd - 1) == CHAR_NEW_LINE;
928 if (end == bufEnd && !lastCharIsNewLine) {
929 lastLine = true;
930 } else if (start == bufEnd && lastCharIsNewLine) {
931 lastLine = true;
932 } else {
933 lastLine = false;
934 }
935 }
Raph Leviend97b0972014-04-24 12:51:35 -0700936
937 if (firstLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800938 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800939 mTopPadding = top - above;
940 }
941
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800942 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800943 above = top;
944 }
945 }
Raph Leviend97b0972014-04-24 12:51:35 -0700946
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800947 int extra;
948
Raph Leviend97b0972014-04-24 12:51:35 -0700949 if (lastLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800950 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800951 mBottomPadding = bottom - below;
952 }
953
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800954 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800955 below = bottom;
956 }
957 }
958
Siyamed Sinir442c1512017-07-24 12:18:27 -0700959 if (needMultiply && (addLastLineLineSpacing || !lastLine)) {
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800960 double ex = (below - above) * (spacingmult - 1) + spacingadd;
Doug Felt10657582010-02-22 11:19:01 -0800961 if (ex >= 0) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800962 extra = (int)(ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -0800963 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800964 extra = -(int)(-ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -0800965 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800966 } else {
967 extra = 0;
968 }
969
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800970 lines[off + START] = start;
971 lines[off + TOP] = v;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800972 lines[off + DESCENT] = below + extra;
Siyamed Sinir0fa89d62017-07-24 20:46:41 -0700973 lines[off + EXTRA] = extra;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800974
Siyamed Sinir0745c722016-05-31 20:39:33 -0700975 // special case for non-ellipsized last visible line when maxLines is set
976 // store the height as if it was ellipsized
977 if (!mEllipsized && currentLineIsTheLastVisibleOne) {
978 // below calculation as if it was the last line
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800979 int maxLineBelow = includePad ? bottom : below;
Siyamed Sinir0745c722016-05-31 20:39:33 -0700980 // similar to the calculation of v below, without the extra.
981 mMaxLineHeight = v + (maxLineBelow - above);
982 }
983
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800984 v += (below - above) + extra;
985 lines[off + mColumns + START] = end;
986 lines[off + mColumns + TOP] = v;
987
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800988 // TODO: could move TAB to share same column as HYPHEN, simplifying this code and gaining
989 // one bit for start field
990 lines[off + TAB] |= flags & TAB_MASK;
991 lines[off + HYPHEN] = flags;
992 lines[off + DIR] |= dir << DIR_SHIFT;
993 mLineDirections[j] = measured.getDirections(start - widthStart, end - widthStart);
994
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800995 mLineCount++;
996 return v;
997 }
998
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800999 private void calculateEllipsis(int lineStart, int lineEnd,
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001000 MeasuredParagraph measured, int widthStart,
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001001 float avail, TextUtils.TruncateAt where,
1002 int line, float textWidth, TextPaint paint,
1003 boolean forceEllipsis) {
1004 avail -= getTotalInsets(line);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001005 if (textWidth <= avail && !forceEllipsis) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001006 // Everything fits!
1007 mLines[mColumns * line + ELLIPSIS_START] = 0;
1008 mLines[mColumns * line + ELLIPSIS_COUNT] = 0;
1009 return;
1010 }
1011
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001012 float ellipsisWidth = paint.measureText(TextUtils.getEllipsisString(where));
1013 int ellipsisStart = 0;
1014 int ellipsisCount = 0;
1015 int len = lineEnd - lineStart;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001016
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001017 // We only support start ellipsis on a single line
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001018 if (where == TextUtils.TruncateAt.START) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001019 if (mMaximumVisibleLineCount == 1) {
1020 float sum = 0;
1021 int i;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001022
Keisuke Kuroyanagied2eea12015-04-14 18:18:35 +09001023 for (i = len; i > 0; i--) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001024 float w = measured.getCharWidthAt(i - 1 + lineStart - widthStart);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001025 if (w + sum + ellipsisWidth > avail) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001026 while (i < len
1027 && measured.getCharWidthAt(i + lineStart - widthStart) == 0.0f) {
Keisuke Kuroyanagi82d1c442016-09-14 14:30:14 +09001028 i++;
1029 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001030 break;
1031 }
1032
1033 sum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001034 }
1035
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001036 ellipsisStart = 0;
1037 ellipsisCount = i;
1038 } else {
1039 if (Log.isLoggable(TAG, Log.WARN)) {
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001040 Log.w(TAG, "Start Ellipsis only supported with one line");
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001041 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001042 }
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001043 } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE ||
1044 where == TextUtils.TruncateAt.END_SMALL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001045 float sum = 0;
1046 int i;
1047
1048 for (i = 0; i < len; i++) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001049 float w = measured.getCharWidthAt(i + lineStart - widthStart);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001050
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001051 if (w + sum + ellipsisWidth > avail) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001052 break;
1053 }
1054
1055 sum += w;
1056 }
1057
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001058 ellipsisStart = i;
1059 ellipsisCount = len - i;
1060 if (forceEllipsis && ellipsisCount == 0 && len > 0) {
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -07001061 ellipsisStart = len - 1;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001062 ellipsisCount = 1;
1063 }
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001064 } else {
1065 // where = TextUtils.TruncateAt.MIDDLE We only support middle ellipsis on a single line
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001066 if (mMaximumVisibleLineCount == 1) {
1067 float lsum = 0, rsum = 0;
1068 int left = 0, right = len;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001069
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001070 float ravail = (avail - ellipsisWidth) / 2;
Raph Levien0e3c5e82014-12-04 13:26:07 -08001071 for (right = len; right > 0; right--) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001072 float w = measured.getCharWidthAt(right - 1 + lineStart - widthStart);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001073
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001074 if (w + rsum > ravail) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001075 while (right < len
1076 && measured.getCharWidthAt(right + lineStart - widthStart)
1077 == 0.0f) {
Keisuke Kuroyanagi82d1c442016-09-14 14:30:14 +09001078 right++;
1079 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001080 break;
1081 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001082 rsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001083 }
1084
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001085 float lavail = avail - ellipsisWidth - rsum;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001086 for (left = 0; left < right; left++) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001087 float w = measured.getCharWidthAt(left + lineStart - widthStart);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001088
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001089 if (w + lsum > lavail) {
1090 break;
1091 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001092
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001093 lsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001094 }
1095
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001096 ellipsisStart = left;
1097 ellipsisCount = right - left;
1098 } else {
1099 if (Log.isLoggable(TAG, Log.WARN)) {
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001100 Log.w(TAG, "Middle Ellipsis only supported with one line");
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001101 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001102 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001103 }
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001104 mEllipsized = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001105 mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
1106 mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
1107 }
1108
Selim Cinek365ec092017-03-09 00:10:52 -08001109 private float getTotalInsets(int line) {
1110 int totalIndent = 0;
1111 if (mLeftIndents != null) {
1112 totalIndent = mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1113 }
1114 if (mRightIndents != null) {
1115 totalIndent += mRightIndents[Math.min(line, mRightIndents.length - 1)];
1116 }
1117 return totalIndent;
1118 }
1119
Doug Felte8e45f22010-03-29 14:58:40 -07001120 // Override the base class so we can directly access our members,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001121 // rather than relying on member functions.
1122 // The logic mirrors that of Layout.getLineForVertical
1123 // FIXME: It may be faster to do a linear search for layouts without many lines.
Gilles Debunne66111472010-11-19 11:04:37 -08001124 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001125 public int getLineForVertical(int vertical) {
1126 int high = mLineCount;
1127 int low = -1;
1128 int guess;
1129 int[] lines = mLines;
1130 while (high - low > 1) {
1131 guess = (high + low) >> 1;
1132 if (lines[mColumns * guess + TOP] > vertical){
1133 high = guess;
1134 } else {
1135 low = guess;
1136 }
1137 }
1138 if (low < 0) {
1139 return 0;
1140 } else {
1141 return low;
1142 }
1143 }
1144
Gilles Debunne66111472010-11-19 11:04:37 -08001145 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001146 public int getLineCount() {
1147 return mLineCount;
1148 }
1149
Gilles Debunne66111472010-11-19 11:04:37 -08001150 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001151 public int getLineTop(int line) {
Raph Levien07e6c232016-04-04 12:34:06 -07001152 return mLines[mColumns * line + TOP];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001153 }
1154
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001155 /**
1156 * @hide
1157 */
1158 @Override
1159 public int getLineExtra(int line) {
1160 return mLines[mColumns * line + EXTRA];
1161 }
1162
Gilles Debunne66111472010-11-19 11:04:37 -08001163 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001164 public int getLineDescent(int line) {
Raph Levien07e6c232016-04-04 12:34:06 -07001165 return mLines[mColumns * line + DESCENT];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001166 }
1167
Gilles Debunne66111472010-11-19 11:04:37 -08001168 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001169 public int getLineStart(int line) {
1170 return mLines[mColumns * line + START] & START_MASK;
1171 }
1172
Gilles Debunne66111472010-11-19 11:04:37 -08001173 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001174 public int getParagraphDirection(int line) {
1175 return mLines[mColumns * line + DIR] >> DIR_SHIFT;
1176 }
1177
Gilles Debunne66111472010-11-19 11:04:37 -08001178 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001179 public boolean getLineContainsTab(int line) {
1180 return (mLines[mColumns * line + TAB] & TAB_MASK) != 0;
1181 }
1182
Gilles Debunne66111472010-11-19 11:04:37 -08001183 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001184 public final Directions getLineDirections(int line) {
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001185 if (line > getLineCount()) {
1186 throw new ArrayIndexOutOfBoundsException();
1187 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001188 return mLineDirections[line];
1189 }
1190
Gilles Debunne66111472010-11-19 11:04:37 -08001191 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001192 public int getTopPadding() {
1193 return mTopPadding;
1194 }
1195
Gilles Debunne66111472010-11-19 11:04:37 -08001196 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001197 public int getBottomPadding() {
1198 return mBottomPadding;
1199 }
1200
Raph Levien26d443a2015-03-30 14:18:32 -07001201 /**
1202 * @hide
1203 */
1204 @Override
1205 public int getHyphen(int line) {
Keisuke Kuroyanagif5af4a32016-08-31 21:40:53 +09001206 return mLines[mColumns * line + HYPHEN] & HYPHEN_MASK;
Raph Levien26d443a2015-03-30 14:18:32 -07001207 }
1208
Raph Levien2ea52902015-07-01 14:39:31 -07001209 /**
1210 * @hide
1211 */
1212 @Override
1213 public int getIndentAdjust(int line, Alignment align) {
1214 if (align == Alignment.ALIGN_LEFT) {
1215 if (mLeftIndents == null) {
1216 return 0;
1217 } else {
1218 return mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1219 }
1220 } else if (align == Alignment.ALIGN_RIGHT) {
1221 if (mRightIndents == null) {
1222 return 0;
1223 } else {
1224 return -mRightIndents[Math.min(line, mRightIndents.length - 1)];
1225 }
1226 } else if (align == Alignment.ALIGN_CENTER) {
1227 int left = 0;
1228 if (mLeftIndents != null) {
1229 left = mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1230 }
1231 int right = 0;
1232 if (mRightIndents != null) {
1233 right = mRightIndents[Math.min(line, mRightIndents.length - 1)];
1234 }
1235 return (left - right) >> 1;
1236 } else {
1237 throw new AssertionError("unhandled alignment " + align);
1238 }
1239 }
1240
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001241 @Override
1242 public int getEllipsisCount(int line) {
1243 if (mColumns < COLUMNS_ELLIPSIZE) {
1244 return 0;
1245 }
1246
1247 return mLines[mColumns * line + ELLIPSIS_COUNT];
1248 }
1249
1250 @Override
1251 public int getEllipsisStart(int line) {
1252 if (mColumns < COLUMNS_ELLIPSIZE) {
1253 return 0;
1254 }
1255
1256 return mLines[mColumns * line + ELLIPSIS_START];
1257 }
1258
1259 @Override
1260 public int getEllipsizedWidth() {
1261 return mEllipsizedWidth;
1262 }
1263
Siyamed Sinir0745c722016-05-31 20:39:33 -07001264 /**
1265 * Return the total height of this layout.
1266 *
1267 * @param cap if true and max lines is set, returns the height of the layout at the max lines.
1268 *
1269 * @hide
1270 */
1271 public int getHeight(boolean cap) {
Siyamed Sinir70ffd282018-03-08 17:19:46 -08001272 if (cap && mLineCount > mMaximumVisibleLineCount && mMaxLineHeight == -1
1273 && Log.isLoggable(TAG, Log.WARN)) {
Siyamed Sinir0745c722016-05-31 20:39:33 -07001274 Log.w(TAG, "maxLineHeight should not be -1. "
1275 + " maxLines:" + mMaximumVisibleLineCount
1276 + " lineCount:" + mLineCount);
1277 }
1278
Siyamed Sinir70ffd282018-03-08 17:19:46 -08001279 return cap && mLineCount > mMaximumVisibleLineCount && mMaxLineHeight != -1
1280 ? mMaxLineHeight : super.getHeight();
Siyamed Sinir0745c722016-05-31 20:39:33 -07001281 }
1282
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001283 private int mLineCount;
1284 private int mTopPadding, mBottomPadding;
1285 private int mColumns;
1286 private int mEllipsizedWidth;
1287
Siyamed Sinir0745c722016-05-31 20:39:33 -07001288 /**
1289 * Keeps track if ellipsize is applied to the text.
1290 */
1291 private boolean mEllipsized;
1292
1293 /**
1294 * If maxLines is set, ellipsize is not set, and the actual line count of text is greater than
1295 * or equal to maxLine, this variable holds the ideal visual height of the maxLine'th line
1296 * starting from the top of the layout. If maxLines is not set its value will be -1.
1297 *
1298 * The value is the same as getLineTop(maxLines) for ellipsized version where structurally no
1299 * more than maxLines is contained.
1300 */
Siyamed Sinira19cd512017-08-03 22:01:56 -07001301 private int mMaxLineHeight = DEFAULT_MAX_LINE_HEIGHT;
Siyamed Sinir0745c722016-05-31 20:39:33 -07001302
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001303 private static final int COLUMNS_NORMAL = 5;
1304 private static final int COLUMNS_ELLIPSIZE = 7;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001305 private static final int START = 0;
1306 private static final int DIR = START;
1307 private static final int TAB = START;
1308 private static final int TOP = 1;
1309 private static final int DESCENT = 2;
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001310 private static final int EXTRA = 3;
1311 private static final int HYPHEN = 4;
1312 private static final int ELLIPSIS_START = 5;
1313 private static final int ELLIPSIS_COUNT = 6;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001314
1315 private int[] mLines;
1316 private Directions[] mLineDirections;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001317 private int mMaximumVisibleLineCount = Integer.MAX_VALUE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001318
1319 private static final int START_MASK = 0x1FFFFFFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001320 private static final int DIR_SHIFT = 30;
1321 private static final int TAB_MASK = 0x20000000;
Keisuke Kuroyanagif5af4a32016-08-31 21:40:53 +09001322 private static final int HYPHEN_MASK = 0xFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001323
Doug Feltc982f602010-05-25 11:51:40 -07001324 private static final int TAB_INCREMENT = 20; // same as Layout, but that's private
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001325
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001326 private static final char CHAR_NEW_LINE = '\n';
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001327
1328 private static final double EXTRA_ROUNDING = 0.5;
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001329
Siyamed Sinira19cd512017-08-03 22:01:56 -07001330 private static final int DEFAULT_MAX_LINE_HEIGHT = -1;
1331
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001332 // Unused, here because of gray list private API accesses.
Deepanshu Gupta70539192014-10-15 15:57:40 -07001333 /*package*/ static class LineBreaks {
Anish Athalyec8f9e622014-07-21 15:26:34 -07001334 private static final int INITIAL_SIZE = 16;
1335 public int[] breaks = new int[INITIAL_SIZE];
1336 public float[] widths = new float[INITIAL_SIZE];
Roozbeh Pournader737dfea2017-08-10 11:32:24 -07001337 public float[] ascents = new float[INITIAL_SIZE];
1338 public float[] descents = new float[INITIAL_SIZE];
Roozbeh Pournader112d9c72015-08-07 12:44:41 -07001339 public int[] flags = new int[INITIAL_SIZE]; // hasTab
Anish Athalyec8f9e622014-07-21 15:26:34 -07001340 // breaks, widths, and flags should all have the same length
1341 }
1342
Seigo Nonakabafe1972017-08-24 15:30:29 -07001343 @Nullable private int[] mLeftIndents;
1344 @Nullable private int[] mRightIndents;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001345}