blob: 2cf0262a1d281ac48bf43849790a33d57db032d3 [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;
Seigo Nonaka70200b02018-10-01 16:04:11 -070025import android.graphics.text.LineBreaker;
Mathew Inwood8c854f82018-09-14 12:35:36 +010026import android.os.Build;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080027import android.text.style.LeadingMarginSpan;
Gilles Debunne66111472010-11-19 11:04:37 -080028import android.text.style.LeadingMarginSpan.LeadingMarginSpan2;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080029import android.text.style.LineHeightSpan;
Doug Feltc982f602010-05-25 11:51:40 -070030import android.text.style.TabStopSpan;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -070031import android.util.Log;
Raph Levien39b4db72015-03-25 13:18:20 -070032import android.util.Pools.SynchronizedPool;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080033
Doug Feltcb3791202011-07-07 11:57:48 -070034import com.android.internal.util.ArrayUtils;
Adam Lesinski776abc22014-03-07 11:30:59 -050035import com.android.internal.util.GrowingArrayUtils;
Doug Feltcb3791202011-07-07 11:57:48 -070036
Anish Athalyec8f9e622014-07-21 15:26:34 -070037import java.util.Arrays;
38
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080039/**
40 * StaticLayout is a Layout for text that will not be edited after it
41 * is laid out. Use {@link DynamicLayout} for text that may change.
42 * <p>This is used by widgets to control text layout. You should not need
43 * to use this class directly unless you are implementing your own widget
44 * or custom display object, or would be tempted to call
Doug Felt4e0c5e52010-03-15 16:56:02 -070045 * {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int,
46 * float, float, android.graphics.Paint)
47 * Canvas.drawText()} directly.</p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080048 */
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -080049public class StaticLayout extends Layout {
Seigo Nonaka6f1868b2017-12-01 16:24:19 -080050 /*
51 * The break iteration is done in native code. The protocol for using the native code is as
52 * follows.
53 *
54 * First, call nInit to setup native line breaker object. Then, for each paragraph, do the
55 * following:
56 *
Seigo Nonaka9d3bd082018-01-11 10:02:12 -080057 * - Create MeasuredParagraph by MeasuredParagraph.buildForStaticLayout which measures in
58 * native.
Seigo Nonaka70200b02018-10-01 16:04:11 -070059 * - Run LineBreaker.computeLineBreaks() to obtain line breaks for the paragraph.
Seigo Nonaka6f1868b2017-12-01 16:24:19 -080060 *
61 * After all paragraphs, call finish() to release expensive buffers.
62 */
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -080063
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -070064 static final String TAG = "StaticLayout";
65
Raph Leviend3ab6922015-03-02 14:30:53 -080066 /**
Roozbeh Pournader22a167c2017-08-21 12:53:44 -070067 * Builder for static layouts. The builder is the preferred pattern for constructing
68 * StaticLayout objects and should be preferred over the constructors, particularly to access
69 * newer features. To build a static layout, first call {@link #obtain} with the required
70 * arguments (text, paint, and width), then call setters for optional parameters, and finally
71 * {@link #build} to build the StaticLayout object. Parameters not explicitly set will get
Raph Levien531c30c2015-04-30 16:29:59 -070072 * default values.
Raph Leviend3ab6922015-03-02 14:30:53 -080073 */
74 public final static class Builder {
Seigo Nonakab90e08f2017-10-20 15:23:51 -070075 private Builder() {}
Raph Levien4c1f12e2015-03-02 16:29:23 -080076
Raph Levien531c30c2015-04-30 16:29:59 -070077 /**
Roozbeh Pournader22a167c2017-08-21 12:53:44 -070078 * Obtain a builder for constructing StaticLayout objects.
Raph Levien531c30c2015-04-30 16:29:59 -070079 *
80 * @param source The text to be laid out, optionally with spans
81 * @param start The index of the start of the text
82 * @param end The index + 1 of the end of the text
83 * @param paint The base paint used for layout
84 * @param width The width in pixels
85 * @return a builder object used for constructing the StaticLayout
86 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -070087 @NonNull
88 public static Builder obtain(@NonNull CharSequence source, @IntRange(from = 0) int start,
89 @IntRange(from = 0) int end, @NonNull TextPaint paint,
90 @IntRange(from = 0) int width) {
Raph Levien39b4db72015-03-25 13:18:20 -070091 Builder b = sPool.acquire();
Raph Leviend3ab6922015-03-02 14:30:53 -080092 if (b == null) {
93 b = new Builder();
94 }
95
96 // set default initial values
Raph Levien39b4db72015-03-25 13:18:20 -070097 b.mText = source;
98 b.mStart = start;
99 b.mEnd = end;
Raph Levienebd66ca2015-04-30 15:27:57 -0700100 b.mPaint = paint;
Raph Levien39b4db72015-03-25 13:18:20 -0700101 b.mWidth = width;
102 b.mAlignment = Alignment.ALIGN_NORMAL;
Raph Leviend3ab6922015-03-02 14:30:53 -0800103 b.mTextDir = TextDirectionHeuristics.FIRSTSTRONG_LTR;
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700104 b.mSpacingMult = DEFAULT_LINESPACING_MULTIPLIER;
105 b.mSpacingAdd = DEFAULT_LINESPACING_ADDITION;
Raph Leviend3ab6922015-03-02 14:30:53 -0800106 b.mIncludePad = true;
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700107 b.mFallbackLineSpacing = false;
Raph Levien39b4db72015-03-25 13:18:20 -0700108 b.mEllipsizedWidth = width;
Raph Leviend3ab6922015-03-02 14:30:53 -0800109 b.mEllipsize = null;
110 b.mMaxLines = Integer.MAX_VALUE;
Raph Levien3bd60c72015-05-06 14:26:35 -0700111 b.mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700112 b.mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700113 b.mJustificationMode = Layout.JUSTIFICATION_MODE_NONE;
Raph Leviend3ab6922015-03-02 14:30:53 -0800114 return b;
115 }
116
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700117 /**
118 * This method should be called after the layout is finished getting constructed and the
119 * builder needs to be cleaned up and returned to the pool.
120 */
121 private static void recycle(@NonNull Builder b) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800122 b.mPaint = null;
123 b.mText = null;
Raph Levien2ea52902015-07-01 14:39:31 -0700124 b.mLeftIndents = null;
125 b.mRightIndents = null;
Raph Levien39b4db72015-03-25 13:18:20 -0700126 sPool.release(b);
Raph Leviend3ab6922015-03-02 14:30:53 -0800127 }
128
129 // release any expensive state
130 /* package */ void finish() {
Raph Levien22ba7862015-07-29 12:34:13 -0700131 mText = null;
132 mPaint = null;
133 mLeftIndents = null;
134 mRightIndents = null;
Raph Leviend3ab6922015-03-02 14:30:53 -0800135 }
136
137 public Builder setText(CharSequence source) {
138 return setText(source, 0, source.length());
139 }
140
Raph Levien531c30c2015-04-30 16:29:59 -0700141 /**
142 * Set the text. Only useful when re-using the builder, which is done for
143 * the internal implementation of {@link DynamicLayout} but not as part
144 * of normal {@link StaticLayout} usage.
145 *
146 * @param source The text to be laid out, optionally with spans
147 * @param start The index of the start of the text
148 * @param end The index + 1 of the end of the text
149 * @return this builder, useful for chaining
150 *
151 * @hide
152 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700153 @NonNull
154 public Builder setText(@NonNull CharSequence source, int start, int end) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800155 mText = source;
156 mStart = start;
157 mEnd = end;
158 return this;
159 }
160
Raph Levien531c30c2015-04-30 16:29:59 -0700161 /**
162 * Set the paint. Internal for reuse cases only.
163 *
164 * @param paint The base paint used for layout
165 * @return this builder, useful for chaining
166 *
167 * @hide
168 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700169 @NonNull
170 public Builder setPaint(@NonNull TextPaint paint) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800171 mPaint = paint;
172 return this;
173 }
174
Raph Levien531c30c2015-04-30 16:29:59 -0700175 /**
176 * Set the width. Internal for reuse cases only.
177 *
178 * @param width The width in pixels
179 * @return this builder, useful for chaining
180 *
181 * @hide
182 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700183 @NonNull
184 public Builder setWidth(@IntRange(from = 0) int width) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800185 mWidth = width;
186 if (mEllipsize == null) {
187 mEllipsizedWidth = width;
188 }
189 return this;
190 }
191
Raph Levien531c30c2015-04-30 16:29:59 -0700192 /**
193 * Set the alignment. The default is {@link Layout.Alignment#ALIGN_NORMAL}.
194 *
195 * @param alignment Alignment for the resulting {@link StaticLayout}
196 * @return this builder, useful for chaining
197 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700198 @NonNull
199 public Builder setAlignment(@NonNull Alignment alignment) {
Raph Levien39b4db72015-03-25 13:18:20 -0700200 mAlignment = alignment;
201 return this;
202 }
203
Raph Levien531c30c2015-04-30 16:29:59 -0700204 /**
205 * Set the text direction heuristic. The text direction heuristic is used to
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700206 * resolve text direction per-paragraph based on the input text. The default is
Raph Levien531c30c2015-04-30 16:29:59 -0700207 * {@link TextDirectionHeuristics#FIRSTSTRONG_LTR}.
208 *
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700209 * @param textDir text direction heuristic for resolving bidi behavior.
Raph Levien531c30c2015-04-30 16:29:59 -0700210 * @return this builder, useful for chaining
211 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700212 @NonNull
213 public Builder setTextDirection(@NonNull TextDirectionHeuristic textDir) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800214 mTextDir = textDir;
215 return this;
216 }
217
Raph Levien531c30c2015-04-30 16:29:59 -0700218 /**
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700219 * Set line spacing parameters. Each line will have its line spacing multiplied by
220 * {@code spacingMult} and then increased by {@code spacingAdd}. The default is 0.0 for
221 * {@code spacingAdd} and 1.0 for {@code spacingMult}.
Raph Levien531c30c2015-04-30 16:29:59 -0700222 *
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700223 * @param spacingAdd the amount of line spacing addition
224 * @param spacingMult the line spacing multiplier
Raph Levien531c30c2015-04-30 16:29:59 -0700225 * @return this builder, useful for chaining
226 * @see android.widget.TextView#setLineSpacing
227 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700228 @NonNull
229 public Builder setLineSpacing(float spacingAdd, @FloatRange(from = 0.0) float spacingMult) {
Raph Levien531c30c2015-04-30 16:29:59 -0700230 mSpacingAdd = spacingAdd;
Raph Leviend3ab6922015-03-02 14:30:53 -0800231 mSpacingMult = spacingMult;
232 return this;
233 }
234
Raph Levien531c30c2015-04-30 16:29:59 -0700235 /**
236 * Set whether to include extra space beyond font ascent and descent (which is
237 * needed to avoid clipping in some languages, such as Arabic and Kannada). The
238 * default is {@code true}.
239 *
240 * @param includePad whether to include padding
241 * @return this builder, useful for chaining
242 * @see android.widget.TextView#setIncludeFontPadding
243 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700244 @NonNull
Raph Leviend3ab6922015-03-02 14:30:53 -0800245 public Builder setIncludePad(boolean includePad) {
246 mIncludePad = includePad;
247 return this;
248 }
249
Raph Levien531c30c2015-04-30 16:29:59 -0700250 /**
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700251 * Set whether to respect the ascent and descent of the fallback fonts that are used in
252 * displaying the text (which is needed to avoid text from consecutive lines running into
253 * each other). If set, fallback fonts that end up getting used can increase the ascent
254 * and descent of the lines that they are used on.
255 *
256 * <p>For backward compatibility reasons, the default is {@code false}, but setting this to
257 * true is strongly recommended. It is required to be true if text could be in languages
258 * like Burmese or Tibetan where text is typically much taller or deeper than Latin text.
259 *
260 * @param useLineSpacingFromFallbacks whether to expand linespacing based on fallback fonts
261 * @return this builder, useful for chaining
262 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700263 @NonNull
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700264 public Builder setUseLineSpacingFromFallbacks(boolean useLineSpacingFromFallbacks) {
265 mFallbackLineSpacing = useLineSpacingFromFallbacks;
266 return this;
267 }
268
269 /**
Raph Levien531c30c2015-04-30 16:29:59 -0700270 * Set the width as used for ellipsizing purposes, if it differs from the
271 * normal layout width. The default is the {@code width}
272 * passed to {@link #obtain}.
273 *
274 * @param ellipsizedWidth width used for ellipsizing, in pixels
275 * @return this builder, useful for chaining
276 * @see android.widget.TextView#setEllipsize
277 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700278 @NonNull
279 public Builder setEllipsizedWidth(@IntRange(from = 0) int ellipsizedWidth) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800280 mEllipsizedWidth = ellipsizedWidth;
281 return this;
282 }
283
Raph Levien531c30c2015-04-30 16:29:59 -0700284 /**
285 * Set ellipsizing on the layout. Causes words that are longer than the view
286 * is wide, or exceeding the number of lines (see #setMaxLines) in the case
287 * of {@link android.text.TextUtils.TruncateAt#END} or
288 * {@link android.text.TextUtils.TruncateAt#MARQUEE}, to be ellipsized instead
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700289 * of broken. The default is {@code null}, indicating no ellipsis is to be applied.
Raph Levien531c30c2015-04-30 16:29:59 -0700290 *
291 * @param ellipsize type of ellipsis behavior
292 * @return this builder, useful for chaining
293 * @see android.widget.TextView#setEllipsize
294 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700295 @NonNull
Raph Levien531c30c2015-04-30 16:29:59 -0700296 public Builder setEllipsize(@Nullable TextUtils.TruncateAt ellipsize) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800297 mEllipsize = ellipsize;
298 return this;
299 }
300
Raph Levien531c30c2015-04-30 16:29:59 -0700301 /**
302 * Set maximum number of lines. This is particularly useful in the case of
303 * ellipsizing, where it changes the layout of the last line. The default is
304 * unlimited.
305 *
306 * @param maxLines maximum number of lines in the layout
307 * @return this builder, useful for chaining
308 * @see android.widget.TextView#setMaxLines
309 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700310 @NonNull
311 public Builder setMaxLines(@IntRange(from = 0) int maxLines) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800312 mMaxLines = maxLines;
313 return this;
314 }
315
Raph Levien531c30c2015-04-30 16:29:59 -0700316 /**
317 * Set break strategy, useful for selecting high quality or balanced paragraph
318 * layout options. The default is {@link Layout#BREAK_STRATEGY_SIMPLE}.
Siyamed Sinirc0dab362018-07-22 13:48:51 -0700319 * <p/>
320 * Enabling hyphenation with either using {@link Layout#HYPHENATION_FREQUENCY_NORMAL} or
321 * {@link Layout#HYPHENATION_FREQUENCY_FULL} while line breaking is set to one of
322 * {@link Layout#BREAK_STRATEGY_BALANCED}, {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}
323 * improves the structure of text layout however has performance impact and requires more
324 * time to do the text layout.
Raph Levien531c30c2015-04-30 16:29:59 -0700325 *
326 * @param breakStrategy break strategy for paragraph layout
327 * @return this builder, useful for chaining
328 * @see android.widget.TextView#setBreakStrategy
Siyamed Sinirc0dab362018-07-22 13:48:51 -0700329 * @see #setHyphenationFrequency(int)
Raph Levien531c30c2015-04-30 16:29:59 -0700330 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700331 @NonNull
Raph Levien39b4db72015-03-25 13:18:20 -0700332 public Builder setBreakStrategy(@BreakStrategy int breakStrategy) {
333 mBreakStrategy = breakStrategy;
334 return this;
335 }
336
Raph Levien531c30c2015-04-30 16:29:59 -0700337 /**
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700338 * Set hyphenation frequency, to control the amount of automatic hyphenation used. The
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700339 * possible values are defined in {@link Layout}, by constants named with the pattern
340 * {@code HYPHENATION_FREQUENCY_*}. The default is
341 * {@link Layout#HYPHENATION_FREQUENCY_NONE}.
Siyamed Sinirc0dab362018-07-22 13:48:51 -0700342 * <p/>
343 * Enabling hyphenation with either using {@link Layout#HYPHENATION_FREQUENCY_NORMAL} or
344 * {@link Layout#HYPHENATION_FREQUENCY_FULL} while line breaking is set to one of
345 * {@link Layout#BREAK_STRATEGY_BALANCED}, {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}
346 * improves the structure of text layout however has performance impact and requires more
347 * time to do the text layout.
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700348 *
349 * @param hyphenationFrequency hyphenation frequency for the paragraph
350 * @return this builder, useful for chaining
351 * @see android.widget.TextView#setHyphenationFrequency
Siyamed Sinirc0dab362018-07-22 13:48:51 -0700352 * @see #setBreakStrategy(int)
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700353 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700354 @NonNull
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700355 public Builder setHyphenationFrequency(@HyphenationFrequency int hyphenationFrequency) {
356 mHyphenationFrequency = hyphenationFrequency;
357 return this;
358 }
359
360 /**
Raph Levien531c30c2015-04-30 16:29:59 -0700361 * Set indents. Arguments are arrays holding an indent amount, one per line, measured in
362 * pixels. For lines past the last element in the array, the last element repeats.
363 *
364 * @param leftIndents array of indent values for left margin, in pixels
365 * @param rightIndents array of indent values for right margin, in pixels
366 * @return this builder, useful for chaining
Raph Levien531c30c2015-04-30 16:29:59 -0700367 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700368 @NonNull
369 public Builder setIndents(@Nullable int[] leftIndents, @Nullable int[] rightIndents) {
Raph Levien2ea52902015-07-01 14:39:31 -0700370 mLeftIndents = leftIndents;
371 mRightIndents = rightIndents;
Raph Leviene319d5a2015-04-14 23:51:07 -0700372 return this;
373 }
374
Raph Levien70616ec2015-03-04 10:41:30 -0800375 /**
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700376 * Set paragraph justification mode. The default value is
377 * {@link Layout#JUSTIFICATION_MODE_NONE}. If the last line is too short for justification,
378 * the last line will be displayed with the alignment set by {@link #setAlignment}.
Seigo Nonaka09da71a2016-11-28 16:24:14 +0900379 *
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700380 * @param justificationMode justification mode for the paragraph.
Seigo Nonaka09da71a2016-11-28 16:24:14 +0900381 * @return this builder, useful for chaining.
382 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700383 @NonNull
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700384 public Builder setJustificationMode(@JustificationMode int justificationMode) {
385 mJustificationMode = justificationMode;
Seigo Nonaka09da71a2016-11-28 16:24:14 +0900386 return this;
387 }
388
Siyamed Sinir442c1512017-07-24 12:18:27 -0700389 /**
390 * Sets whether the line spacing should be applied for the last line. Default value is
391 * {@code false}.
392 *
393 * @hide
394 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700395 @NonNull
Siyamed Sinir442c1512017-07-24 12:18:27 -0700396 /* package */ Builder setAddLastLineLineSpacing(boolean value) {
397 mAddLastLineLineSpacing = value;
398 return this;
399 }
400
Seigo Nonaka09da71a2016-11-28 16:24:14 +0900401 /**
Raph Levien531c30c2015-04-30 16:29:59 -0700402 * Build the {@link StaticLayout} after options have been set.
403 *
404 * <p>Note: the builder object must not be reused in any way after calling this
405 * method. Setting parameters after calling this method, or calling it a second
406 * time on the same builder object, will likely lead to unexpected results.
407 *
408 * @return the newly constructed {@link StaticLayout} object
409 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700410 @NonNull
Raph Leviend3ab6922015-03-02 14:30:53 -0800411 public StaticLayout build() {
Raph Levien39b4db72015-03-25 13:18:20 -0700412 StaticLayout result = new StaticLayout(this);
413 Builder.recycle(this);
Raph Leviend3ab6922015-03-02 14:30:53 -0800414 return result;
415 }
416
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700417 private CharSequence mText;
418 private int mStart;
419 private int mEnd;
420 private TextPaint mPaint;
421 private int mWidth;
422 private Alignment mAlignment;
423 private TextDirectionHeuristic mTextDir;
424 private float mSpacingMult;
425 private float mSpacingAdd;
426 private boolean mIncludePad;
427 private boolean mFallbackLineSpacing;
428 private int mEllipsizedWidth;
429 private TextUtils.TruncateAt mEllipsize;
430 private int mMaxLines;
431 private int mBreakStrategy;
432 private int mHyphenationFrequency;
Seigo Nonakabafe1972017-08-24 15:30:29 -0700433 @Nullable private int[] mLeftIndents;
434 @Nullable private int[] mRightIndents;
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700435 private int mJustificationMode;
436 private boolean mAddLastLineLineSpacing;
Raph Leviend3ab6922015-03-02 14:30:53 -0800437
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700438 private final Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
Raph Leviend3ab6922015-03-02 14:30:53 -0800439
Siyamed Sinira273a702017-10-05 11:22:12 -0700440 private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<>(3);
Raph Leviend3ab6922015-03-02 14:30:53 -0800441 }
442
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000443 /**
444 * @deprecated Use {@link Builder} instead.
445 */
446 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800447 public StaticLayout(CharSequence source, TextPaint paint,
448 int width,
449 Alignment align, float spacingmult, float spacingadd,
450 boolean includepad) {
451 this(source, 0, source.length(), paint, width, align,
452 spacingmult, spacingadd, includepad);
453 }
454
Doug Feltcb3791202011-07-07 11:57:48 -0700455 /**
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000456 * @deprecated Use {@link Builder} instead.
Doug Feltcb3791202011-07-07 11:57:48 -0700457 */
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000458 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800459 public StaticLayout(CharSequence source, int bufstart, int bufend,
460 TextPaint paint, int outerwidth,
461 Alignment align,
462 float spacingmult, float spacingadd,
463 boolean includepad) {
464 this(source, bufstart, bufend, paint, outerwidth, align,
465 spacingmult, spacingadd, includepad, null, 0);
466 }
467
Doug Feltcb3791202011-07-07 11:57:48 -0700468 /**
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000469 * @deprecated Use {@link Builder} instead.
Doug Feltcb3791202011-07-07 11:57:48 -0700470 */
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000471 @Deprecated
Doug Feltcb3791202011-07-07 11:57:48 -0700472 public StaticLayout(CharSequence source, int bufstart, int bufend,
473 TextPaint paint, int outerwidth,
474 Alignment align,
475 float spacingmult, float spacingadd,
476 boolean includepad,
477 TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
Seigo Nonaka4e90fa22018-02-13 21:40:01 +0000478 this(source, bufstart, bufend, paint, outerwidth, align,
Doug Feltcb3791202011-07-07 11:57:48 -0700479 TextDirectionHeuristics.FIRSTSTRONG_LTR,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700480 spacingmult, spacingadd, includepad, ellipsize, ellipsizedWidth, Integer.MAX_VALUE);
Doug Feltcb3791202011-07-07 11:57:48 -0700481 }
482
483 /**
484 * @hide
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000485 * @deprecated Use {@link Builder} instead.
Doug Feltcb3791202011-07-07 11:57:48 -0700486 */
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000487 @Deprecated
Mathew Inwoodefeab842018-08-14 15:21:30 +0100488 @UnsupportedAppUsage
Seigo Nonaka4e90fa22018-02-13 21:40:01 +0000489 public StaticLayout(CharSequence source, int bufstart, int bufend,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800490 TextPaint paint, int outerwidth,
Doug Feltcb3791202011-07-07 11:57:48 -0700491 Alignment align, TextDirectionHeuristic textDir,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800492 float spacingmult, float spacingadd,
493 boolean includepad,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700494 TextUtils.TruncateAt ellipsize, int ellipsizedWidth, int maxLines) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800495 super((ellipsize == null)
Doug Felt4e0c5e52010-03-15 16:56:02 -0700496 ? source
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800497 : (source instanceof Spanned)
498 ? new SpannedEllipsizer(source)
499 : new Ellipsizer(source),
Doug Feltcb3791202011-07-07 11:57:48 -0700500 paint, outerwidth, align, textDir, spacingmult, spacingadd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800501
Raph Levienebd66ca2015-04-30 15:27:57 -0700502 Builder b = Builder.obtain(source, bufstart, bufend, paint, outerwidth)
Raph Levien39b4db72015-03-25 13:18:20 -0700503 .setAlignment(align)
Raph Leviena6a08282015-06-03 13:20:45 -0700504 .setTextDirection(textDir)
Raph Levien531c30c2015-04-30 16:29:59 -0700505 .setLineSpacing(spacingadd, spacingmult)
Raph Leviend3ab6922015-03-02 14:30:53 -0800506 .setIncludePad(includepad)
507 .setEllipsizedWidth(ellipsizedWidth)
508 .setEllipsize(ellipsize)
509 .setMaxLines(maxLines);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800510 /*
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700511 * This is annoying, but we can't refer to the layout until superclass construction is
512 * finished, and the superclass constructor wants the reference to the display text.
Doug Felt4e0c5e52010-03-15 16:56:02 -0700513 *
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700514 * In other words, the two Ellipsizer classes in Layout.java need a (Dynamic|Static)Layout
515 * as a parameter to do their calculations, but the Ellipsizers also need to be the input
516 * to the superclass's constructor (Layout). In order to go around the circular
517 * dependency, we construct the Ellipsizer with only one of the parameters, the text. And
518 * we fill in the rest of the needed information (layout, width, and method) later, here.
519 *
520 * This will break if the superclass constructor ever actually cares about the content
521 * instead of just holding the reference.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800522 */
523 if (ellipsize != null) {
524 Ellipsizer e = (Ellipsizer) getText();
525
526 e.mLayout = this;
527 e.mWidth = ellipsizedWidth;
528 e.mMethod = ellipsize;
529 mEllipsizedWidth = ellipsizedWidth;
530
531 mColumns = COLUMNS_ELLIPSIZE;
532 } else {
533 mColumns = COLUMNS_NORMAL;
534 mEllipsizedWidth = outerwidth;
535 }
536
Siyamed Siniraf398512017-07-25 19:08:42 -0700537 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2);
538 mLines = ArrayUtils.newUnpaddedIntArray(2 * mColumns);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700539 mMaximumVisibleLineCount = maxLines;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800540
Raph Levien70616ec2015-03-04 10:41:30 -0800541 generate(b, b.mIncludePad, b.mIncludePad);
Doug Felte8e45f22010-03-29 14:58:40 -0700542
Raph Leviend3ab6922015-03-02 14:30:53 -0800543 Builder.recycle(b);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800544 }
545
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000546 /**
547 * Used by DynamicLayout.
548 */
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700549 /* package */ StaticLayout(@Nullable CharSequence text) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700550 super(text, null, 0, null, 0, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800551
552 mColumns = COLUMNS_ELLIPSIZE;
Siyamed Siniraf398512017-07-25 19:08:42 -0700553 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2);
554 mLines = ArrayUtils.newUnpaddedIntArray(2 * mColumns);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800555 }
556
Raph Levien39b4db72015-03-25 13:18:20 -0700557 private StaticLayout(Builder b) {
558 super((b.mEllipsize == null)
559 ? b.mText
560 : (b.mText instanceof Spanned)
561 ? new SpannedEllipsizer(b.mText)
562 : new Ellipsizer(b.mText),
Roozbeh Pournader158dfaf2017-09-21 13:23:50 -0700563 b.mPaint, b.mWidth, b.mAlignment, b.mTextDir, b.mSpacingMult, b.mSpacingAdd);
Raph Levien39b4db72015-03-25 13:18:20 -0700564
565 if (b.mEllipsize != null) {
566 Ellipsizer e = (Ellipsizer) getText();
567
568 e.mLayout = this;
569 e.mWidth = b.mEllipsizedWidth;
570 e.mMethod = b.mEllipsize;
571 mEllipsizedWidth = b.mEllipsizedWidth;
572
573 mColumns = COLUMNS_ELLIPSIZE;
574 } else {
575 mColumns = COLUMNS_NORMAL;
576 mEllipsizedWidth = b.mWidth;
577 }
578
Siyamed Siniraf398512017-07-25 19:08:42 -0700579 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2);
580 mLines = ArrayUtils.newUnpaddedIntArray(2 * mColumns);
Raph Levien39b4db72015-03-25 13:18:20 -0700581 mMaximumVisibleLineCount = b.mMaxLines;
582
Raph Levien2ea52902015-07-01 14:39:31 -0700583 mLeftIndents = b.mLeftIndents;
584 mRightIndents = b.mRightIndents;
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700585 setJustificationMode(b.mJustificationMode);
Raph Levien2ea52902015-07-01 14:39:31 -0700586
Raph Levien39b4db72015-03-25 13:18:20 -0700587 generate(b, b.mIncludePad, b.mIncludePad);
588 }
589
Raph Leviend3ab6922015-03-02 14:30:53 -0800590 /* package */ void generate(Builder b, boolean includepad, boolean trackpad) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700591 final CharSequence source = b.mText;
Seigo Nonakafbe63bd2017-12-02 19:28:05 -0800592 final int bufStart = b.mStart;
593 final int bufEnd = b.mEnd;
Raph Leviend3ab6922015-03-02 14:30:53 -0800594 TextPaint paint = b.mPaint;
595 int outerWidth = b.mWidth;
596 TextDirectionHeuristic textDir = b.mTextDir;
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700597 final boolean fallbackLineSpacing = b.mFallbackLineSpacing;
Raph Leviend3ab6922015-03-02 14:30:53 -0800598 float spacingmult = b.mSpacingMult;
599 float spacingadd = b.mSpacingAdd;
600 float ellipsizedWidth = b.mEllipsizedWidth;
601 TextUtils.TruncateAt ellipsize = b.mEllipsize;
Siyamed Sinir442c1512017-07-24 12:18:27 -0700602 final boolean addLastLineSpacing = b.mAddLastLineLineSpacing;
Seigo Nonakaab9b4792018-09-24 17:37:15 -0700603
604 int lineBreakCapacity = 0;
605 int[] breaks = null;
606 float[] lineWidths = null;
607 float[] ascents = null;
608 float[] descents = null;
609 boolean[] hasTabs = null;
610 int[] hyphenEdits = null;
Anish Athalye88b5b0b2014-06-24 14:39:43 -0700611
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800612 mLineCount = 0;
Siyamed Sinira19cd512017-08-03 22:01:56 -0700613 mEllipsized = false;
Siyamed Sinira8d982d2017-08-21 13:27:47 -0700614 mMaxLineHeight = mMaximumVisibleLineCount < 1 ? 0 : DEFAULT_MAX_LINE_HEIGHT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800615
616 int v = 0;
617 boolean needMultiply = (spacingmult != 1 || spacingadd != 0);
618
Raph Leviend3ab6922015-03-02 14:30:53 -0800619 Paint.FontMetricsInt fm = b.mFontMetricsInt;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800620 int[] chooseHtv = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800621
Seigo Nonakabafe1972017-08-24 15:30:29 -0700622 final int[] indents;
623 if (mLeftIndents != null || mRightIndents != null) {
624 final int leftLen = mLeftIndents == null ? 0 : mLeftIndents.length;
625 final int rightLen = mRightIndents == null ? 0 : mRightIndents.length;
626 final int indentsLen = Math.max(leftLen, rightLen);
627 indents = new int[indentsLen];
628 for (int i = 0; i < leftLen; i++) {
629 indents[i] = mLeftIndents[i];
630 }
631 for (int i = 0; i < rightLen; i++) {
632 indents[i] += mRightIndents[i];
633 }
634 } else {
635 indents = null;
636 }
637
Seigo Nonaka70200b02018-10-01 16:04:11 -0700638 final LineBreaker lineBreaker = new LineBreaker.Builder()
Seigo Nonaka41bb4fa2018-08-07 15:15:42 -0700639 .setBreakStrategy(b.mBreakStrategy)
640 .setHyphenationFrequency(b.mHyphenationFrequency)
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700641 // TODO: Support more justification mode, e.g. letter spacing, stretching.
Seigo Nonaka41bb4fa2018-08-07 15:15:42 -0700642 .setJustified(b.mJustificationMode)
643 .setIndents(indents)
644 .build();
645
Seigo Nonaka70200b02018-10-01 16:04:11 -0700646 LineBreaker.ParagraphConstraints constraints =
647 new LineBreaker.ParagraphConstraints();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800648
Seigo Nonakac3328d62018-03-20 15:18:59 -0700649 PrecomputedText.ParagraphInfo[] paragraphInfo = null;
650 final Spanned spanned = (source instanceof Spanned) ? (Spanned) source : null;
Seigo Nonakabeafa1f2018-02-01 21:39:24 -0800651 if (source instanceof PrecomputedText) {
Seigo Nonakac3328d62018-03-20 15:18:59 -0700652 PrecomputedText precomputed = (PrecomputedText) source;
653 if (precomputed.canUseMeasuredResult(bufStart, bufEnd, textDir, paint,
654 b.mBreakStrategy, b.mHyphenationFrequency)) {
Seigo Nonakabeafa1f2018-02-01 21:39:24 -0800655 // Some parameters are different from the ones when measured text is created.
Seigo Nonakac3328d62018-03-20 15:18:59 -0700656 paragraphInfo = precomputed.getParagraphInfo();
Seigo Nonakafbe63bd2017-12-02 19:28:05 -0800657 }
Seigo Nonaka87b15472018-01-12 14:06:29 -0800658 }
659
Seigo Nonakac3328d62018-03-20 15:18:59 -0700660 if (paragraphInfo == null) {
Seigo Nonakabeafa1f2018-02-01 21:39:24 -0800661 final PrecomputedText.Params param = new PrecomputedText.Params(paint, textDir,
662 b.mBreakStrategy, b.mHyphenationFrequency);
Seigo Nonakac3328d62018-03-20 15:18:59 -0700663 paragraphInfo = PrecomputedText.createMeasuredParagraphs(source, param, bufStart,
664 bufEnd, false /* computeLayout */);
Seigo Nonaka4e90fa22018-02-13 21:40:01 +0000665 }
Seigo Nonakafbe63bd2017-12-02 19:28:05 -0800666
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700667 for (int paraIndex = 0; paraIndex < paragraphInfo.length; paraIndex++) {
668 final int paraStart = paraIndex == 0
669 ? bufStart : paragraphInfo[paraIndex - 1].paragraphEnd;
670 final int paraEnd = paragraphInfo[paraIndex].paragraphEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800671
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700672 int firstWidthLineCount = 1;
673 int firstWidth = outerWidth;
674 int restWidth = outerWidth;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800675
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700676 LineHeightSpan[] chooseHt = null;
677 if (spanned != null) {
678 LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
679 LeadingMarginSpan.class);
680 for (int i = 0; i < sp.length; i++) {
681 LeadingMarginSpan lms = sp[i];
682 firstWidth -= sp[i].getLeadingMargin(true);
683 restWidth -= sp[i].getLeadingMargin(false);
Doug Feltcb3791202011-07-07 11:57:48 -0700684
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700685 // LeadingMarginSpan2 is odd. The count affects all
686 // leading margin spans, not just this particular one
687 if (lms instanceof LeadingMarginSpan2) {
688 LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
689 firstWidthLineCount = Math.max(firstWidthLineCount,
690 lms2.getLeadingMarginLineCount());
691 }
692 }
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700693
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700694 chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
695
696 if (chooseHt.length == 0) {
697 chooseHt = null; // So that out() would not assume it has any contents
698 } else {
699 if (chooseHtv == null || chooseHtv.length < chooseHt.length) {
700 chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700701 }
702
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700703 for (int i = 0; i < chooseHt.length; i++) {
704 int o = spanned.getSpanStart(chooseHt[i]);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700705
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700706 if (o < paraStart) {
707 // starts in this layout, before the
708 // current paragraph
709
710 chooseHtv[i] = getLineTop(getLineForOffset(o));
711 } else {
712 // starts in this paragraph
713
714 chooseHtv[i] = v;
715 }
716 }
717 }
718 }
719 // tab stop locations
720 int[] variableTabStops = null;
721 if (spanned != null) {
722 TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
723 paraEnd, TabStopSpan.class);
724 if (spans.length > 0) {
725 int[] stops = new int[spans.length];
726 for (int i = 0; i < spans.length; i++) {
727 stops[i] = spans[i].getTabStop();
728 }
729 Arrays.sort(stops, 0, stops.length);
730 variableTabStops = stops;
731 }
732 }
733
734 final MeasuredParagraph measuredPara = paragraphInfo[paraIndex].measured;
735 final char[] chs = measuredPara.getChars();
736 final int[] spanEndCache = measuredPara.getSpanEndCache().getRawArray();
737 final int[] fmCache = measuredPara.getFontMetrics().getRawArray();
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700738
Seigo Nonaka41bb4fa2018-08-07 15:15:42 -0700739 constraints.setWidth(restWidth);
740 constraints.setIndent(firstWidth, firstWidthLineCount);
741 constraints.setTabStops(variableTabStops, TAB_INCREMENT);
742
Seigo Nonaka70200b02018-10-01 16:04:11 -0700743 LineBreaker.Result res = lineBreaker.computeLineBreaks(
744 measuredPara.getMeasuredText(), constraints, mLineCount);
Seigo Nonakaab9b4792018-09-24 17:37:15 -0700745 int breakCount = res.getLineCount();
746 if (lineBreakCapacity < breakCount) {
747 lineBreakCapacity = breakCount;
748 breaks = new int[lineBreakCapacity];
749 lineWidths = new float[lineBreakCapacity];
750 ascents = new float[lineBreakCapacity];
751 descents = new float[lineBreakCapacity];
752 hasTabs = new boolean[lineBreakCapacity];
753 hyphenEdits = new int[lineBreakCapacity];
754 }
755
756 for (int i = 0; i < breakCount; ++i) {
757 breaks[i] = res.getLineBreakOffset(i);
758 lineWidths[i] = res.getLineWidth(i);
759 ascents[i] = res.getLineAscent(i);
760 descents[i] = res.getLineDescent(i);
761 hasTabs[i] = res.hasLineTab(i);
762 hyphenEdits[i] = res.getLineHyphenEdit(i);
763 }
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700764
765 final int remainingLineCount = mMaximumVisibleLineCount - mLineCount;
766 final boolean ellipsisMayBeApplied = ellipsize != null
767 && (ellipsize == TextUtils.TruncateAt.END
768 || (mMaximumVisibleLineCount == 1
769 && ellipsize != TextUtils.TruncateAt.MARQUEE));
770 if (0 < remainingLineCount && remainingLineCount < breakCount
771 && ellipsisMayBeApplied) {
772 // Calculate width
773 float width = 0;
Seigo Nonakaab9b4792018-09-24 17:37:15 -0700774 boolean hasTab = false; // XXX May need to also have starting hyphen edit
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700775 for (int i = remainingLineCount - 1; i < breakCount; i++) {
776 if (i == breakCount - 1) {
777 width += lineWidths[i];
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700778 } else {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700779 for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) {
Seigo Nonaka5783c132018-10-15 17:35:56 -0700780 width += measuredPara.getCharWidthAt(j);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700781 }
Mark Wagner7b5676e2009-10-16 11:44:23 -0700782 }
Seigo Nonakaab9b4792018-09-24 17:37:15 -0700783 hasTab |= hasTabs[i];
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700784 }
785 // Treat the last line and overflowed lines as a single line.
786 breaks[remainingLineCount - 1] = breaks[breakCount - 1];
787 lineWidths[remainingLineCount - 1] = width;
Seigo Nonakaab9b4792018-09-24 17:37:15 -0700788 hasTabs[remainingLineCount - 1] = hasTab;
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700789
790 breakCount = remainingLineCount;
791 }
792
793 // here is the offset of the starting character of the line we are currently
794 // measuring
795 int here = paraStart;
796
797 int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
798 int fmCacheIndex = 0;
799 int spanEndCacheIndex = 0;
800 int breakIndex = 0;
801 for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
802 // retrieve end of span
803 spanEnd = spanEndCache[spanEndCacheIndex++];
804
805 // retrieve cached metrics, order matches above
806 fm.top = fmCache[fmCacheIndex * 4 + 0];
807 fm.bottom = fmCache[fmCacheIndex * 4 + 1];
808 fm.ascent = fmCache[fmCacheIndex * 4 + 2];
809 fm.descent = fmCache[fmCacheIndex * 4 + 3];
810 fmCacheIndex++;
811
812 if (fm.top < fmTop) {
813 fmTop = fm.top;
814 }
815 if (fm.ascent < fmAscent) {
816 fmAscent = fm.ascent;
817 }
818 if (fm.descent > fmDescent) {
819 fmDescent = fm.descent;
820 }
821 if (fm.bottom > fmBottom) {
822 fmBottom = fm.bottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800823 }
824
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700825 // skip breaks ending before current span range
826 while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
827 breakIndex++;
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700828 }
829
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700830 while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
831 int endPos = paraStart + breaks[breakIndex];
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800832
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700833 boolean moreChars = (endPos < bufEnd);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700834
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700835 final int ascent = fallbackLineSpacing
836 ? Math.min(fmAscent, Math.round(ascents[breakIndex]))
837 : fmAscent;
838 final int descent = fallbackLineSpacing
839 ? Math.max(fmDescent, Math.round(descents[breakIndex]))
840 : fmDescent;
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700841
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700842 v = out(source, here, endPos,
843 ascent, descent, fmTop, fmBottom,
844 v, spacingmult, spacingadd, chooseHt, chooseHtv, fm,
Seigo Nonakaab9b4792018-09-24 17:37:15 -0700845 hasTabs[breakIndex], hyphenEdits[breakIndex], needMultiply,
846 measuredPara, bufEnd, includepad, trackpad, addLastLineSpacing, chs,
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700847 paraStart, ellipsize, ellipsizedWidth, lineWidths[breakIndex],
848 paint, moreChars);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700849
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700850 if (endPos < spanEnd) {
851 // preserve metrics for current span
Anish Athalyec8f9e622014-07-21 15:26:34 -0700852 fmTop = fm.top;
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700853 fmBottom = fm.bottom;
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700854 fmAscent = fm.ascent;
855 fmDescent = fm.descent;
856 } else {
857 fmTop = fmBottom = fmAscent = fmDescent = 0;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700858 }
859
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700860 here = endPos;
861 breakIndex++;
862
863 if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) {
864 return;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700865 }
866 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800867 }
868
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700869 if (paraEnd == bufEnd) {
870 break;
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700871 }
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700872 }
873
874 if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE)
875 && mLineCount < mMaximumVisibleLineCount) {
876 final MeasuredParagraph measuredPara =
877 MeasuredParagraph.buildForBidi(source, bufEnd, bufEnd, textDir, null);
878 paint.getFontMetricsInt(fm);
879 v = out(source,
880 bufEnd, bufEnd, fm.ascent, fm.descent,
881 fm.top, fm.bottom,
882 v,
883 spacingmult, spacingadd, null,
Seigo Nonakaab9b4792018-09-24 17:37:15 -0700884 null, fm, false, 0,
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700885 needMultiply, measuredPara, bufEnd,
886 includepad, trackpad, addLastLineSpacing, null,
887 bufStart, ellipsize,
888 ellipsizedWidth, 0, paint, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800889 }
890 }
891
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700892 private int out(final CharSequence text, final int start, final int end, int above, int below,
893 int top, int bottom, int v, final float spacingmult, final float spacingadd,
894 final LineHeightSpan[] chooseHt, final int[] chooseHtv, final Paint.FontMetricsInt fm,
Seigo Nonakaab9b4792018-09-24 17:37:15 -0700895 final boolean hasTab, final int hyphenEdit, final boolean needMultiply,
896 @NonNull final MeasuredParagraph measured,
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800897 final int bufEnd, final boolean includePad, final boolean trackPad,
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700898 final boolean addLastLineLineSpacing, final char[] chs,
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700899 final int widthStart, final TextUtils.TruncateAt ellipsize, final float ellipsisWidth,
900 final float textWidth, final TextPaint paint, final boolean moreChars) {
Siyamed Siniraf398512017-07-25 19:08:42 -0700901 final int j = mLineCount;
902 final int off = j * mColumns;
903 final int want = off + mColumns + TOP;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800904 int[] lines = mLines;
Seigo Nonaka4b6bcee2017-12-11 11:39:51 -0800905 final int dir = measured.getParagraphDir();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800906
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800907 if (want >= lines.length) {
Siyamed Siniraf398512017-07-25 19:08:42 -0700908 final int[] grow = ArrayUtils.newUnpaddedIntArray(GrowingArrayUtils.growSize(want));
Adam Lesinski776abc22014-03-07 11:30:59 -0500909 System.arraycopy(lines, 0, grow, 0, lines.length);
910 mLines = grow;
911 lines = grow;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800912 }
913
Siyamed Siniraf398512017-07-25 19:08:42 -0700914 if (j >= mLineDirections.length) {
915 final Directions[] grow = ArrayUtils.newUnpaddedArray(Directions.class,
916 GrowingArrayUtils.growSize(j));
917 System.arraycopy(mLineDirections, 0, grow, 0, mLineDirections.length);
918 mLineDirections = grow;
919 }
920
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800921 if (chooseHt != null) {
922 fm.ascent = above;
923 fm.descent = below;
924 fm.top = top;
925 fm.bottom = bottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800926
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800927 for (int i = 0; i < chooseHt.length; i++) {
928 if (chooseHt[i] instanceof LineHeightSpan.WithDensity) {
929 ((LineHeightSpan.WithDensity) chooseHt[i])
930 .chooseHeight(text, start, end, chooseHtv[i], v, fm, paint);
931 } else {
932 chooseHt[i].chooseHeight(text, start, end, chooseHtv[i], v, fm);
933 }
934 }
Eric Fischera9f1dd02009-08-12 15:00:10 -0700935
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800936 above = fm.ascent;
937 below = fm.descent;
938 top = fm.top;
939 bottom = fm.bottom;
940 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800941
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800942 boolean firstLine = (j == 0);
943 boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);
Siyamed Sinir0745c722016-05-31 20:39:33 -0700944
945 if (ellipsize != null) {
946 // If there is only one line, then do any type of ellipsis except when it is MARQUEE
947 // if there are multiple lines, just allow END ellipsis on the last line
948 boolean forceEllipsis = moreChars && (mLineCount + 1 == mMaximumVisibleLineCount);
949
950 boolean doEllipsis =
951 (((mMaximumVisibleLineCount == 1 && moreChars) || (firstLine && !moreChars)) &&
952 ellipsize != TextUtils.TruncateAt.MARQUEE) ||
953 (!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
954 ellipsize == TextUtils.TruncateAt.END);
955 if (doEllipsis) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -0700956 calculateEllipsis(start, end, measured, widthStart,
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800957 ellipsisWidth, ellipsize, j,
958 textWidth, paint, forceEllipsis);
Siyamed Sinir0745c722016-05-31 20:39:33 -0700959 }
960 }
961
Siyamed Sinirfb0b2dc2017-07-26 22:08:24 -0700962 final boolean lastLine;
963 if (mEllipsized) {
964 lastLine = true;
965 } else {
966 final boolean lastCharIsNewLine = widthStart != bufEnd && bufEnd > 0
967 && text.charAt(bufEnd - 1) == CHAR_NEW_LINE;
968 if (end == bufEnd && !lastCharIsNewLine) {
969 lastLine = true;
970 } else if (start == bufEnd && lastCharIsNewLine) {
971 lastLine = true;
972 } else {
973 lastLine = false;
974 }
975 }
Raph Leviend97b0972014-04-24 12:51:35 -0700976
977 if (firstLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800978 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800979 mTopPadding = top - above;
980 }
981
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800982 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800983 above = top;
984 }
985 }
Raph Leviend97b0972014-04-24 12:51:35 -0700986
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800987 int extra;
988
Raph Leviend97b0972014-04-24 12:51:35 -0700989 if (lastLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800990 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800991 mBottomPadding = bottom - below;
992 }
993
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800994 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800995 below = bottom;
996 }
997 }
998
Siyamed Sinir442c1512017-07-24 12:18:27 -0700999 if (needMultiply && (addLastLineLineSpacing || !lastLine)) {
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001000 double ex = (below - above) * (spacingmult - 1) + spacingadd;
Doug Felt10657582010-02-22 11:19:01 -08001001 if (ex >= 0) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001002 extra = (int)(ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -08001003 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001004 extra = -(int)(-ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -08001005 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001006 } else {
1007 extra = 0;
1008 }
1009
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001010 lines[off + START] = start;
1011 lines[off + TOP] = v;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001012 lines[off + DESCENT] = below + extra;
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001013 lines[off + EXTRA] = extra;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001014
Siyamed Sinir0745c722016-05-31 20:39:33 -07001015 // special case for non-ellipsized last visible line when maxLines is set
1016 // store the height as if it was ellipsized
1017 if (!mEllipsized && currentLineIsTheLastVisibleOne) {
1018 // below calculation as if it was the last line
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001019 int maxLineBelow = includePad ? bottom : below;
Siyamed Sinir0745c722016-05-31 20:39:33 -07001020 // similar to the calculation of v below, without the extra.
1021 mMaxLineHeight = v + (maxLineBelow - above);
1022 }
1023
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001024 v += (below - above) + extra;
1025 lines[off + mColumns + START] = end;
1026 lines[off + mColumns + TOP] = v;
1027
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001028 // TODO: could move TAB to share same column as HYPHEN, simplifying this code and gaining
1029 // one bit for start field
Seigo Nonakaab9b4792018-09-24 17:37:15 -07001030 lines[off + TAB] |= hasTab ? TAB_MASK : 0;
1031 lines[off + HYPHEN] = hyphenEdit;
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001032 lines[off + DIR] |= dir << DIR_SHIFT;
1033 mLineDirections[j] = measured.getDirections(start - widthStart, end - widthStart);
1034
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001035 mLineCount++;
1036 return v;
1037 }
1038
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001039 private void calculateEllipsis(int lineStart, int lineEnd,
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001040 MeasuredParagraph measured, int widthStart,
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001041 float avail, TextUtils.TruncateAt where,
1042 int line, float textWidth, TextPaint paint,
1043 boolean forceEllipsis) {
1044 avail -= getTotalInsets(line);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001045 if (textWidth <= avail && !forceEllipsis) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001046 // Everything fits!
1047 mLines[mColumns * line + ELLIPSIS_START] = 0;
1048 mLines[mColumns * line + ELLIPSIS_COUNT] = 0;
1049 return;
1050 }
1051
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001052 float ellipsisWidth = paint.measureText(TextUtils.getEllipsisString(where));
1053 int ellipsisStart = 0;
1054 int ellipsisCount = 0;
1055 int len = lineEnd - lineStart;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001056
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001057 // We only support start ellipsis on a single line
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001058 if (where == TextUtils.TruncateAt.START) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001059 if (mMaximumVisibleLineCount == 1) {
1060 float sum = 0;
1061 int i;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001062
Keisuke Kuroyanagied2eea12015-04-14 18:18:35 +09001063 for (i = len; i > 0; i--) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001064 float w = measured.getCharWidthAt(i - 1 + lineStart - widthStart);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001065 if (w + sum + ellipsisWidth > avail) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001066 while (i < len
1067 && measured.getCharWidthAt(i + lineStart - widthStart) == 0.0f) {
Keisuke Kuroyanagi82d1c442016-09-14 14:30:14 +09001068 i++;
1069 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001070 break;
1071 }
1072
1073 sum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001074 }
1075
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001076 ellipsisStart = 0;
1077 ellipsisCount = i;
1078 } else {
1079 if (Log.isLoggable(TAG, Log.WARN)) {
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001080 Log.w(TAG, "Start Ellipsis only supported with one line");
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001081 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001082 }
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001083 } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE ||
1084 where == TextUtils.TruncateAt.END_SMALL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001085 float sum = 0;
1086 int i;
1087
1088 for (i = 0; i < len; i++) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001089 float w = measured.getCharWidthAt(i + lineStart - widthStart);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001090
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001091 if (w + sum + ellipsisWidth > avail) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001092 break;
1093 }
1094
1095 sum += w;
1096 }
1097
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001098 ellipsisStart = i;
1099 ellipsisCount = len - i;
1100 if (forceEllipsis && ellipsisCount == 0 && len > 0) {
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -07001101 ellipsisStart = len - 1;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001102 ellipsisCount = 1;
1103 }
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001104 } else {
1105 // where = TextUtils.TruncateAt.MIDDLE We only support middle ellipsis on a single line
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001106 if (mMaximumVisibleLineCount == 1) {
1107 float lsum = 0, rsum = 0;
1108 int left = 0, right = len;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001109
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001110 float ravail = (avail - ellipsisWidth) / 2;
Raph Levien0e3c5e82014-12-04 13:26:07 -08001111 for (right = len; right > 0; right--) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001112 float w = measured.getCharWidthAt(right - 1 + lineStart - widthStart);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001113
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001114 if (w + rsum > ravail) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001115 while (right < len
1116 && measured.getCharWidthAt(right + lineStart - widthStart)
1117 == 0.0f) {
Keisuke Kuroyanagi82d1c442016-09-14 14:30:14 +09001118 right++;
1119 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001120 break;
1121 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001122 rsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001123 }
1124
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001125 float lavail = avail - ellipsisWidth - rsum;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001126 for (left = 0; left < right; left++) {
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001127 float w = measured.getCharWidthAt(left + lineStart - widthStart);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001128
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001129 if (w + lsum > lavail) {
1130 break;
1131 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001132
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001133 lsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001134 }
1135
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001136 ellipsisStart = left;
1137 ellipsisCount = right - left;
1138 } else {
1139 if (Log.isLoggable(TAG, Log.WARN)) {
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001140 Log.w(TAG, "Middle Ellipsis only supported with one line");
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001141 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001142 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001143 }
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001144 mEllipsized = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001145 mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
1146 mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
1147 }
1148
Selim Cinek365ec092017-03-09 00:10:52 -08001149 private float getTotalInsets(int line) {
1150 int totalIndent = 0;
1151 if (mLeftIndents != null) {
1152 totalIndent = mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1153 }
1154 if (mRightIndents != null) {
1155 totalIndent += mRightIndents[Math.min(line, mRightIndents.length - 1)];
1156 }
1157 return totalIndent;
1158 }
1159
Doug Felte8e45f22010-03-29 14:58:40 -07001160 // Override the base class so we can directly access our members,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001161 // rather than relying on member functions.
1162 // The logic mirrors that of Layout.getLineForVertical
1163 // FIXME: It may be faster to do a linear search for layouts without many lines.
Gilles Debunne66111472010-11-19 11:04:37 -08001164 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001165 public int getLineForVertical(int vertical) {
1166 int high = mLineCount;
1167 int low = -1;
1168 int guess;
1169 int[] lines = mLines;
1170 while (high - low > 1) {
1171 guess = (high + low) >> 1;
1172 if (lines[mColumns * guess + TOP] > vertical){
1173 high = guess;
1174 } else {
1175 low = guess;
1176 }
1177 }
1178 if (low < 0) {
1179 return 0;
1180 } else {
1181 return low;
1182 }
1183 }
1184
Gilles Debunne66111472010-11-19 11:04:37 -08001185 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001186 public int getLineCount() {
1187 return mLineCount;
1188 }
1189
Gilles Debunne66111472010-11-19 11:04:37 -08001190 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001191 public int getLineTop(int line) {
Raph Levien07e6c232016-04-04 12:34:06 -07001192 return mLines[mColumns * line + TOP];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001193 }
1194
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001195 /**
1196 * @hide
1197 */
1198 @Override
1199 public int getLineExtra(int line) {
1200 return mLines[mColumns * line + EXTRA];
1201 }
1202
Gilles Debunne66111472010-11-19 11:04:37 -08001203 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001204 public int getLineDescent(int line) {
Raph Levien07e6c232016-04-04 12:34:06 -07001205 return mLines[mColumns * line + DESCENT];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001206 }
1207
Gilles Debunne66111472010-11-19 11:04:37 -08001208 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001209 public int getLineStart(int line) {
1210 return mLines[mColumns * line + START] & START_MASK;
1211 }
1212
Gilles Debunne66111472010-11-19 11:04:37 -08001213 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001214 public int getParagraphDirection(int line) {
1215 return mLines[mColumns * line + DIR] >> DIR_SHIFT;
1216 }
1217
Gilles Debunne66111472010-11-19 11:04:37 -08001218 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001219 public boolean getLineContainsTab(int line) {
1220 return (mLines[mColumns * line + TAB] & TAB_MASK) != 0;
1221 }
1222
Gilles Debunne66111472010-11-19 11:04:37 -08001223 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001224 public final Directions getLineDirections(int line) {
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001225 if (line > getLineCount()) {
1226 throw new ArrayIndexOutOfBoundsException();
1227 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001228 return mLineDirections[line];
1229 }
1230
Gilles Debunne66111472010-11-19 11:04:37 -08001231 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001232 public int getTopPadding() {
1233 return mTopPadding;
1234 }
1235
Gilles Debunne66111472010-11-19 11:04:37 -08001236 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001237 public int getBottomPadding() {
1238 return mBottomPadding;
1239 }
1240
Raph Levien26d443a2015-03-30 14:18:32 -07001241 /**
Seigo Nonaka9033e0c2018-09-06 18:42:10 -07001242 * Returns the packed hyphen edit value for this line.
1243 *
1244 * You can extract start hyphen edit and end hyphen edit by using
1245 * {@link Hyphenator#unpackStartHyphenEdit(int)} and
1246 * {@link Hyphenator#unpackEndHyphenEdit(int)}.
1247 *
1248 * @param lineNumber a line number
1249 * @return A packed hyphen edit value.
Raph Levien26d443a2015-03-30 14:18:32 -07001250 * @hide
1251 */
1252 @Override
Seigo Nonaka9033e0c2018-09-06 18:42:10 -07001253 public int getHyphen(int lineNumber) {
1254 return mLines[mColumns * lineNumber + HYPHEN] & HYPHEN_MASK;
Raph Levien26d443a2015-03-30 14:18:32 -07001255 }
1256
Raph Levien2ea52902015-07-01 14:39:31 -07001257 /**
1258 * @hide
1259 */
1260 @Override
1261 public int getIndentAdjust(int line, Alignment align) {
1262 if (align == Alignment.ALIGN_LEFT) {
1263 if (mLeftIndents == null) {
1264 return 0;
1265 } else {
1266 return mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1267 }
1268 } else if (align == Alignment.ALIGN_RIGHT) {
1269 if (mRightIndents == null) {
1270 return 0;
1271 } else {
1272 return -mRightIndents[Math.min(line, mRightIndents.length - 1)];
1273 }
1274 } else if (align == Alignment.ALIGN_CENTER) {
1275 int left = 0;
1276 if (mLeftIndents != null) {
1277 left = mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1278 }
1279 int right = 0;
1280 if (mRightIndents != null) {
1281 right = mRightIndents[Math.min(line, mRightIndents.length - 1)];
1282 }
1283 return (left - right) >> 1;
1284 } else {
1285 throw new AssertionError("unhandled alignment " + align);
1286 }
1287 }
1288
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001289 @Override
1290 public int getEllipsisCount(int line) {
1291 if (mColumns < COLUMNS_ELLIPSIZE) {
1292 return 0;
1293 }
1294
1295 return mLines[mColumns * line + ELLIPSIS_COUNT];
1296 }
1297
1298 @Override
1299 public int getEllipsisStart(int line) {
1300 if (mColumns < COLUMNS_ELLIPSIZE) {
1301 return 0;
1302 }
1303
1304 return mLines[mColumns * line + ELLIPSIS_START];
1305 }
1306
1307 @Override
1308 public int getEllipsizedWidth() {
1309 return mEllipsizedWidth;
1310 }
1311
Siyamed Sinir0745c722016-05-31 20:39:33 -07001312 /**
1313 * Return the total height of this layout.
1314 *
1315 * @param cap if true and max lines is set, returns the height of the layout at the max lines.
1316 *
1317 * @hide
1318 */
Mathew Inwood8c854f82018-09-14 12:35:36 +01001319 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
Siyamed Sinir0745c722016-05-31 20:39:33 -07001320 public int getHeight(boolean cap) {
Siyamed Sinir70ffd282018-03-08 17:19:46 -08001321 if (cap && mLineCount > mMaximumVisibleLineCount && mMaxLineHeight == -1
1322 && Log.isLoggable(TAG, Log.WARN)) {
Siyamed Sinir0745c722016-05-31 20:39:33 -07001323 Log.w(TAG, "maxLineHeight should not be -1. "
1324 + " maxLines:" + mMaximumVisibleLineCount
1325 + " lineCount:" + mLineCount);
1326 }
1327
Siyamed Sinir70ffd282018-03-08 17:19:46 -08001328 return cap && mLineCount > mMaximumVisibleLineCount && mMaxLineHeight != -1
1329 ? mMaxLineHeight : super.getHeight();
Siyamed Sinir0745c722016-05-31 20:39:33 -07001330 }
1331
Mathew Inwoodefeab842018-08-14 15:21:30 +01001332 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001333 private int mLineCount;
1334 private int mTopPadding, mBottomPadding;
Mathew Inwoodefeab842018-08-14 15:21:30 +01001335 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001336 private int mColumns;
1337 private int mEllipsizedWidth;
1338
Siyamed Sinir0745c722016-05-31 20:39:33 -07001339 /**
1340 * Keeps track if ellipsize is applied to the text.
1341 */
1342 private boolean mEllipsized;
1343
1344 /**
1345 * If maxLines is set, ellipsize is not set, and the actual line count of text is greater than
1346 * or equal to maxLine, this variable holds the ideal visual height of the maxLine'th line
1347 * starting from the top of the layout. If maxLines is not set its value will be -1.
1348 *
1349 * The value is the same as getLineTop(maxLines) for ellipsized version where structurally no
1350 * more than maxLines is contained.
1351 */
Siyamed Sinira19cd512017-08-03 22:01:56 -07001352 private int mMaxLineHeight = DEFAULT_MAX_LINE_HEIGHT;
Siyamed Sinir0745c722016-05-31 20:39:33 -07001353
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001354 private static final int COLUMNS_NORMAL = 5;
1355 private static final int COLUMNS_ELLIPSIZE = 7;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001356 private static final int START = 0;
1357 private static final int DIR = START;
1358 private static final int TAB = START;
1359 private static final int TOP = 1;
1360 private static final int DESCENT = 2;
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001361 private static final int EXTRA = 3;
1362 private static final int HYPHEN = 4;
Mathew Inwoodefeab842018-08-14 15:21:30 +01001363 @UnsupportedAppUsage
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001364 private static final int ELLIPSIS_START = 5;
1365 private static final int ELLIPSIS_COUNT = 6;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001366
Mathew Inwoodefeab842018-08-14 15:21:30 +01001367 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001368 private int[] mLines;
Mathew Inwoodefeab842018-08-14 15:21:30 +01001369 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001370 private Directions[] mLineDirections;
Mathew Inwoodefeab842018-08-14 15:21:30 +01001371 @UnsupportedAppUsage
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001372 private int mMaximumVisibleLineCount = Integer.MAX_VALUE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001373
1374 private static final int START_MASK = 0x1FFFFFFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001375 private static final int DIR_SHIFT = 30;
1376 private static final int TAB_MASK = 0x20000000;
Keisuke Kuroyanagif5af4a32016-08-31 21:40:53 +09001377 private static final int HYPHEN_MASK = 0xFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001378
Doug Feltc982f602010-05-25 11:51:40 -07001379 private static final int TAB_INCREMENT = 20; // same as Layout, but that's private
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001380
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001381 private static final char CHAR_NEW_LINE = '\n';
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001382
1383 private static final double EXTRA_ROUNDING = 0.5;
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001384
Siyamed Sinira19cd512017-08-03 22:01:56 -07001385 private static final int DEFAULT_MAX_LINE_HEIGHT = -1;
1386
Seigo Nonaka6f11c6e2018-07-24 11:26:18 -07001387 // Unused, here because of gray list private API accesses.
Deepanshu Gupta70539192014-10-15 15:57:40 -07001388 /*package*/ static class LineBreaks {
Anish Athalyec8f9e622014-07-21 15:26:34 -07001389 private static final int INITIAL_SIZE = 16;
Mathew Inwoodefeab842018-08-14 15:21:30 +01001390 @UnsupportedAppUsage
Anish Athalyec8f9e622014-07-21 15:26:34 -07001391 public int[] breaks = new int[INITIAL_SIZE];
Mathew Inwoodefeab842018-08-14 15:21:30 +01001392 @UnsupportedAppUsage
Anish Athalyec8f9e622014-07-21 15:26:34 -07001393 public float[] widths = new float[INITIAL_SIZE];
Mathew Inwoodefeab842018-08-14 15:21:30 +01001394 @UnsupportedAppUsage
Roozbeh Pournader737dfea2017-08-10 11:32:24 -07001395 public float[] ascents = new float[INITIAL_SIZE];
Mathew Inwoodefeab842018-08-14 15:21:30 +01001396 @UnsupportedAppUsage
Roozbeh Pournader737dfea2017-08-10 11:32:24 -07001397 public float[] descents = new float[INITIAL_SIZE];
Mathew Inwoodefeab842018-08-14 15:21:30 +01001398 @UnsupportedAppUsage
Roozbeh Pournader112d9c72015-08-07 12:44:41 -07001399 public int[] flags = new int[INITIAL_SIZE]; // hasTab
Anish Athalyec8f9e622014-07-21 15:26:34 -07001400 // breaks, widths, and flags should all have the same length
1401 }
1402
Seigo Nonakabafe1972017-08-24 15:30:29 -07001403 @Nullable private int[] mLeftIndents;
1404 @Nullable private int[] mRightIndents;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001405}