blob: f25f93ef2cea295a846e428d06979582ae9a22b7 [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;
Seigo Nonakaf1644f72017-11-27 22:09:49 -080024import android.text.AutoGrowArray.FloatArray;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080025import android.text.style.LeadingMarginSpan;
Gilles Debunne66111472010-11-19 11:04:37 -080026import android.text.style.LeadingMarginSpan.LeadingMarginSpan2;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080027import android.text.style.LineHeightSpan;
Doug Feltc982f602010-05-25 11:51:40 -070028import android.text.style.TabStopSpan;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -070029import android.util.Log;
Raph Levien39b4db72015-03-25 13:18:20 -070030import android.util.Pools.SynchronizedPool;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080031
Doug Feltcb3791202011-07-07 11:57:48 -070032import com.android.internal.util.ArrayUtils;
Adam Lesinski776abc22014-03-07 11:30:59 -050033import com.android.internal.util.GrowingArrayUtils;
Doug Feltcb3791202011-07-07 11:57:48 -070034
Seigo Nonakab90e08f2017-10-20 15:23:51 -070035import dalvik.annotation.optimization.CriticalNative;
36import dalvik.annotation.optimization.FastNative;
37
Anish Athalyec8f9e622014-07-21 15:26:34 -070038import java.util.Arrays;
39
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080040/**
41 * StaticLayout is a Layout for text that will not be edited after it
42 * is laid out. Use {@link DynamicLayout} for text that may change.
43 * <p>This is used by widgets to control text layout. You should not need
44 * to use this class directly unless you are implementing your own widget
45 * or custom display object, or would be tempted to call
Doug Felt4e0c5e52010-03-15 16:56:02 -070046 * {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int,
47 * float, float, android.graphics.Paint)
48 * Canvas.drawText()} directly.</p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080049 */
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -080050public class StaticLayout extends Layout {
Seigo Nonaka6f1868b2017-12-01 16:24:19 -080051 /*
52 * The break iteration is done in native code. The protocol for using the native code is as
53 * follows.
54 *
55 * First, call nInit to setup native line breaker object. Then, for each paragraph, do the
56 * following:
57 *
Seigo Nonaka9d3bd082018-01-11 10:02:12 -080058 * - Create MeasuredParagraph by MeasuredParagraph.buildForStaticLayout which measures in
59 * native.
Seigo Nonaka6f1868b2017-12-01 16:24:19 -080060 * - Run nComputeLineBreaks() to obtain line breaks for the paragraph.
61 *
62 * After all paragraphs, call finish() to release expensive buffers.
63 */
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -080064
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -070065 static final String TAG = "StaticLayout";
66
Raph Leviend3ab6922015-03-02 14:30:53 -080067 /**
Roozbeh Pournader22a167c2017-08-21 12:53:44 -070068 * Builder for static layouts. The builder is the preferred pattern for constructing
69 * StaticLayout objects and should be preferred over the constructors, particularly to access
70 * newer features. To build a static layout, first call {@link #obtain} with the required
71 * arguments (text, paint, and width), then call setters for optional parameters, and finally
72 * {@link #build} to build the StaticLayout object. Parameters not explicitly set will get
Raph Levien531c30c2015-04-30 16:29:59 -070073 * default values.
Raph Leviend3ab6922015-03-02 14:30:53 -080074 */
75 public final static class Builder {
Seigo Nonakab90e08f2017-10-20 15:23:51 -070076 private Builder() {}
Raph Levien4c1f12e2015-03-02 16:29:23 -080077
Raph Levien531c30c2015-04-30 16:29:59 -070078 /**
Roozbeh Pournader22a167c2017-08-21 12:53:44 -070079 * Obtain a builder for constructing StaticLayout objects.
Raph Levien531c30c2015-04-30 16:29:59 -070080 *
81 * @param source The text to be laid out, optionally with spans
82 * @param start The index of the start of the text
83 * @param end The index + 1 of the end of the text
84 * @param paint The base paint used for layout
85 * @param width The width in pixels
86 * @return a builder object used for constructing the StaticLayout
87 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -070088 @NonNull
89 public static Builder obtain(@NonNull CharSequence source, @IntRange(from = 0) int start,
90 @IntRange(from = 0) int end, @NonNull TextPaint paint,
91 @IntRange(from = 0) int width) {
Raph Levien39b4db72015-03-25 13:18:20 -070092 Builder b = sPool.acquire();
Raph Leviend3ab6922015-03-02 14:30:53 -080093 if (b == null) {
94 b = new Builder();
95 }
96
97 // set default initial values
Raph Levien39b4db72015-03-25 13:18:20 -070098 b.mText = source;
99 b.mStart = start;
100 b.mEnd = end;
Raph Levienebd66ca2015-04-30 15:27:57 -0700101 b.mPaint = paint;
Raph Levien39b4db72015-03-25 13:18:20 -0700102 b.mWidth = width;
103 b.mAlignment = Alignment.ALIGN_NORMAL;
Raph Leviend3ab6922015-03-02 14:30:53 -0800104 b.mTextDir = TextDirectionHeuristics.FIRSTSTRONG_LTR;
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700105 b.mSpacingMult = DEFAULT_LINESPACING_MULTIPLIER;
106 b.mSpacingAdd = DEFAULT_LINESPACING_ADDITION;
Raph Leviend3ab6922015-03-02 14:30:53 -0800107 b.mIncludePad = true;
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700108 b.mFallbackLineSpacing = false;
Raph Levien39b4db72015-03-25 13:18:20 -0700109 b.mEllipsizedWidth = width;
Raph Leviend3ab6922015-03-02 14:30:53 -0800110 b.mEllipsize = null;
111 b.mMaxLines = Integer.MAX_VALUE;
Raph Levien3bd60c72015-05-06 14:26:35 -0700112 b.mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700113 b.mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700114 b.mJustificationMode = Layout.JUSTIFICATION_MODE_NONE;
Raph Leviend3ab6922015-03-02 14:30:53 -0800115 return b;
116 }
117
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700118 /**
119 * This method should be called after the layout is finished getting constructed and the
120 * builder needs to be cleaned up and returned to the pool.
121 */
122 private static void recycle(@NonNull Builder b) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800123 b.mPaint = null;
124 b.mText = null;
Raph Levien2ea52902015-07-01 14:39:31 -0700125 b.mLeftIndents = null;
126 b.mRightIndents = null;
Raph Levien39b4db72015-03-25 13:18:20 -0700127 sPool.release(b);
Raph Leviend3ab6922015-03-02 14:30:53 -0800128 }
129
130 // release any expensive state
131 /* package */ void finish() {
Raph Levien22ba7862015-07-29 12:34:13 -0700132 mText = null;
133 mPaint = null;
134 mLeftIndents = null;
135 mRightIndents = null;
Raph Leviend3ab6922015-03-02 14:30:53 -0800136 }
137
138 public Builder setText(CharSequence source) {
139 return setText(source, 0, source.length());
140 }
141
Raph Levien531c30c2015-04-30 16:29:59 -0700142 /**
143 * Set the text. Only useful when re-using the builder, which is done for
144 * the internal implementation of {@link DynamicLayout} but not as part
145 * of normal {@link StaticLayout} usage.
146 *
147 * @param source The text to be laid out, optionally with spans
148 * @param start The index of the start of the text
149 * @param end The index + 1 of the end of the text
150 * @return this builder, useful for chaining
151 *
152 * @hide
153 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700154 @NonNull
155 public Builder setText(@NonNull CharSequence source, int start, int end) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800156 mText = source;
157 mStart = start;
158 mEnd = end;
159 return this;
160 }
161
Raph Levien531c30c2015-04-30 16:29:59 -0700162 /**
163 * Set the paint. Internal for reuse cases only.
164 *
165 * @param paint The base paint used for layout
166 * @return this builder, useful for chaining
167 *
168 * @hide
169 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700170 @NonNull
171 public Builder setPaint(@NonNull TextPaint paint) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800172 mPaint = paint;
173 return this;
174 }
175
Raph Levien531c30c2015-04-30 16:29:59 -0700176 /**
177 * Set the width. Internal for reuse cases only.
178 *
179 * @param width The width in pixels
180 * @return this builder, useful for chaining
181 *
182 * @hide
183 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700184 @NonNull
185 public Builder setWidth(@IntRange(from = 0) int width) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800186 mWidth = width;
187 if (mEllipsize == null) {
188 mEllipsizedWidth = width;
189 }
190 return this;
191 }
192
Raph Levien531c30c2015-04-30 16:29:59 -0700193 /**
194 * Set the alignment. The default is {@link Layout.Alignment#ALIGN_NORMAL}.
195 *
196 * @param alignment Alignment for the resulting {@link StaticLayout}
197 * @return this builder, useful for chaining
198 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700199 @NonNull
200 public Builder setAlignment(@NonNull Alignment alignment) {
Raph Levien39b4db72015-03-25 13:18:20 -0700201 mAlignment = alignment;
202 return this;
203 }
204
Raph Levien531c30c2015-04-30 16:29:59 -0700205 /**
206 * Set the text direction heuristic. The text direction heuristic is used to
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700207 * resolve text direction per-paragraph based on the input text. The default is
Raph Levien531c30c2015-04-30 16:29:59 -0700208 * {@link TextDirectionHeuristics#FIRSTSTRONG_LTR}.
209 *
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700210 * @param textDir text direction heuristic for resolving bidi behavior.
Raph Levien531c30c2015-04-30 16:29:59 -0700211 * @return this builder, useful for chaining
212 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700213 @NonNull
214 public Builder setTextDirection(@NonNull TextDirectionHeuristic textDir) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800215 mTextDir = textDir;
216 return this;
217 }
218
Raph Levien531c30c2015-04-30 16:29:59 -0700219 /**
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700220 * Set line spacing parameters. Each line will have its line spacing multiplied by
221 * {@code spacingMult} and then increased by {@code spacingAdd}. The default is 0.0 for
222 * {@code spacingAdd} and 1.0 for {@code spacingMult}.
Raph Levien531c30c2015-04-30 16:29:59 -0700223 *
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700224 * @param spacingAdd the amount of line spacing addition
225 * @param spacingMult the line spacing multiplier
Raph Levien531c30c2015-04-30 16:29:59 -0700226 * @return this builder, useful for chaining
227 * @see android.widget.TextView#setLineSpacing
228 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700229 @NonNull
230 public Builder setLineSpacing(float spacingAdd, @FloatRange(from = 0.0) float spacingMult) {
Raph Levien531c30c2015-04-30 16:29:59 -0700231 mSpacingAdd = spacingAdd;
Raph Leviend3ab6922015-03-02 14:30:53 -0800232 mSpacingMult = spacingMult;
233 return this;
234 }
235
Raph Levien531c30c2015-04-30 16:29:59 -0700236 /**
237 * Set whether to include extra space beyond font ascent and descent (which is
238 * needed to avoid clipping in some languages, such as Arabic and Kannada). The
239 * default is {@code true}.
240 *
241 * @param includePad whether to include padding
242 * @return this builder, useful for chaining
243 * @see android.widget.TextView#setIncludeFontPadding
244 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700245 @NonNull
Raph Leviend3ab6922015-03-02 14:30:53 -0800246 public Builder setIncludePad(boolean includePad) {
247 mIncludePad = includePad;
248 return this;
249 }
250
Raph Levien531c30c2015-04-30 16:29:59 -0700251 /**
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700252 * Set whether to respect the ascent and descent of the fallback fonts that are used in
253 * displaying the text (which is needed to avoid text from consecutive lines running into
254 * each other). If set, fallback fonts that end up getting used can increase the ascent
255 * and descent of the lines that they are used on.
256 *
257 * <p>For backward compatibility reasons, the default is {@code false}, but setting this to
258 * true is strongly recommended. It is required to be true if text could be in languages
259 * like Burmese or Tibetan where text is typically much taller or deeper than Latin text.
260 *
261 * @param useLineSpacingFromFallbacks whether to expand linespacing based on fallback fonts
262 * @return this builder, useful for chaining
263 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700264 @NonNull
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700265 public Builder setUseLineSpacingFromFallbacks(boolean useLineSpacingFromFallbacks) {
266 mFallbackLineSpacing = useLineSpacingFromFallbacks;
267 return this;
268 }
269
270 /**
Raph Levien531c30c2015-04-30 16:29:59 -0700271 * Set the width as used for ellipsizing purposes, if it differs from the
272 * normal layout width. The default is the {@code width}
273 * passed to {@link #obtain}.
274 *
275 * @param ellipsizedWidth width used for ellipsizing, in pixels
276 * @return this builder, useful for chaining
277 * @see android.widget.TextView#setEllipsize
278 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700279 @NonNull
280 public Builder setEllipsizedWidth(@IntRange(from = 0) int ellipsizedWidth) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800281 mEllipsizedWidth = ellipsizedWidth;
282 return this;
283 }
284
Raph Levien531c30c2015-04-30 16:29:59 -0700285 /**
286 * Set ellipsizing on the layout. Causes words that are longer than the view
287 * is wide, or exceeding the number of lines (see #setMaxLines) in the case
288 * of {@link android.text.TextUtils.TruncateAt#END} or
289 * {@link android.text.TextUtils.TruncateAt#MARQUEE}, to be ellipsized instead
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700290 * of broken. The default is {@code null}, indicating no ellipsis is to be applied.
Raph Levien531c30c2015-04-30 16:29:59 -0700291 *
292 * @param ellipsize type of ellipsis behavior
293 * @return this builder, useful for chaining
294 * @see android.widget.TextView#setEllipsize
295 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700296 @NonNull
Raph Levien531c30c2015-04-30 16:29:59 -0700297 public Builder setEllipsize(@Nullable TextUtils.TruncateAt ellipsize) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800298 mEllipsize = ellipsize;
299 return this;
300 }
301
Raph Levien531c30c2015-04-30 16:29:59 -0700302 /**
303 * Set maximum number of lines. This is particularly useful in the case of
304 * ellipsizing, where it changes the layout of the last line. The default is
305 * unlimited.
306 *
307 * @param maxLines maximum number of lines in the layout
308 * @return this builder, useful for chaining
309 * @see android.widget.TextView#setMaxLines
310 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700311 @NonNull
312 public Builder setMaxLines(@IntRange(from = 0) int maxLines) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800313 mMaxLines = maxLines;
314 return this;
315 }
316
Raph Levien531c30c2015-04-30 16:29:59 -0700317 /**
318 * Set break strategy, useful for selecting high quality or balanced paragraph
319 * layout options. The default is {@link Layout#BREAK_STRATEGY_SIMPLE}.
Siyamed Sinirc0dab362018-07-22 13:48:51 -0700320 * <p/>
321 * Enabling hyphenation with either using {@link Layout#HYPHENATION_FREQUENCY_NORMAL} or
322 * {@link Layout#HYPHENATION_FREQUENCY_FULL} while line breaking is set to one of
323 * {@link Layout#BREAK_STRATEGY_BALANCED}, {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}
324 * improves the structure of text layout however has performance impact and requires more
325 * time to do the text layout.
Raph Levien531c30c2015-04-30 16:29:59 -0700326 *
327 * @param breakStrategy break strategy for paragraph layout
328 * @return this builder, useful for chaining
329 * @see android.widget.TextView#setBreakStrategy
Siyamed Sinirc0dab362018-07-22 13:48:51 -0700330 * @see #setHyphenationFrequency(int)
Raph Levien531c30c2015-04-30 16:29:59 -0700331 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700332 @NonNull
Raph Levien39b4db72015-03-25 13:18:20 -0700333 public Builder setBreakStrategy(@BreakStrategy int breakStrategy) {
334 mBreakStrategy = breakStrategy;
335 return this;
336 }
337
Raph Levien531c30c2015-04-30 16:29:59 -0700338 /**
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700339 * Set hyphenation frequency, to control the amount of automatic hyphenation used. The
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700340 * possible values are defined in {@link Layout}, by constants named with the pattern
341 * {@code HYPHENATION_FREQUENCY_*}. The default is
342 * {@link Layout#HYPHENATION_FREQUENCY_NONE}.
Siyamed Sinirc0dab362018-07-22 13:48:51 -0700343 * <p/>
344 * Enabling hyphenation with either using {@link Layout#HYPHENATION_FREQUENCY_NORMAL} or
345 * {@link Layout#HYPHENATION_FREQUENCY_FULL} while line breaking is set to one of
346 * {@link Layout#BREAK_STRATEGY_BALANCED}, {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}
347 * improves the structure of text layout however has performance impact and requires more
348 * time to do the text layout.
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700349 *
350 * @param hyphenationFrequency hyphenation frequency for the paragraph
351 * @return this builder, useful for chaining
352 * @see android.widget.TextView#setHyphenationFrequency
Siyamed Sinirc0dab362018-07-22 13:48:51 -0700353 * @see #setBreakStrategy(int)
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700354 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700355 @NonNull
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700356 public Builder setHyphenationFrequency(@HyphenationFrequency int hyphenationFrequency) {
357 mHyphenationFrequency = hyphenationFrequency;
358 return this;
359 }
360
361 /**
Raph Levien531c30c2015-04-30 16:29:59 -0700362 * Set indents. Arguments are arrays holding an indent amount, one per line, measured in
363 * pixels. For lines past the last element in the array, the last element repeats.
364 *
365 * @param leftIndents array of indent values for left margin, in pixels
366 * @param rightIndents array of indent values for right margin, in pixels
367 * @return this builder, useful for chaining
Raph Levien531c30c2015-04-30 16:29:59 -0700368 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700369 @NonNull
370 public Builder setIndents(@Nullable int[] leftIndents, @Nullable int[] rightIndents) {
Raph Levien2ea52902015-07-01 14:39:31 -0700371 mLeftIndents = leftIndents;
372 mRightIndents = rightIndents;
Raph Leviene319d5a2015-04-14 23:51:07 -0700373 return this;
374 }
375
Raph Levien70616ec2015-03-04 10:41:30 -0800376 /**
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700377 * Set paragraph justification mode. The default value is
378 * {@link Layout#JUSTIFICATION_MODE_NONE}. If the last line is too short for justification,
379 * the last line will be displayed with the alignment set by {@link #setAlignment}.
Seigo Nonaka09da71a2016-11-28 16:24:14 +0900380 *
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700381 * @param justificationMode justification mode for the paragraph.
Seigo Nonaka09da71a2016-11-28 16:24:14 +0900382 * @return this builder, useful for chaining.
383 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700384 @NonNull
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700385 public Builder setJustificationMode(@JustificationMode int justificationMode) {
386 mJustificationMode = justificationMode;
Seigo Nonaka09da71a2016-11-28 16:24:14 +0900387 return this;
388 }
389
Siyamed Sinir442c1512017-07-24 12:18:27 -0700390 /**
391 * Sets whether the line spacing should be applied for the last line. Default value is
392 * {@code false}.
393 *
394 * @hide
395 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700396 @NonNull
Siyamed Sinir442c1512017-07-24 12:18:27 -0700397 /* package */ Builder setAddLastLineLineSpacing(boolean value) {
398 mAddLastLineLineSpacing = value;
399 return this;
400 }
401
Seigo Nonaka09da71a2016-11-28 16:24:14 +0900402 /**
Raph Levien531c30c2015-04-30 16:29:59 -0700403 * Build the {@link StaticLayout} after options have been set.
404 *
405 * <p>Note: the builder object must not be reused in any way after calling this
406 * method. Setting parameters after calling this method, or calling it a second
407 * time on the same builder object, will likely lead to unexpected results.
408 *
409 * @return the newly constructed {@link StaticLayout} object
410 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700411 @NonNull
Raph Leviend3ab6922015-03-02 14:30:53 -0800412 public StaticLayout build() {
Raph Levien39b4db72015-03-25 13:18:20 -0700413 StaticLayout result = new StaticLayout(this);
414 Builder.recycle(this);
Raph Leviend3ab6922015-03-02 14:30:53 -0800415 return result;
416 }
417
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700418 private CharSequence mText;
419 private int mStart;
420 private int mEnd;
421 private TextPaint mPaint;
422 private int mWidth;
423 private Alignment mAlignment;
424 private TextDirectionHeuristic mTextDir;
425 private float mSpacingMult;
426 private float mSpacingAdd;
427 private boolean mIncludePad;
428 private boolean mFallbackLineSpacing;
429 private int mEllipsizedWidth;
430 private TextUtils.TruncateAt mEllipsize;
431 private int mMaxLines;
432 private int mBreakStrategy;
433 private int mHyphenationFrequency;
Seigo Nonakabafe1972017-08-24 15:30:29 -0700434 @Nullable private int[] mLeftIndents;
435 @Nullable private int[] mRightIndents;
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700436 private int mJustificationMode;
437 private boolean mAddLastLineLineSpacing;
Raph Leviend3ab6922015-03-02 14:30:53 -0800438
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700439 private final Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
Raph Leviend3ab6922015-03-02 14:30:53 -0800440
Siyamed Sinira273a702017-10-05 11:22:12 -0700441 private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<>(3);
Raph Leviend3ab6922015-03-02 14:30:53 -0800442 }
443
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000444 /**
445 * @deprecated Use {@link Builder} instead.
446 */
447 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800448 public StaticLayout(CharSequence source, TextPaint paint,
449 int width,
450 Alignment align, float spacingmult, float spacingadd,
451 boolean includepad) {
452 this(source, 0, source.length(), paint, width, align,
453 spacingmult, spacingadd, includepad);
454 }
455
Doug Feltcb3791202011-07-07 11:57:48 -0700456 /**
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000457 * @deprecated Use {@link Builder} instead.
Doug Feltcb3791202011-07-07 11:57:48 -0700458 */
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000459 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800460 public StaticLayout(CharSequence source, int bufstart, int bufend,
461 TextPaint paint, int outerwidth,
462 Alignment align,
463 float spacingmult, float spacingadd,
464 boolean includepad) {
465 this(source, bufstart, bufend, paint, outerwidth, align,
466 spacingmult, spacingadd, includepad, null, 0);
467 }
468
Doug Feltcb3791202011-07-07 11:57:48 -0700469 /**
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000470 * @deprecated Use {@link Builder} instead.
Doug Feltcb3791202011-07-07 11:57:48 -0700471 */
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000472 @Deprecated
Doug Feltcb3791202011-07-07 11:57:48 -0700473 public StaticLayout(CharSequence source, int bufstart, int bufend,
474 TextPaint paint, int outerwidth,
475 Alignment align,
476 float spacingmult, float spacingadd,
477 boolean includepad,
478 TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
Seigo Nonaka4e90fa22018-02-13 21:40:01 +0000479 this(source, bufstart, bufend, paint, outerwidth, align,
Doug Feltcb3791202011-07-07 11:57:48 -0700480 TextDirectionHeuristics.FIRSTSTRONG_LTR,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700481 spacingmult, spacingadd, includepad, ellipsize, ellipsizedWidth, Integer.MAX_VALUE);
Doug Feltcb3791202011-07-07 11:57:48 -0700482 }
483
484 /**
485 * @hide
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000486 * @deprecated Use {@link Builder} instead.
Doug Feltcb3791202011-07-07 11:57:48 -0700487 */
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000488 @Deprecated
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;
Raph Levien4c1f12e2015-03-02 16:29:23 -0800603 LineBreaks lineBreaks = new LineBreaks(); // TODO: move to builder to avoid allocation costs
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800604 FloatArray widths = new FloatArray();
Anish Athalye88b5b0b2014-06-24 14:39:43 -0700605
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800606 mLineCount = 0;
Siyamed Sinira19cd512017-08-03 22:01:56 -0700607 mEllipsized = false;
Siyamed Sinira8d982d2017-08-21 13:27:47 -0700608 mMaxLineHeight = mMaximumVisibleLineCount < 1 ? 0 : DEFAULT_MAX_LINE_HEIGHT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800609
610 int v = 0;
611 boolean needMultiply = (spacingmult != 1 || spacingadd != 0);
612
Raph Leviend3ab6922015-03-02 14:30:53 -0800613 Paint.FontMetricsInt fm = b.mFontMetricsInt;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800614 int[] chooseHtv = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800615
Seigo Nonakabafe1972017-08-24 15:30:29 -0700616 final int[] indents;
617 if (mLeftIndents != null || mRightIndents != null) {
618 final int leftLen = mLeftIndents == null ? 0 : mLeftIndents.length;
619 final int rightLen = mRightIndents == null ? 0 : mRightIndents.length;
620 final int indentsLen = Math.max(leftLen, rightLen);
621 indents = new int[indentsLen];
622 for (int i = 0; i < leftLen; i++) {
623 indents[i] = mLeftIndents[i];
624 }
625 for (int i = 0; i < rightLen; i++) {
626 indents[i] += mRightIndents[i];
627 }
628 } else {
629 indents = null;
630 }
631
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700632 final long nativePtr = nInit(
633 b.mBreakStrategy, b.mHyphenationFrequency,
634 // TODO: Support more justification mode, e.g. letter spacing, stretching.
635 b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE,
Seigo Nonaka9155fa62018-06-05 18:22:03 -0700636 indents);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800637
Seigo Nonakac3328d62018-03-20 15:18:59 -0700638 PrecomputedText.ParagraphInfo[] paragraphInfo = null;
639 final Spanned spanned = (source instanceof Spanned) ? (Spanned) source : null;
Seigo Nonakabeafa1f2018-02-01 21:39:24 -0800640 if (source instanceof PrecomputedText) {
Seigo Nonakac3328d62018-03-20 15:18:59 -0700641 PrecomputedText precomputed = (PrecomputedText) source;
642 if (precomputed.canUseMeasuredResult(bufStart, bufEnd, textDir, paint,
643 b.mBreakStrategy, b.mHyphenationFrequency)) {
Seigo Nonakabeafa1f2018-02-01 21:39:24 -0800644 // Some parameters are different from the ones when measured text is created.
Seigo Nonakac3328d62018-03-20 15:18:59 -0700645 paragraphInfo = precomputed.getParagraphInfo();
Seigo Nonakafbe63bd2017-12-02 19:28:05 -0800646 }
Seigo Nonaka87b15472018-01-12 14:06:29 -0800647 }
648
Seigo Nonakac3328d62018-03-20 15:18:59 -0700649 if (paragraphInfo == null) {
Seigo Nonakabeafa1f2018-02-01 21:39:24 -0800650 final PrecomputedText.Params param = new PrecomputedText.Params(paint, textDir,
651 b.mBreakStrategy, b.mHyphenationFrequency);
Seigo Nonakac3328d62018-03-20 15:18:59 -0700652 paragraphInfo = PrecomputedText.createMeasuredParagraphs(source, param, bufStart,
653 bufEnd, false /* computeLayout */);
Seigo Nonaka4e90fa22018-02-13 21:40:01 +0000654 }
Seigo Nonakafbe63bd2017-12-02 19:28:05 -0800655
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700656 try {
Seigo Nonakac3328d62018-03-20 15:18:59 -0700657 for (int paraIndex = 0; paraIndex < paragraphInfo.length; paraIndex++) {
658 final int paraStart = paraIndex == 0
659 ? bufStart : paragraphInfo[paraIndex - 1].paragraphEnd;
660 final int paraEnd = paragraphInfo[paraIndex].paragraphEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800661
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700662 int firstWidthLineCount = 1;
663 int firstWidth = outerWidth;
664 int restWidth = outerWidth;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800665
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700666 LineHeightSpan[] chooseHt = null;
Doug Feltcb3791202011-07-07 11:57:48 -0700667
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700668 if (spanned != null) {
669 LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
670 LeadingMarginSpan.class);
671 for (int i = 0; i < sp.length; i++) {
672 LeadingMarginSpan lms = sp[i];
673 firstWidth -= sp[i].getLeadingMargin(true);
674 restWidth -= sp[i].getLeadingMargin(false);
675
676 // LeadingMarginSpan2 is odd. The count affects all
677 // leading margin spans, not just this particular one
678 if (lms instanceof LeadingMarginSpan2) {
679 LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
680 firstWidthLineCount = Math.max(firstWidthLineCount,
681 lms2.getLeadingMarginLineCount());
682 }
683 }
684
685 chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
686
687 if (chooseHt.length == 0) {
688 chooseHt = null; // So that out() would not assume it has any contents
689 } else {
690 if (chooseHtv == null || chooseHtv.length < chooseHt.length) {
691 chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
692 }
693
694 for (int i = 0; i < chooseHt.length; i++) {
695 int o = spanned.getSpanStart(chooseHt[i]);
696
697 if (o < paraStart) {
698 // starts in this layout, before the
699 // current paragraph
700
701 chooseHtv[i] = getLineTop(getLineForOffset(o));
702 } else {
703 // starts in this paragraph
704
705 chooseHtv[i] = v;
706 }
707 }
Mark Wagner7b5676e2009-10-16 11:44:23 -0700708 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800709 }
710
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700711 // tab stop locations
712 int[] variableTabStops = null;
713 if (spanned != null) {
714 TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
715 paraEnd, TabStopSpan.class);
716 if (spans.length > 0) {
717 int[] stops = new int[spans.length];
718 for (int i = 0; i < spans.length; i++) {
719 stops[i] = spans[i].getTabStop();
720 }
721 Arrays.sort(stops, 0, stops.length);
722 variableTabStops = stops;
723 }
724 }
725
Seigo Nonakac3328d62018-03-20 15:18:59 -0700726 final MeasuredParagraph measuredPara = paragraphInfo[paraIndex].measured;
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800727 final char[] chs = measuredPara.getChars();
728 final int[] spanEndCache = measuredPara.getSpanEndCache().getRawArray();
729 final int[] fmCache = measuredPara.getFontMetrics().getRawArray();
Seigo Nonaka6f1868b2017-12-01 16:24:19 -0800730 // TODO: Stop keeping duplicated width copy in native and Java.
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800731 widths.resize(chs.length);
732
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700733 // measurement has to be done before performing line breaking
734 // but we don't want to recompute fontmetrics or span ranges the
735 // second time, so we cache those and then use those stored values
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700736
737 int breakCount = nComputeLineBreaks(
738 nativePtr,
739
740 // Inputs
741 chs,
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800742 measuredPara.getNativePtr(),
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700743 paraEnd - paraStart,
744 firstWidth,
745 firstWidthLineCount,
746 restWidth,
747 variableTabStops,
748 TAB_INCREMENT,
749 mLineCount,
750
751 // Outputs
752 lineBreaks,
753 lineBreaks.breaks.length,
754 lineBreaks.breaks,
755 lineBreaks.widths,
756 lineBreaks.ascents,
757 lineBreaks.descents,
758 lineBreaks.flags,
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800759 widths.getRawArray());
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700760
761 final int[] breaks = lineBreaks.breaks;
762 final float[] lineWidths = lineBreaks.widths;
763 final float[] ascents = lineBreaks.ascents;
764 final float[] descents = lineBreaks.descents;
765 final int[] flags = lineBreaks.flags;
766
767 final int remainingLineCount = mMaximumVisibleLineCount - mLineCount;
768 final boolean ellipsisMayBeApplied = ellipsize != null
769 && (ellipsize == TextUtils.TruncateAt.END
770 || (mMaximumVisibleLineCount == 1
771 && ellipsize != TextUtils.TruncateAt.MARQUEE));
772 if (0 < remainingLineCount && remainingLineCount < breakCount
773 && ellipsisMayBeApplied) {
774 // Calculate width and flag.
775 float width = 0;
776 int flag = 0; // XXX May need to also have starting hyphen edit
777 for (int i = remainingLineCount - 1; i < breakCount; i++) {
778 if (i == breakCount - 1) {
779 width += lineWidths[i];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800780 } else {
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700781 for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) {
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800782 width += widths.get(j);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700783 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800784 }
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700785 flag |= flags[i] & TAB_MASK;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800786 }
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700787 // Treat the last line and overflowed lines as a single line.
788 breaks[remainingLineCount - 1] = breaks[breakCount - 1];
789 lineWidths[remainingLineCount - 1] = width;
790 flags[remainingLineCount - 1] = flag;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800791
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700792 breakCount = remainingLineCount;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700793 }
794
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700795 // here is the offset of the starting character of the line we are currently
796 // measuring
797 int here = paraStart;
Doug Felte8e45f22010-03-29 14:58:40 -0700798
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700799 int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
800 int fmCacheIndex = 0;
801 int spanEndCacheIndex = 0;
802 int breakIndex = 0;
803 for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
804 // retrieve end of span
805 spanEnd = spanEndCache[spanEndCacheIndex++];
Doug Felt23241882010-06-02 14:41:06 -0700806
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700807 // retrieve cached metrics, order matches above
808 fm.top = fmCache[fmCacheIndex * 4 + 0];
809 fm.bottom = fmCache[fmCacheIndex * 4 + 1];
810 fm.ascent = fmCache[fmCacheIndex * 4 + 2];
811 fm.descent = fmCache[fmCacheIndex * 4 + 3];
812 fmCacheIndex++;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800813
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700814 if (fm.top < fmTop) {
Anish Athalyec8f9e622014-07-21 15:26:34 -0700815 fmTop = fm.top;
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700816 }
817 if (fm.ascent < fmAscent) {
Anish Athalyec8f9e622014-07-21 15:26:34 -0700818 fmAscent = fm.ascent;
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700819 }
820 if (fm.descent > fmDescent) {
Anish Athalyec8f9e622014-07-21 15:26:34 -0700821 fmDescent = fm.descent;
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700822 }
823 if (fm.bottom > fmBottom) {
824 fmBottom = fm.bottom;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700825 }
826
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700827 // skip breaks ending before current span range
828 while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
829 breakIndex++;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700830 }
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700831
832 while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
833 int endPos = paraStart + breaks[breakIndex];
834
835 boolean moreChars = (endPos < bufEnd);
836
837 final int ascent = fallbackLineSpacing
838 ? Math.min(fmAscent, Math.round(ascents[breakIndex]))
839 : fmAscent;
840 final int descent = fallbackLineSpacing
841 ? Math.max(fmDescent, Math.round(descents[breakIndex]))
842 : fmDescent;
843 v = out(source, here, endPos,
844 ascent, descent, fmTop, fmBottom,
845 v, spacingmult, spacingadd, chooseHt, chooseHtv, fm,
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800846 flags[breakIndex], needMultiply, measuredPara, bufEnd,
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800847 includepad, trackpad, addLastLineSpacing, chs, widths.getRawArray(),
848 paraStart, ellipsize, ellipsizedWidth, lineWidths[breakIndex],
849 paint, moreChars);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700850
851 if (endPos < spanEnd) {
852 // preserve metrics for current span
853 fmTop = fm.top;
854 fmBottom = fm.bottom;
855 fmAscent = fm.ascent;
856 fmDescent = fm.descent;
857 } else {
858 fmTop = fmBottom = fmAscent = fmDescent = 0;
859 }
860
861 here = endPos;
862 breakIndex++;
863
864 if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) {
865 return;
866 }
867 }
868 }
869
870 if (paraEnd == bufEnd) {
871 break;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700872 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800873 }
874
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700875 if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE)
876 && mLineCount < mMaximumVisibleLineCount) {
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800877 final MeasuredParagraph measuredPara =
878 MeasuredParagraph.buildForBidi(source, bufEnd, bufEnd, textDir, null);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700879 paint.getFontMetricsInt(fm);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700880 v = out(source,
881 bufEnd, bufEnd, fm.ascent, fm.descent,
882 fm.top, fm.bottom,
883 v,
884 spacingmult, spacingadd, null,
885 null, fm, 0,
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800886 needMultiply, measuredPara, bufEnd,
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700887 includepad, trackpad, addLastLineSpacing, null,
888 null, bufStart, ellipsize,
889 ellipsizedWidth, 0, paint, false);
890 }
891 } finally {
892 nFinish(nativePtr);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800893 }
894 }
895
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700896 private int out(final CharSequence text, final int start, final int end, int above, int below,
897 int top, int bottom, int v, final float spacingmult, final float spacingadd,
898 final LineHeightSpan[] chooseHt, final int[] chooseHtv, final Paint.FontMetricsInt fm,
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800899 final int flags, final boolean needMultiply, @NonNull final MeasuredParagraph measured,
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800900 final int bufEnd, final boolean includePad, final boolean trackPad,
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700901 final boolean addLastLineLineSpacing, final char[] chs, final float[] widths,
902 final int widthStart, final TextUtils.TruncateAt ellipsize, final float ellipsisWidth,
903 final float textWidth, final TextPaint paint, final boolean moreChars) {
Siyamed Siniraf398512017-07-25 19:08:42 -0700904 final int j = mLineCount;
905 final int off = j * mColumns;
906 final int want = off + mColumns + TOP;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800907 int[] lines = mLines;
Seigo Nonaka4b6bcee2017-12-11 11:39:51 -0800908 final int dir = measured.getParagraphDir();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800909
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800910 if (want >= lines.length) {
Siyamed Siniraf398512017-07-25 19:08:42 -0700911 final int[] grow = ArrayUtils.newUnpaddedIntArray(GrowingArrayUtils.growSize(want));
Adam Lesinski776abc22014-03-07 11:30:59 -0500912 System.arraycopy(lines, 0, grow, 0, lines.length);
913 mLines = grow;
914 lines = grow;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800915 }
916
Siyamed Siniraf398512017-07-25 19:08:42 -0700917 if (j >= mLineDirections.length) {
918 final Directions[] grow = ArrayUtils.newUnpaddedArray(Directions.class,
919 GrowingArrayUtils.growSize(j));
920 System.arraycopy(mLineDirections, 0, grow, 0, mLineDirections.length);
921 mLineDirections = grow;
922 }
923
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800924 if (chooseHt != null) {
925 fm.ascent = above;
926 fm.descent = below;
927 fm.top = top;
928 fm.bottom = bottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800929
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800930 for (int i = 0; i < chooseHt.length; i++) {
931 if (chooseHt[i] instanceof LineHeightSpan.WithDensity) {
932 ((LineHeightSpan.WithDensity) chooseHt[i])
933 .chooseHeight(text, start, end, chooseHtv[i], v, fm, paint);
934 } else {
935 chooseHt[i].chooseHeight(text, start, end, chooseHtv[i], v, fm);
936 }
937 }
Eric Fischera9f1dd02009-08-12 15:00:10 -0700938
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800939 above = fm.ascent;
940 below = fm.descent;
941 top = fm.top;
942 bottom = fm.bottom;
943 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800944
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800945 boolean firstLine = (j == 0);
946 boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);
Siyamed Sinir0745c722016-05-31 20:39:33 -0700947
948 if (ellipsize != null) {
949 // If there is only one line, then do any type of ellipsis except when it is MARQUEE
950 // if there are multiple lines, just allow END ellipsis on the last line
951 boolean forceEllipsis = moreChars && (mLineCount + 1 == mMaximumVisibleLineCount);
952
953 boolean doEllipsis =
954 (((mMaximumVisibleLineCount == 1 && moreChars) || (firstLine && !moreChars)) &&
955 ellipsize != TextUtils.TruncateAt.MARQUEE) ||
956 (!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
957 ellipsize == TextUtils.TruncateAt.END);
958 if (doEllipsis) {
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800959 calculateEllipsis(start, end, widths, widthStart,
960 ellipsisWidth, ellipsize, j,
961 textWidth, paint, forceEllipsis);
Siyamed Sinir0745c722016-05-31 20:39:33 -0700962 }
963 }
964
Siyamed Sinirfb0b2dc2017-07-26 22:08:24 -0700965 final boolean lastLine;
966 if (mEllipsized) {
967 lastLine = true;
968 } else {
969 final boolean lastCharIsNewLine = widthStart != bufEnd && bufEnd > 0
970 && text.charAt(bufEnd - 1) == CHAR_NEW_LINE;
971 if (end == bufEnd && !lastCharIsNewLine) {
972 lastLine = true;
973 } else if (start == bufEnd && lastCharIsNewLine) {
974 lastLine = true;
975 } else {
976 lastLine = false;
977 }
978 }
Raph Leviend97b0972014-04-24 12:51:35 -0700979
980 if (firstLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800981 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800982 mTopPadding = top - above;
983 }
984
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800985 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800986 above = top;
987 }
988 }
Raph Leviend97b0972014-04-24 12:51:35 -0700989
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800990 int extra;
991
Raph Leviend97b0972014-04-24 12:51:35 -0700992 if (lastLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800993 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800994 mBottomPadding = bottom - below;
995 }
996
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800997 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800998 below = bottom;
999 }
1000 }
1001
Siyamed Sinir442c1512017-07-24 12:18:27 -07001002 if (needMultiply && (addLastLineLineSpacing || !lastLine)) {
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001003 double ex = (below - above) * (spacingmult - 1) + spacingadd;
Doug Felt10657582010-02-22 11:19:01 -08001004 if (ex >= 0) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001005 extra = (int)(ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -08001006 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001007 extra = -(int)(-ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -08001008 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001009 } else {
1010 extra = 0;
1011 }
1012
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001013 lines[off + START] = start;
1014 lines[off + TOP] = v;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001015 lines[off + DESCENT] = below + extra;
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001016 lines[off + EXTRA] = extra;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001017
Siyamed Sinir0745c722016-05-31 20:39:33 -07001018 // special case for non-ellipsized last visible line when maxLines is set
1019 // store the height as if it was ellipsized
1020 if (!mEllipsized && currentLineIsTheLastVisibleOne) {
1021 // below calculation as if it was the last line
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001022 int maxLineBelow = includePad ? bottom : below;
Siyamed Sinir0745c722016-05-31 20:39:33 -07001023 // similar to the calculation of v below, without the extra.
1024 mMaxLineHeight = v + (maxLineBelow - above);
1025 }
1026
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001027 v += (below - above) + extra;
1028 lines[off + mColumns + START] = end;
1029 lines[off + mColumns + TOP] = v;
1030
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001031 // TODO: could move TAB to share same column as HYPHEN, simplifying this code and gaining
1032 // one bit for start field
1033 lines[off + TAB] |= flags & TAB_MASK;
1034 lines[off + HYPHEN] = flags;
1035 lines[off + DIR] |= dir << DIR_SHIFT;
1036 mLineDirections[j] = measured.getDirections(start - widthStart, end - widthStart);
1037
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001038 mLineCount++;
1039 return v;
1040 }
1041
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001042 private void calculateEllipsis(int lineStart, int lineEnd,
1043 float[] widths, int widthStart,
1044 float avail, TextUtils.TruncateAt where,
1045 int line, float textWidth, TextPaint paint,
1046 boolean forceEllipsis) {
1047 avail -= getTotalInsets(line);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001048 if (textWidth <= avail && !forceEllipsis) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001049 // Everything fits!
1050 mLines[mColumns * line + ELLIPSIS_START] = 0;
1051 mLines[mColumns * line + ELLIPSIS_COUNT] = 0;
1052 return;
1053 }
1054
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001055 float ellipsisWidth = paint.measureText(TextUtils.getEllipsisString(where));
1056 int ellipsisStart = 0;
1057 int ellipsisCount = 0;
1058 int len = lineEnd - lineStart;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001059
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001060 // We only support start ellipsis on a single line
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001061 if (where == TextUtils.TruncateAt.START) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001062 if (mMaximumVisibleLineCount == 1) {
1063 float sum = 0;
1064 int i;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001065
Keisuke Kuroyanagied2eea12015-04-14 18:18:35 +09001066 for (i = len; i > 0; i--) {
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001067 float w = widths[i - 1 + lineStart - widthStart];
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001068 if (w + sum + ellipsisWidth > avail) {
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001069 while (i < len && widths[i + lineStart - widthStart] == 0.0f) {
Keisuke Kuroyanagi82d1c442016-09-14 14:30:14 +09001070 i++;
1071 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001072 break;
1073 }
1074
1075 sum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001076 }
1077
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001078 ellipsisStart = 0;
1079 ellipsisCount = i;
1080 } else {
1081 if (Log.isLoggable(TAG, Log.WARN)) {
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001082 Log.w(TAG, "Start Ellipsis only supported with one line");
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001083 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001084 }
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001085 } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE ||
1086 where == TextUtils.TruncateAt.END_SMALL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001087 float sum = 0;
1088 int i;
1089
1090 for (i = 0; i < len; i++) {
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001091 float w = widths[i + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001092
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001093 if (w + sum + ellipsisWidth > avail) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001094 break;
1095 }
1096
1097 sum += w;
1098 }
1099
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001100 ellipsisStart = i;
1101 ellipsisCount = len - i;
1102 if (forceEllipsis && ellipsisCount == 0 && len > 0) {
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -07001103 ellipsisStart = len - 1;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001104 ellipsisCount = 1;
1105 }
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001106 } else {
1107 // where = TextUtils.TruncateAt.MIDDLE We only support middle ellipsis on a single line
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001108 if (mMaximumVisibleLineCount == 1) {
1109 float lsum = 0, rsum = 0;
1110 int left = 0, right = len;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001111
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001112 float ravail = (avail - ellipsisWidth) / 2;
Raph Levien0e3c5e82014-12-04 13:26:07 -08001113 for (right = len; right > 0; right--) {
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001114 float w = widths[right - 1 + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001115
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001116 if (w + rsum > ravail) {
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001117 while (right < len && widths[right + lineStart - widthStart] == 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++) {
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001127 float w = widths[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 /**
1242 * @hide
1243 */
1244 @Override
1245 public int getHyphen(int line) {
Keisuke Kuroyanagif5af4a32016-08-31 21:40:53 +09001246 return mLines[mColumns * line + HYPHEN] & HYPHEN_MASK;
Raph Levien26d443a2015-03-30 14:18:32 -07001247 }
1248
Raph Levien2ea52902015-07-01 14:39:31 -07001249 /**
1250 * @hide
1251 */
1252 @Override
1253 public int getIndentAdjust(int line, Alignment align) {
1254 if (align == Alignment.ALIGN_LEFT) {
1255 if (mLeftIndents == null) {
1256 return 0;
1257 } else {
1258 return mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1259 }
1260 } else if (align == Alignment.ALIGN_RIGHT) {
1261 if (mRightIndents == null) {
1262 return 0;
1263 } else {
1264 return -mRightIndents[Math.min(line, mRightIndents.length - 1)];
1265 }
1266 } else if (align == Alignment.ALIGN_CENTER) {
1267 int left = 0;
1268 if (mLeftIndents != null) {
1269 left = mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1270 }
1271 int right = 0;
1272 if (mRightIndents != null) {
1273 right = mRightIndents[Math.min(line, mRightIndents.length - 1)];
1274 }
1275 return (left - right) >> 1;
1276 } else {
1277 throw new AssertionError("unhandled alignment " + align);
1278 }
1279 }
1280
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001281 @Override
1282 public int getEllipsisCount(int line) {
1283 if (mColumns < COLUMNS_ELLIPSIZE) {
1284 return 0;
1285 }
1286
1287 return mLines[mColumns * line + ELLIPSIS_COUNT];
1288 }
1289
1290 @Override
1291 public int getEllipsisStart(int line) {
1292 if (mColumns < COLUMNS_ELLIPSIZE) {
1293 return 0;
1294 }
1295
1296 return mLines[mColumns * line + ELLIPSIS_START];
1297 }
1298
1299 @Override
1300 public int getEllipsizedWidth() {
1301 return mEllipsizedWidth;
1302 }
1303
Siyamed Sinir0745c722016-05-31 20:39:33 -07001304 /**
1305 * Return the total height of this layout.
1306 *
1307 * @param cap if true and max lines is set, returns the height of the layout at the max lines.
1308 *
1309 * @hide
1310 */
1311 public int getHeight(boolean cap) {
Siyamed Sinir70ffd282018-03-08 17:19:46 -08001312 if (cap && mLineCount > mMaximumVisibleLineCount && mMaxLineHeight == -1
1313 && Log.isLoggable(TAG, Log.WARN)) {
Siyamed Sinir0745c722016-05-31 20:39:33 -07001314 Log.w(TAG, "maxLineHeight should not be -1. "
1315 + " maxLines:" + mMaximumVisibleLineCount
1316 + " lineCount:" + mLineCount);
1317 }
1318
Siyamed Sinir70ffd282018-03-08 17:19:46 -08001319 return cap && mLineCount > mMaximumVisibleLineCount && mMaxLineHeight != -1
1320 ? mMaxLineHeight : super.getHeight();
Siyamed Sinir0745c722016-05-31 20:39:33 -07001321 }
1322
Seigo Nonakab90e08f2017-10-20 15:23:51 -07001323 @FastNative
1324 private static native long nInit(
1325 @BreakStrategy int breakStrategy,
1326 @HyphenationFrequency int hyphenationFrequency,
1327 boolean isJustified,
Seigo Nonaka9155fa62018-06-05 18:22:03 -07001328 @Nullable int[] indents);
Seigo Nonakab90e08f2017-10-20 15:23:51 -07001329
1330 @CriticalNative
1331 private static native void nFinish(long nativePtr);
1332
Anish Athalyec8f9e622014-07-21 15:26:34 -07001333 // populates LineBreaks and returns the number of breaks found
1334 //
1335 // the arrays inside the LineBreaks objects are passed in as well
1336 // to reduce the number of JNI calls in the common case where the
1337 // arrays do not have to be resized
Seigo Nonaka2dd1a552017-10-06 11:41:00 -07001338 // The individual character widths will be returned in charWidths. The length of charWidths must
1339 // be at least the length of the text.
Seigo Nonakab90e08f2017-10-20 15:23:51 -07001340 private static native int nComputeLineBreaks(
1341 /* non zero */ long nativePtr,
1342
1343 // Inputs
1344 @NonNull char[] text,
Seigo Nonaka6f1868b2017-12-01 16:24:19 -08001345 /* Non Zero */ long measuredTextPtr,
Seigo Nonakab90e08f2017-10-20 15:23:51 -07001346 @IntRange(from = 0) int length,
1347 @FloatRange(from = 0.0f) float firstWidth,
1348 @IntRange(from = 0) int firstWidthLineCount,
1349 @FloatRange(from = 0.0f) float restWidth,
1350 @Nullable int[] variableTabStops,
1351 int defaultTabStop,
1352 @IntRange(from = 0) int indentsOffset,
1353
1354 // Outputs
1355 @NonNull LineBreaks recycle,
1356 @IntRange(from = 0) int recycleLength,
1357 @NonNull int[] recycleBreaks,
1358 @NonNull float[] recycleWidths,
1359 @NonNull float[] recycleAscents,
1360 @NonNull float[] recycleDescents,
1361 @NonNull int[] recycleFlags,
1362 @NonNull float[] charWidths);
Anish Athalye88b5b0b2014-06-24 14:39:43 -07001363
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001364 private int mLineCount;
1365 private int mTopPadding, mBottomPadding;
1366 private int mColumns;
1367 private int mEllipsizedWidth;
1368
Siyamed Sinir0745c722016-05-31 20:39:33 -07001369 /**
1370 * Keeps track if ellipsize is applied to the text.
1371 */
1372 private boolean mEllipsized;
1373
1374 /**
1375 * If maxLines is set, ellipsize is not set, and the actual line count of text is greater than
1376 * or equal to maxLine, this variable holds the ideal visual height of the maxLine'th line
1377 * starting from the top of the layout. If maxLines is not set its value will be -1.
1378 *
1379 * The value is the same as getLineTop(maxLines) for ellipsized version where structurally no
1380 * more than maxLines is contained.
1381 */
Siyamed Sinira19cd512017-08-03 22:01:56 -07001382 private int mMaxLineHeight = DEFAULT_MAX_LINE_HEIGHT;
Siyamed Sinir0745c722016-05-31 20:39:33 -07001383
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001384 private static final int COLUMNS_NORMAL = 5;
1385 private static final int COLUMNS_ELLIPSIZE = 7;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001386 private static final int START = 0;
1387 private static final int DIR = START;
1388 private static final int TAB = START;
1389 private static final int TOP = 1;
1390 private static final int DESCENT = 2;
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001391 private static final int EXTRA = 3;
1392 private static final int HYPHEN = 4;
1393 private static final int ELLIPSIS_START = 5;
1394 private static final int ELLIPSIS_COUNT = 6;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001395
1396 private int[] mLines;
1397 private Directions[] mLineDirections;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001398 private int mMaximumVisibleLineCount = Integer.MAX_VALUE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001399
1400 private static final int START_MASK = 0x1FFFFFFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001401 private static final int DIR_SHIFT = 30;
1402 private static final int TAB_MASK = 0x20000000;
Keisuke Kuroyanagif5af4a32016-08-31 21:40:53 +09001403 private static final int HYPHEN_MASK = 0xFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001404
Doug Feltc982f602010-05-25 11:51:40 -07001405 private static final int TAB_INCREMENT = 20; // same as Layout, but that's private
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001406
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001407 private static final char CHAR_NEW_LINE = '\n';
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001408
1409 private static final double EXTRA_ROUNDING = 0.5;
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001410
Siyamed Sinira19cd512017-08-03 22:01:56 -07001411 private static final int DEFAULT_MAX_LINE_HEIGHT = -1;
1412
Anish Athalyec8f9e622014-07-21 15:26:34 -07001413 // This is used to return three arrays from a single JNI call when
1414 // performing line breaking
Deepanshu Gupta70539192014-10-15 15:57:40 -07001415 /*package*/ static class LineBreaks {
Anish Athalyec8f9e622014-07-21 15:26:34 -07001416 private static final int INITIAL_SIZE = 16;
1417 public int[] breaks = new int[INITIAL_SIZE];
1418 public float[] widths = new float[INITIAL_SIZE];
Roozbeh Pournader737dfea2017-08-10 11:32:24 -07001419 public float[] ascents = new float[INITIAL_SIZE];
1420 public float[] descents = new float[INITIAL_SIZE];
Roozbeh Pournader112d9c72015-08-07 12:44:41 -07001421 public int[] flags = new int[INITIAL_SIZE]; // hasTab
Anish Athalyec8f9e622014-07-21 15:26:34 -07001422 // breaks, widths, and flags should all have the same length
1423 }
1424
Seigo Nonakabafe1972017-08-24 15:30:29 -07001425 @Nullable private int[] mLeftIndents;
1426 @Nullable private int[] mRightIndents;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001427}