blob: 400b075d889da1f40b2677eede3d8575cb7728b8 [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 *
58 * - Create MeasuredText by MeasuredText.buildForStaticLayout which measures in native.
59 * - Run nComputeLineBreaks() to obtain line breaks for the paragraph.
60 *
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;
Roozbeh Pournader9a9179d2017-08-29 14:14:28 -0700126 b.mLeftPaddings = null;
127 b.mRightPaddings = null;
Raph Levien39b4db72015-03-25 13:18:20 -0700128 sPool.release(b);
Raph Leviend3ab6922015-03-02 14:30:53 -0800129 }
130
131 // release any expensive state
132 /* package */ void finish() {
Raph Levien22ba7862015-07-29 12:34:13 -0700133 mText = null;
134 mPaint = null;
135 mLeftIndents = null;
136 mRightIndents = null;
Roozbeh Pournader9a9179d2017-08-29 14:14:28 -0700137 mLeftPaddings = null;
138 mRightPaddings = null;
Raph Leviend3ab6922015-03-02 14:30:53 -0800139 }
140
141 public Builder setText(CharSequence source) {
142 return setText(source, 0, source.length());
143 }
144
Raph Levien531c30c2015-04-30 16:29:59 -0700145 /**
146 * Set the text. Only useful when re-using the builder, which is done for
147 * the internal implementation of {@link DynamicLayout} but not as part
148 * of normal {@link StaticLayout} usage.
149 *
150 * @param source The text to be laid out, optionally with spans
151 * @param start The index of the start of the text
152 * @param end The index + 1 of the end of the text
153 * @return this builder, useful for chaining
154 *
155 * @hide
156 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700157 @NonNull
158 public Builder setText(@NonNull CharSequence source, int start, int end) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800159 mText = source;
160 mStart = start;
161 mEnd = end;
162 return this;
163 }
164
Raph Levien531c30c2015-04-30 16:29:59 -0700165 /**
166 * Set the paint. Internal for reuse cases only.
167 *
168 * @param paint The base paint used for layout
169 * @return this builder, useful for chaining
170 *
171 * @hide
172 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700173 @NonNull
174 public Builder setPaint(@NonNull TextPaint paint) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800175 mPaint = paint;
176 return this;
177 }
178
Raph Levien531c30c2015-04-30 16:29:59 -0700179 /**
180 * Set the width. Internal for reuse cases only.
181 *
182 * @param width The width in pixels
183 * @return this builder, useful for chaining
184 *
185 * @hide
186 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700187 @NonNull
188 public Builder setWidth(@IntRange(from = 0) int width) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800189 mWidth = width;
190 if (mEllipsize == null) {
191 mEllipsizedWidth = width;
192 }
193 return this;
194 }
195
Raph Levien531c30c2015-04-30 16:29:59 -0700196 /**
197 * Set the alignment. The default is {@link Layout.Alignment#ALIGN_NORMAL}.
198 *
199 * @param alignment Alignment for the resulting {@link StaticLayout}
200 * @return this builder, useful for chaining
201 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700202 @NonNull
203 public Builder setAlignment(@NonNull Alignment alignment) {
Raph Levien39b4db72015-03-25 13:18:20 -0700204 mAlignment = alignment;
205 return this;
206 }
207
Raph Levien531c30c2015-04-30 16:29:59 -0700208 /**
209 * Set the text direction heuristic. The text direction heuristic is used to
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700210 * resolve text direction per-paragraph based on the input text. The default is
Raph Levien531c30c2015-04-30 16:29:59 -0700211 * {@link TextDirectionHeuristics#FIRSTSTRONG_LTR}.
212 *
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700213 * @param textDir text direction heuristic for resolving bidi behavior.
Raph Levien531c30c2015-04-30 16:29:59 -0700214 * @return this builder, useful for chaining
215 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700216 @NonNull
217 public Builder setTextDirection(@NonNull TextDirectionHeuristic textDir) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800218 mTextDir = textDir;
219 return this;
220 }
221
Raph Levien531c30c2015-04-30 16:29:59 -0700222 /**
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700223 * Set line spacing parameters. Each line will have its line spacing multiplied by
224 * {@code spacingMult} and then increased by {@code spacingAdd}. The default is 0.0 for
225 * {@code spacingAdd} and 1.0 for {@code spacingMult}.
Raph Levien531c30c2015-04-30 16:29:59 -0700226 *
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700227 * @param spacingAdd the amount of line spacing addition
228 * @param spacingMult the line spacing multiplier
Raph Levien531c30c2015-04-30 16:29:59 -0700229 * @return this builder, useful for chaining
230 * @see android.widget.TextView#setLineSpacing
231 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700232 @NonNull
233 public Builder setLineSpacing(float spacingAdd, @FloatRange(from = 0.0) float spacingMult) {
Raph Levien531c30c2015-04-30 16:29:59 -0700234 mSpacingAdd = spacingAdd;
Raph Leviend3ab6922015-03-02 14:30:53 -0800235 mSpacingMult = spacingMult;
236 return this;
237 }
238
Raph Levien531c30c2015-04-30 16:29:59 -0700239 /**
240 * Set whether to include extra space beyond font ascent and descent (which is
241 * needed to avoid clipping in some languages, such as Arabic and Kannada). The
242 * default is {@code true}.
243 *
244 * @param includePad whether to include padding
245 * @return this builder, useful for chaining
246 * @see android.widget.TextView#setIncludeFontPadding
247 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700248 @NonNull
Raph Leviend3ab6922015-03-02 14:30:53 -0800249 public Builder setIncludePad(boolean includePad) {
250 mIncludePad = includePad;
251 return this;
252 }
253
Raph Levien531c30c2015-04-30 16:29:59 -0700254 /**
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700255 * Set whether to respect the ascent and descent of the fallback fonts that are used in
256 * displaying the text (which is needed to avoid text from consecutive lines running into
257 * each other). If set, fallback fonts that end up getting used can increase the ascent
258 * and descent of the lines that they are used on.
259 *
260 * <p>For backward compatibility reasons, the default is {@code false}, but setting this to
261 * true is strongly recommended. It is required to be true if text could be in languages
262 * like Burmese or Tibetan where text is typically much taller or deeper than Latin text.
263 *
264 * @param useLineSpacingFromFallbacks whether to expand linespacing based on fallback fonts
265 * @return this builder, useful for chaining
266 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700267 @NonNull
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700268 public Builder setUseLineSpacingFromFallbacks(boolean useLineSpacingFromFallbacks) {
269 mFallbackLineSpacing = useLineSpacingFromFallbacks;
270 return this;
271 }
272
273 /**
Raph Levien531c30c2015-04-30 16:29:59 -0700274 * Set the width as used for ellipsizing purposes, if it differs from the
275 * normal layout width. The default is the {@code width}
276 * passed to {@link #obtain}.
277 *
278 * @param ellipsizedWidth width used for ellipsizing, in pixels
279 * @return this builder, useful for chaining
280 * @see android.widget.TextView#setEllipsize
281 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700282 @NonNull
283 public Builder setEllipsizedWidth(@IntRange(from = 0) int ellipsizedWidth) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800284 mEllipsizedWidth = ellipsizedWidth;
285 return this;
286 }
287
Raph Levien531c30c2015-04-30 16:29:59 -0700288 /**
289 * Set ellipsizing on the layout. Causes words that are longer than the view
290 * is wide, or exceeding the number of lines (see #setMaxLines) in the case
291 * of {@link android.text.TextUtils.TruncateAt#END} or
292 * {@link android.text.TextUtils.TruncateAt#MARQUEE}, to be ellipsized instead
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700293 * of broken. The default is {@code null}, indicating no ellipsis is to be applied.
Raph Levien531c30c2015-04-30 16:29:59 -0700294 *
295 * @param ellipsize type of ellipsis behavior
296 * @return this builder, useful for chaining
297 * @see android.widget.TextView#setEllipsize
298 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700299 @NonNull
Raph Levien531c30c2015-04-30 16:29:59 -0700300 public Builder setEllipsize(@Nullable TextUtils.TruncateAt ellipsize) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800301 mEllipsize = ellipsize;
302 return this;
303 }
304
Raph Levien531c30c2015-04-30 16:29:59 -0700305 /**
306 * Set maximum number of lines. This is particularly useful in the case of
307 * ellipsizing, where it changes the layout of the last line. The default is
308 * unlimited.
309 *
310 * @param maxLines maximum number of lines in the layout
311 * @return this builder, useful for chaining
312 * @see android.widget.TextView#setMaxLines
313 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700314 @NonNull
315 public Builder setMaxLines(@IntRange(from = 0) int maxLines) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800316 mMaxLines = maxLines;
317 return this;
318 }
319
Raph Levien531c30c2015-04-30 16:29:59 -0700320 /**
321 * Set break strategy, useful for selecting high quality or balanced paragraph
322 * layout options. The default is {@link Layout#BREAK_STRATEGY_SIMPLE}.
323 *
324 * @param breakStrategy break strategy for paragraph layout
325 * @return this builder, useful for chaining
326 * @see android.widget.TextView#setBreakStrategy
327 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700328 @NonNull
Raph Levien39b4db72015-03-25 13:18:20 -0700329 public Builder setBreakStrategy(@BreakStrategy int breakStrategy) {
330 mBreakStrategy = breakStrategy;
331 return this;
332 }
333
Raph Levien531c30c2015-04-30 16:29:59 -0700334 /**
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700335 * Set hyphenation frequency, to control the amount of automatic hyphenation used. The
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700336 * possible values are defined in {@link Layout}, by constants named with the pattern
337 * {@code HYPHENATION_FREQUENCY_*}. The default is
338 * {@link Layout#HYPHENATION_FREQUENCY_NONE}.
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700339 *
340 * @param hyphenationFrequency hyphenation frequency for the paragraph
341 * @return this builder, useful for chaining
342 * @see android.widget.TextView#setHyphenationFrequency
343 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700344 @NonNull
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700345 public Builder setHyphenationFrequency(@HyphenationFrequency int hyphenationFrequency) {
346 mHyphenationFrequency = hyphenationFrequency;
347 return this;
348 }
349
350 /**
Raph Levien531c30c2015-04-30 16:29:59 -0700351 * Set indents. Arguments are arrays holding an indent amount, one per line, measured in
352 * pixels. For lines past the last element in the array, the last element repeats.
353 *
354 * @param leftIndents array of indent values for left margin, in pixels
355 * @param rightIndents array of indent values for right margin, in pixels
356 * @return this builder, useful for chaining
Raph Levien531c30c2015-04-30 16:29:59 -0700357 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700358 @NonNull
359 public Builder setIndents(@Nullable int[] leftIndents, @Nullable int[] rightIndents) {
Raph Levien2ea52902015-07-01 14:39:31 -0700360 mLeftIndents = leftIndents;
361 mRightIndents = rightIndents;
Raph Leviene319d5a2015-04-14 23:51:07 -0700362 return this;
363 }
364
Raph Levien70616ec2015-03-04 10:41:30 -0800365 /**
Roozbeh Pournader9a9179d2017-08-29 14:14:28 -0700366 * Set available paddings to draw overhanging text on. Arguments are arrays holding the
367 * amount of padding available, one per line, measured in pixels. For lines past the last
368 * element in the array, the last element repeats.
369 *
370 * The individual padding amounts should be non-negative. The result of passing negative
371 * paddings is undefined.
372 *
373 * @param leftPaddings array of amounts of available padding for left margin, in pixels
374 * @param rightPaddings array of amounts of available padding for right margin, in pixels
375 * @return this builder, useful for chaining
376 *
377 * @hide
378 */
379 @NonNull
380 public Builder setAvailablePaddings(@Nullable int[] leftPaddings,
381 @Nullable int[] rightPaddings) {
382 mLeftPaddings = leftPaddings;
383 mRightPaddings = rightPaddings;
384 return this;
385 }
386
387 /**
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700388 * Set paragraph justification mode. The default value is
389 * {@link Layout#JUSTIFICATION_MODE_NONE}. If the last line is too short for justification,
390 * the last line will be displayed with the alignment set by {@link #setAlignment}.
Seigo Nonaka09da71a2016-11-28 16:24:14 +0900391 *
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700392 * @param justificationMode justification mode for the paragraph.
Seigo Nonaka09da71a2016-11-28 16:24:14 +0900393 * @return this builder, useful for chaining.
394 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700395 @NonNull
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700396 public Builder setJustificationMode(@JustificationMode int justificationMode) {
397 mJustificationMode = justificationMode;
Seigo Nonaka09da71a2016-11-28 16:24:14 +0900398 return this;
399 }
400
Siyamed Sinir442c1512017-07-24 12:18:27 -0700401 /**
402 * Sets whether the line spacing should be applied for the last line. Default value is
403 * {@code false}.
404 *
405 * @hide
406 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700407 @NonNull
Siyamed Sinir442c1512017-07-24 12:18:27 -0700408 /* package */ Builder setAddLastLineLineSpacing(boolean value) {
409 mAddLastLineLineSpacing = value;
410 return this;
411 }
412
Seigo Nonaka09da71a2016-11-28 16:24:14 +0900413 /**
Raph Levien531c30c2015-04-30 16:29:59 -0700414 * Build the {@link StaticLayout} after options have been set.
415 *
416 * <p>Note: the builder object must not be reused in any way after calling this
417 * method. Setting parameters after calling this method, or calling it a second
418 * time on the same builder object, will likely lead to unexpected results.
419 *
420 * @return the newly constructed {@link StaticLayout} object
421 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700422 @NonNull
Raph Leviend3ab6922015-03-02 14:30:53 -0800423 public StaticLayout build() {
Raph Levien39b4db72015-03-25 13:18:20 -0700424 StaticLayout result = new StaticLayout(this);
425 Builder.recycle(this);
Raph Leviend3ab6922015-03-02 14:30:53 -0800426 return result;
427 }
428
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700429 private CharSequence mText;
430 private int mStart;
431 private int mEnd;
432 private TextPaint mPaint;
433 private int mWidth;
434 private Alignment mAlignment;
435 private TextDirectionHeuristic mTextDir;
436 private float mSpacingMult;
437 private float mSpacingAdd;
438 private boolean mIncludePad;
439 private boolean mFallbackLineSpacing;
440 private int mEllipsizedWidth;
441 private TextUtils.TruncateAt mEllipsize;
442 private int mMaxLines;
443 private int mBreakStrategy;
444 private int mHyphenationFrequency;
Seigo Nonakabafe1972017-08-24 15:30:29 -0700445 @Nullable private int[] mLeftIndents;
446 @Nullable private int[] mRightIndents;
Roozbeh Pournader9a9179d2017-08-29 14:14:28 -0700447 @Nullable private int[] mLeftPaddings;
448 @Nullable private int[] mRightPaddings;
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700449 private int mJustificationMode;
450 private boolean mAddLastLineLineSpacing;
Raph Leviend3ab6922015-03-02 14:30:53 -0800451
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700452 private final Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
Raph Leviend3ab6922015-03-02 14:30:53 -0800453
Siyamed Sinira273a702017-10-05 11:22:12 -0700454 private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<>(3);
Raph Leviend3ab6922015-03-02 14:30:53 -0800455 }
456
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800457 public StaticLayout(CharSequence source, TextPaint paint,
458 int width,
459 Alignment align, float spacingmult, float spacingadd,
460 boolean includepad) {
461 this(source, 0, source.length(), paint, width, align,
462 spacingmult, spacingadd, includepad);
463 }
464
Doug Feltcb3791202011-07-07 11:57:48 -0700465 /**
466 * @hide
467 */
468 public StaticLayout(CharSequence source, TextPaint paint,
469 int width, Alignment align, TextDirectionHeuristic textDir,
470 float spacingmult, float spacingadd,
471 boolean includepad) {
472 this(source, 0, source.length(), paint, width, align, textDir,
473 spacingmult, spacingadd, includepad);
474 }
475
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800476 public StaticLayout(CharSequence source, int bufstart, int bufend,
477 TextPaint paint, int outerwidth,
478 Alignment align,
479 float spacingmult, float spacingadd,
480 boolean includepad) {
481 this(source, bufstart, bufend, paint, outerwidth, align,
482 spacingmult, spacingadd, includepad, null, 0);
483 }
484
Doug Feltcb3791202011-07-07 11:57:48 -0700485 /**
486 * @hide
487 */
488 public StaticLayout(CharSequence source, int bufstart, int bufend,
489 TextPaint paint, int outerwidth,
490 Alignment align, TextDirectionHeuristic textDir,
491 float spacingmult, float spacingadd,
492 boolean includepad) {
493 this(source, bufstart, bufend, paint, outerwidth, align, textDir,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700494 spacingmult, spacingadd, includepad, null, 0, Integer.MAX_VALUE);
Doug Feltcb3791202011-07-07 11:57:48 -0700495}
496
497 public StaticLayout(CharSequence source, int bufstart, int bufend,
498 TextPaint paint, int outerwidth,
499 Alignment align,
500 float spacingmult, float spacingadd,
501 boolean includepad,
502 TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
503 this(source, bufstart, bufend, paint, outerwidth, align,
504 TextDirectionHeuristics.FIRSTSTRONG_LTR,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700505 spacingmult, spacingadd, includepad, ellipsize, ellipsizedWidth, Integer.MAX_VALUE);
Doug Feltcb3791202011-07-07 11:57:48 -0700506 }
507
508 /**
509 * @hide
510 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800511 public StaticLayout(CharSequence source, int bufstart, int bufend,
512 TextPaint paint, int outerwidth,
Doug Feltcb3791202011-07-07 11:57:48 -0700513 Alignment align, TextDirectionHeuristic textDir,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800514 float spacingmult, float spacingadd,
515 boolean includepad,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700516 TextUtils.TruncateAt ellipsize, int ellipsizedWidth, int maxLines) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800517 super((ellipsize == null)
Doug Felt4e0c5e52010-03-15 16:56:02 -0700518 ? source
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800519 : (source instanceof Spanned)
520 ? new SpannedEllipsizer(source)
521 : new Ellipsizer(source),
Doug Feltcb3791202011-07-07 11:57:48 -0700522 paint, outerwidth, align, textDir, spacingmult, spacingadd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800523
Raph Levienebd66ca2015-04-30 15:27:57 -0700524 Builder b = Builder.obtain(source, bufstart, bufend, paint, outerwidth)
Raph Levien39b4db72015-03-25 13:18:20 -0700525 .setAlignment(align)
Raph Leviena6a08282015-06-03 13:20:45 -0700526 .setTextDirection(textDir)
Raph Levien531c30c2015-04-30 16:29:59 -0700527 .setLineSpacing(spacingadd, spacingmult)
Raph Leviend3ab6922015-03-02 14:30:53 -0800528 .setIncludePad(includepad)
529 .setEllipsizedWidth(ellipsizedWidth)
530 .setEllipsize(ellipsize)
531 .setMaxLines(maxLines);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800532 /*
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700533 * This is annoying, but we can't refer to the layout until superclass construction is
534 * finished, and the superclass constructor wants the reference to the display text.
Doug Felt4e0c5e52010-03-15 16:56:02 -0700535 *
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700536 * In other words, the two Ellipsizer classes in Layout.java need a (Dynamic|Static)Layout
537 * as a parameter to do their calculations, but the Ellipsizers also need to be the input
538 * to the superclass's constructor (Layout). In order to go around the circular
539 * dependency, we construct the Ellipsizer with only one of the parameters, the text. And
540 * we fill in the rest of the needed information (layout, width, and method) later, here.
541 *
542 * This will break if the superclass constructor ever actually cares about the content
543 * instead of just holding the reference.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800544 */
545 if (ellipsize != null) {
546 Ellipsizer e = (Ellipsizer) getText();
547
548 e.mLayout = this;
549 e.mWidth = ellipsizedWidth;
550 e.mMethod = ellipsize;
551 mEllipsizedWidth = ellipsizedWidth;
552
553 mColumns = COLUMNS_ELLIPSIZE;
554 } else {
555 mColumns = COLUMNS_NORMAL;
556 mEllipsizedWidth = outerwidth;
557 }
558
Siyamed Siniraf398512017-07-25 19:08:42 -0700559 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2);
560 mLines = ArrayUtils.newUnpaddedIntArray(2 * mColumns);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700561 mMaximumVisibleLineCount = maxLines;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800562
Raph Levien70616ec2015-03-04 10:41:30 -0800563 generate(b, b.mIncludePad, b.mIncludePad);
Doug Felte8e45f22010-03-29 14:58:40 -0700564
Raph Leviend3ab6922015-03-02 14:30:53 -0800565 Builder.recycle(b);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800566 }
567
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700568 /* package */ StaticLayout(@Nullable CharSequence text) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700569 super(text, null, 0, null, 0, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800570
571 mColumns = COLUMNS_ELLIPSIZE;
Siyamed Siniraf398512017-07-25 19:08:42 -0700572 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2);
573 mLines = ArrayUtils.newUnpaddedIntArray(2 * mColumns);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800574 }
575
Raph Levien39b4db72015-03-25 13:18:20 -0700576 private StaticLayout(Builder b) {
577 super((b.mEllipsize == null)
578 ? b.mText
579 : (b.mText instanceof Spanned)
580 ? new SpannedEllipsizer(b.mText)
581 : new Ellipsizer(b.mText),
Roozbeh Pournader158dfaf2017-09-21 13:23:50 -0700582 b.mPaint, b.mWidth, b.mAlignment, b.mTextDir, b.mSpacingMult, b.mSpacingAdd);
Raph Levien39b4db72015-03-25 13:18:20 -0700583
584 if (b.mEllipsize != null) {
585 Ellipsizer e = (Ellipsizer) getText();
586
587 e.mLayout = this;
588 e.mWidth = b.mEllipsizedWidth;
589 e.mMethod = b.mEllipsize;
590 mEllipsizedWidth = b.mEllipsizedWidth;
591
592 mColumns = COLUMNS_ELLIPSIZE;
593 } else {
594 mColumns = COLUMNS_NORMAL;
595 mEllipsizedWidth = b.mWidth;
596 }
597
Siyamed Siniraf398512017-07-25 19:08:42 -0700598 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2);
599 mLines = ArrayUtils.newUnpaddedIntArray(2 * mColumns);
Raph Levien39b4db72015-03-25 13:18:20 -0700600 mMaximumVisibleLineCount = b.mMaxLines;
601
Raph Levien2ea52902015-07-01 14:39:31 -0700602 mLeftIndents = b.mLeftIndents;
603 mRightIndents = b.mRightIndents;
Roozbeh Pournader9a9179d2017-08-29 14:14:28 -0700604 mLeftPaddings = b.mLeftPaddings;
605 mRightPaddings = b.mRightPaddings;
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700606 setJustificationMode(b.mJustificationMode);
Raph Levien2ea52902015-07-01 14:39:31 -0700607
Raph Levien39b4db72015-03-25 13:18:20 -0700608 generate(b, b.mIncludePad, b.mIncludePad);
609 }
610
Raph Leviend3ab6922015-03-02 14:30:53 -0800611 /* package */ void generate(Builder b, boolean includepad, boolean trackpad) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700612 final CharSequence source = b.mText;
Raph Leviend3ab6922015-03-02 14:30:53 -0800613 int bufStart = b.mStart;
614 int bufEnd = b.mEnd;
615 TextPaint paint = b.mPaint;
616 int outerWidth = b.mWidth;
617 TextDirectionHeuristic textDir = b.mTextDir;
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700618 final boolean fallbackLineSpacing = b.mFallbackLineSpacing;
Raph Leviend3ab6922015-03-02 14:30:53 -0800619 float spacingmult = b.mSpacingMult;
620 float spacingadd = b.mSpacingAdd;
621 float ellipsizedWidth = b.mEllipsizedWidth;
622 TextUtils.TruncateAt ellipsize = b.mEllipsize;
Siyamed Sinir442c1512017-07-24 12:18:27 -0700623 final boolean addLastLineSpacing = b.mAddLastLineLineSpacing;
Raph Levien4c1f12e2015-03-02 16:29:23 -0800624 LineBreaks lineBreaks = new LineBreaks(); // TODO: move to builder to avoid allocation costs
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800625 FloatArray widths = new FloatArray();
Anish Athalye88b5b0b2014-06-24 14:39:43 -0700626
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800627 mLineCount = 0;
Siyamed Sinira19cd512017-08-03 22:01:56 -0700628 mEllipsized = false;
Siyamed Sinira8d982d2017-08-21 13:27:47 -0700629 mMaxLineHeight = mMaximumVisibleLineCount < 1 ? 0 : DEFAULT_MAX_LINE_HEIGHT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800630
631 int v = 0;
632 boolean needMultiply = (spacingmult != 1 || spacingadd != 0);
633
Raph Leviend3ab6922015-03-02 14:30:53 -0800634 Paint.FontMetricsInt fm = b.mFontMetricsInt;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800635 int[] chooseHtv = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800636
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800637 Spanned spanned = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800638 if (source instanceof Spanned)
639 spanned = (Spanned) source;
640
Seigo Nonakabafe1972017-08-24 15:30:29 -0700641 final int[] indents;
642 if (mLeftIndents != null || mRightIndents != null) {
643 final int leftLen = mLeftIndents == null ? 0 : mLeftIndents.length;
644 final int rightLen = mRightIndents == null ? 0 : mRightIndents.length;
645 final int indentsLen = Math.max(leftLen, rightLen);
646 indents = new int[indentsLen];
647 for (int i = 0; i < leftLen; i++) {
648 indents[i] = mLeftIndents[i];
649 }
650 for (int i = 0; i < rightLen; i++) {
651 indents[i] += mRightIndents[i];
652 }
653 } else {
654 indents = null;
655 }
656
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700657 final long nativePtr = nInit(
658 b.mBreakStrategy, b.mHyphenationFrequency,
659 // TODO: Support more justification mode, e.g. letter spacing, stretching.
660 b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE,
661 indents, mLeftPaddings, mRightPaddings);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800662
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800663 MeasuredText measured = null;
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700664 try {
665 int paraEnd;
666 for (int paraStart = bufStart; paraStart <= bufEnd; paraStart = paraEnd) {
667 paraEnd = TextUtils.indexOf(source, CHAR_NEW_LINE, paraStart, bufEnd);
668 if (paraEnd < 0) {
669 paraEnd = bufEnd;
670 } else {
671 paraEnd++;
672 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800673
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700674 int firstWidthLineCount = 1;
675 int firstWidth = outerWidth;
676 int restWidth = outerWidth;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800677
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700678 LineHeightSpan[] chooseHt = null;
Doug Feltcb3791202011-07-07 11:57:48 -0700679
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700680 if (spanned != null) {
681 LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
682 LeadingMarginSpan.class);
683 for (int i = 0; i < sp.length; i++) {
684 LeadingMarginSpan lms = sp[i];
685 firstWidth -= sp[i].getLeadingMargin(true);
686 restWidth -= sp[i].getLeadingMargin(false);
687
688 // LeadingMarginSpan2 is odd. The count affects all
689 // leading margin spans, not just this particular one
690 if (lms instanceof LeadingMarginSpan2) {
691 LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
692 firstWidthLineCount = Math.max(firstWidthLineCount,
693 lms2.getLeadingMarginLineCount());
694 }
695 }
696
697 chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
698
699 if (chooseHt.length == 0) {
700 chooseHt = null; // So that out() would not assume it has any contents
701 } else {
702 if (chooseHtv == null || chooseHtv.length < chooseHt.length) {
703 chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
704 }
705
706 for (int i = 0; i < chooseHt.length; i++) {
707 int o = spanned.getSpanStart(chooseHt[i]);
708
709 if (o < paraStart) {
710 // starts in this layout, before the
711 // current paragraph
712
713 chooseHtv[i] = getLineTop(getLineForOffset(o));
714 } else {
715 // starts in this paragraph
716
717 chooseHtv[i] = v;
718 }
719 }
Mark Wagner7b5676e2009-10-16 11:44:23 -0700720 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800721 }
722
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700723 // tab stop locations
724 int[] variableTabStops = null;
725 if (spanned != null) {
726 TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
727 paraEnd, TabStopSpan.class);
728 if (spans.length > 0) {
729 int[] stops = new int[spans.length];
730 for (int i = 0; i < spans.length; i++) {
731 stops[i] = spans[i].getTabStop();
732 }
733 Arrays.sort(stops, 0, stops.length);
734 variableTabStops = stops;
735 }
736 }
737
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800738 measured = MeasuredText.buildForStaticLayout(
Seigo Nonaka6f1868b2017-12-01 16:24:19 -0800739 paint, source, paraStart, paraEnd, textDir, measured);
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800740 final char[] chs = measured.getChars();
741 final int[] spanEndCache = measured.getSpanEndCache().getRawArray();
742 final int[] fmCache = measured.getFontMetrics().getRawArray();
Seigo Nonaka6f1868b2017-12-01 16:24:19 -0800743 // TODO: Stop keeping duplicated width copy in native and Java.
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800744 widths.resize(chs.length);
745
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700746 // measurement has to be done before performing line breaking
747 // but we don't want to recompute fontmetrics or span ranges the
748 // second time, so we cache those and then use those stored values
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700749
750 int breakCount = nComputeLineBreaks(
751 nativePtr,
752
753 // Inputs
754 chs,
Seigo Nonaka6f1868b2017-12-01 16:24:19 -0800755 measured.getNativePtr(),
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700756 paraEnd - paraStart,
757 firstWidth,
758 firstWidthLineCount,
759 restWidth,
760 variableTabStops,
761 TAB_INCREMENT,
762 mLineCount,
763
764 // Outputs
765 lineBreaks,
766 lineBreaks.breaks.length,
767 lineBreaks.breaks,
768 lineBreaks.widths,
769 lineBreaks.ascents,
770 lineBreaks.descents,
771 lineBreaks.flags,
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800772 widths.getRawArray());
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700773
774 final int[] breaks = lineBreaks.breaks;
775 final float[] lineWidths = lineBreaks.widths;
776 final float[] ascents = lineBreaks.ascents;
777 final float[] descents = lineBreaks.descents;
778 final int[] flags = lineBreaks.flags;
779
780 final int remainingLineCount = mMaximumVisibleLineCount - mLineCount;
781 final boolean ellipsisMayBeApplied = ellipsize != null
782 && (ellipsize == TextUtils.TruncateAt.END
783 || (mMaximumVisibleLineCount == 1
784 && ellipsize != TextUtils.TruncateAt.MARQUEE));
785 if (0 < remainingLineCount && remainingLineCount < breakCount
786 && ellipsisMayBeApplied) {
787 // Calculate width and flag.
788 float width = 0;
789 int flag = 0; // XXX May need to also have starting hyphen edit
790 for (int i = remainingLineCount - 1; i < breakCount; i++) {
791 if (i == breakCount - 1) {
792 width += lineWidths[i];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800793 } else {
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700794 for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) {
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800795 width += widths.get(j);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700796 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800797 }
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700798 flag |= flags[i] & TAB_MASK;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800799 }
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700800 // Treat the last line and overflowed lines as a single line.
801 breaks[remainingLineCount - 1] = breaks[breakCount - 1];
802 lineWidths[remainingLineCount - 1] = width;
803 flags[remainingLineCount - 1] = flag;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800804
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700805 breakCount = remainingLineCount;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700806 }
807
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700808 // here is the offset of the starting character of the line we are currently
809 // measuring
810 int here = paraStart;
Doug Felte8e45f22010-03-29 14:58:40 -0700811
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700812 int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
813 int fmCacheIndex = 0;
814 int spanEndCacheIndex = 0;
815 int breakIndex = 0;
816 for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
817 // retrieve end of span
818 spanEnd = spanEndCache[spanEndCacheIndex++];
Doug Felt23241882010-06-02 14:41:06 -0700819
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700820 // retrieve cached metrics, order matches above
821 fm.top = fmCache[fmCacheIndex * 4 + 0];
822 fm.bottom = fmCache[fmCacheIndex * 4 + 1];
823 fm.ascent = fmCache[fmCacheIndex * 4 + 2];
824 fm.descent = fmCache[fmCacheIndex * 4 + 3];
825 fmCacheIndex++;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800826
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700827 if (fm.top < fmTop) {
Anish Athalyec8f9e622014-07-21 15:26:34 -0700828 fmTop = fm.top;
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700829 }
830 if (fm.ascent < fmAscent) {
Anish Athalyec8f9e622014-07-21 15:26:34 -0700831 fmAscent = fm.ascent;
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700832 }
833 if (fm.descent > fmDescent) {
Anish Athalyec8f9e622014-07-21 15:26:34 -0700834 fmDescent = fm.descent;
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700835 }
836 if (fm.bottom > fmBottom) {
837 fmBottom = fm.bottom;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700838 }
839
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700840 // skip breaks ending before current span range
841 while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
842 breakIndex++;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700843 }
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700844
845 while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
846 int endPos = paraStart + breaks[breakIndex];
847
848 boolean moreChars = (endPos < bufEnd);
849
850 final int ascent = fallbackLineSpacing
851 ? Math.min(fmAscent, Math.round(ascents[breakIndex]))
852 : fmAscent;
853 final int descent = fallbackLineSpacing
854 ? Math.max(fmDescent, Math.round(descents[breakIndex]))
855 : fmDescent;
856 v = out(source, here, endPos,
857 ascent, descent, fmTop, fmBottom,
858 v, spacingmult, spacingadd, chooseHt, chooseHtv, fm,
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800859 flags[breakIndex], needMultiply, measured, bufEnd,
860 includepad, trackpad, addLastLineSpacing, chs, widths.getRawArray(),
861 paraStart, ellipsize, ellipsizedWidth, lineWidths[breakIndex],
862 paint, moreChars);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700863
864 if (endPos < spanEnd) {
865 // preserve metrics for current span
866 fmTop = fm.top;
867 fmBottom = fm.bottom;
868 fmAscent = fm.ascent;
869 fmDescent = fm.descent;
870 } else {
871 fmTop = fmBottom = fmAscent = fmDescent = 0;
872 }
873
874 here = endPos;
875 breakIndex++;
876
877 if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) {
878 return;
879 }
880 }
881 }
882
883 if (paraEnd == bufEnd) {
884 break;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700885 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800886 }
887
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700888 if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE)
889 && mLineCount < mMaximumVisibleLineCount) {
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700890 paint.getFontMetricsInt(fm);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700891 v = out(source,
892 bufEnd, bufEnd, fm.ascent, fm.descent,
893 fm.top, fm.bottom,
894 v,
895 spacingmult, spacingadd, null,
896 null, fm, 0,
Seigo Nonaka6f1868b2017-12-01 16:24:19 -0800897 needMultiply, null, bufEnd,
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700898 includepad, trackpad, addLastLineSpacing, null,
899 null, bufStart, ellipsize,
900 ellipsizedWidth, 0, paint, false);
901 }
902 } finally {
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800903 if (measured != null) {
904 measured.recycle();
905 }
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700906 nFinish(nativePtr);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800907 }
908 }
909
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700910 // The parameters that are not changed in the method are marked as final to make the code
911 // easier to understand.
912 private int out(final CharSequence text, final int start, final int end, int above, int below,
913 int top, int bottom, int v, final float spacingmult, final float spacingadd,
914 final LineHeightSpan[] chooseHt, final int[] chooseHtv, final Paint.FontMetricsInt fm,
Seigo Nonaka6f1868b2017-12-01 16:24:19 -0800915 final int flags, final boolean needMultiply, @Nullable final MeasuredText measured,
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800916 final int bufEnd, final boolean includePad, final boolean trackPad,
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700917 final boolean addLastLineLineSpacing, final char[] chs, final float[] widths,
918 final int widthStart, final TextUtils.TruncateAt ellipsize, final float ellipsisWidth,
919 final float textWidth, final TextPaint paint, final boolean moreChars) {
Siyamed Siniraf398512017-07-25 19:08:42 -0700920 final int j = mLineCount;
921 final int off = j * mColumns;
922 final int want = off + mColumns + TOP;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800923 int[] lines = mLines;
Seigo Nonaka6f1868b2017-12-01 16:24:19 -0800924 final int dir = (start == end) ? Layout.DIR_LEFT_TO_RIGHT : measured.getParagraphDir();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800925
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800926 if (want >= lines.length) {
Siyamed Siniraf398512017-07-25 19:08:42 -0700927 final int[] grow = ArrayUtils.newUnpaddedIntArray(GrowingArrayUtils.growSize(want));
Adam Lesinski776abc22014-03-07 11:30:59 -0500928 System.arraycopy(lines, 0, grow, 0, lines.length);
929 mLines = grow;
930 lines = grow;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800931 }
932
Siyamed Siniraf398512017-07-25 19:08:42 -0700933 if (j >= mLineDirections.length) {
934 final Directions[] grow = ArrayUtils.newUnpaddedArray(Directions.class,
935 GrowingArrayUtils.growSize(j));
936 System.arraycopy(mLineDirections, 0, grow, 0, mLineDirections.length);
937 mLineDirections = grow;
938 }
939
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700940 lines[off + START] = start;
941 lines[off + TOP] = v;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800942
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700943 // Information about hyphenation, tabs, and directions are needed for determining
944 // ellipsization, so the values should be assigned before ellipsization.
Eric Fischera9f1dd02009-08-12 15:00:10 -0700945
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700946 // TODO: could move TAB to share same column as HYPHEN, simplifying this code and gaining
947 // one bit for start field
948 lines[off + TAB] |= flags & TAB_MASK;
949 lines[off + HYPHEN] = flags;
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700950 lines[off + DIR] |= dir << DIR_SHIFT;
Seigo Nonaka6f1868b2017-12-01 16:24:19 -0800951 if (start == end) {
952 mLineDirections[j] = Layout.DIRS_ALL_LEFT_TO_RIGHT;
953 } else {
954 mLineDirections[j] = measured.getDirections(start - widthStart, end - widthStart);
955 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800956
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700957 final boolean firstLine = (j == 0);
958 final boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);
Siyamed Sinir0745c722016-05-31 20:39:33 -0700959
960 if (ellipsize != null) {
961 // If there is only one line, then do any type of ellipsis except when it is MARQUEE
962 // if there are multiple lines, just allow END ellipsis on the last line
963 boolean forceEllipsis = moreChars && (mLineCount + 1 == mMaximumVisibleLineCount);
964
965 boolean doEllipsis =
966 (((mMaximumVisibleLineCount == 1 && moreChars) || (firstLine && !moreChars)) &&
967 ellipsize != TextUtils.TruncateAt.MARQUEE) ||
968 (!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
969 ellipsize == TextUtils.TruncateAt.END);
970 if (doEllipsis) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700971 calculateEllipsis(text, start, end, widths, widthStart,
972 ellipsisWidth - getTotalInsets(j), ellipsize, j,
973 textWidth, paint, forceEllipsis, dir);
Siyamed Sinir0745c722016-05-31 20:39:33 -0700974 }
975 }
976
Siyamed Sinirfb0b2dc2017-07-26 22:08:24 -0700977 final boolean lastLine;
978 if (mEllipsized) {
979 lastLine = true;
980 } else {
981 final boolean lastCharIsNewLine = widthStart != bufEnd && bufEnd > 0
982 && text.charAt(bufEnd - 1) == CHAR_NEW_LINE;
983 if (end == bufEnd && !lastCharIsNewLine) {
984 lastLine = true;
985 } else if (start == bufEnd && lastCharIsNewLine) {
986 lastLine = true;
987 } else {
988 lastLine = false;
989 }
990 }
Raph Leviend97b0972014-04-24 12:51:35 -0700991
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700992 if (chooseHt != null) {
993 fm.ascent = above;
994 fm.descent = below;
995 fm.top = top;
996 fm.bottom = bottom;
997
998 for (int i = 0; i < chooseHt.length; i++) {
999 if (chooseHt[i] instanceof LineHeightSpan.WithDensity) {
1000 ((LineHeightSpan.WithDensity) chooseHt[i])
1001 .chooseHeight(text, start, end, chooseHtv[i], v, fm, paint);
1002
1003 } else {
1004 chooseHt[i].chooseHeight(text, start, end, chooseHtv[i], v, fm);
1005 }
1006 }
1007
1008 above = fm.ascent;
1009 below = fm.descent;
1010 top = fm.top;
1011 bottom = fm.bottom;
1012 }
1013
Raph Leviend97b0972014-04-24 12:51:35 -07001014 if (firstLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001015 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001016 mTopPadding = top - above;
1017 }
1018
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001019 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001020 above = top;
1021 }
1022 }
Raph Leviend97b0972014-04-24 12:51:35 -07001023
Raph Leviend97b0972014-04-24 12:51:35 -07001024 if (lastLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001025 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001026 mBottomPadding = bottom - below;
1027 }
1028
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001029 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001030 below = bottom;
1031 }
1032 }
1033
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001034 final int extra;
Siyamed Sinir442c1512017-07-24 12:18:27 -07001035 if (needMultiply && (addLastLineLineSpacing || !lastLine)) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001036 final double ex = (below - above) * (spacingmult - 1) + spacingadd;
Doug Felt10657582010-02-22 11:19:01 -08001037 if (ex >= 0) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001038 extra = (int)(ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -08001039 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001040 extra = -(int)(-ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -08001041 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001042 } else {
1043 extra = 0;
1044 }
1045
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001046 lines[off + DESCENT] = below + extra;
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001047 lines[off + EXTRA] = extra;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001048
Siyamed Sinir0745c722016-05-31 20:39:33 -07001049 // special case for non-ellipsized last visible line when maxLines is set
1050 // store the height as if it was ellipsized
1051 if (!mEllipsized && currentLineIsTheLastVisibleOne) {
1052 // below calculation as if it was the last line
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001053 final int maxLineBelow = includePad ? bottom : below;
Siyamed Sinir0745c722016-05-31 20:39:33 -07001054 // similar to the calculation of v below, without the extra.
1055 mMaxLineHeight = v + (maxLineBelow - above);
1056 }
1057
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001058 v += (below - above) + extra;
1059 lines[off + mColumns + START] = end;
1060 lines[off + mColumns + TOP] = v;
1061
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001062 mLineCount++;
1063 return v;
1064 }
1065
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001066 private void calculateEllipsis(CharSequence text, int lineStart, int lineEnd, float[] widths,
1067 int widthStart, float avail, TextUtils.TruncateAt where, int line, float textWidth,
1068 TextPaint paint, boolean forceEllipsis, int dir) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001069 if (textWidth <= avail && !forceEllipsis) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001070 // Everything fits!
1071 mLines[mColumns * line + ELLIPSIS_START] = 0;
1072 mLines[mColumns * line + ELLIPSIS_COUNT] = 0;
1073 return;
1074 }
1075
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001076 float tempAvail = avail;
1077 int numberOfTries = 0;
1078 boolean lineFits = false;
1079 mWorkPaint.set(paint);
1080 do {
1081 final float ellipsizedWidth = guessEllipsis(text, lineStart, lineEnd, widths,
Siyamed Sinira273a702017-10-05 11:22:12 -07001082 widthStart, tempAvail, where, line, mWorkPaint, forceEllipsis, dir);
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001083 if (ellipsizedWidth <= avail) {
1084 lineFits = true;
1085 } else {
1086 numberOfTries++;
1087 if (numberOfTries > 10) {
1088 // If the text still doesn't fit after ten tries, assume it will never fit and
1089 // ellipsize it all.
1090 mLines[mColumns * line + ELLIPSIS_START] = 0;
1091 mLines[mColumns * line + ELLIPSIS_COUNT] = lineEnd - lineStart;
1092 lineFits = true;
1093 } else {
1094 // Some side effect of ellipsization has caused the text to go over the
1095 // available width. Let's make the available width shorter by exactly that
1096 // amount and retry.
1097 tempAvail -= ellipsizedWidth - avail;
1098 }
1099 }
1100 } while (!lineFits);
1101 mEllipsized = true;
1102 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001103
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001104 // Returns the width of the ellipsized line which in some rare cases can actually be larger
1105 // than 'avail' (due to kerning or other context-based effect of replacement of text by
1106 // ellipsis). If all the line needs to ellipsized away, or it's an invalud hyphenation mode,
1107 // returns 0 so the caller can stop iterating.
1108 //
1109 // This method temporarily modifies the TextPaint passed to it, so the TextPaint passed to it
1110 // should not be accessed while the method is running.
1111 private float guessEllipsis(CharSequence text, int lineStart, int lineEnd, float[] widths,
Siyamed Sinira273a702017-10-05 11:22:12 -07001112 int widthStart, float avail, TextUtils.TruncateAt where, int line,
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001113 TextPaint paint, boolean forceEllipsis, int dir) {
1114 final int savedHyphenEdit = paint.getHyphenEdit();
1115 paint.setHyphenEdit(0);
1116 final float ellipsisWidth = paint.measureText(TextUtils.getEllipsisString(where));
1117 final int ellipsisStart;
1118 final int ellipsisCount;
1119 final int len = lineEnd - lineStart;
1120 final int offset = lineStart - widthStart;
1121
1122 int hyphen = getHyphen(line);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001123 // We only support start ellipsis on a single line
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001124 if (where == TextUtils.TruncateAt.START) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001125 if (mMaximumVisibleLineCount == 1) {
1126 float sum = 0;
1127 int i;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001128
Keisuke Kuroyanagied2eea12015-04-14 18:18:35 +09001129 for (i = len; i > 0; i--) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001130 final float w = widths[i - 1 + offset];
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001131 if (w + sum + ellipsisWidth > avail) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001132 while (i < len && widths[i + offset] == 0.0f) {
Keisuke Kuroyanagi82d1c442016-09-14 14:30:14 +09001133 i++;
1134 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001135 break;
1136 }
1137
1138 sum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001139 }
1140
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001141 ellipsisStart = 0;
1142 ellipsisCount = i;
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001143 // Strip the potential hyphenation at beginning of line.
1144 hyphen &= ~Paint.HYPHENEDIT_MASK_START_OF_LINE;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001145 } else {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001146 ellipsisStart = 0;
1147 ellipsisCount = 0;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001148 if (Log.isLoggable(TAG, Log.WARN)) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001149 Log.w(TAG, "Start ellipsis only supported with one line");
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001150 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001151 }
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001152 } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE ||
1153 where == TextUtils.TruncateAt.END_SMALL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001154 float sum = 0;
1155 int i;
1156
1157 for (i = 0; i < len; i++) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001158 final float w = widths[i + offset];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001159
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001160 if (w + sum + ellipsisWidth > avail) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001161 break;
1162 }
1163
1164 sum += w;
1165 }
1166
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001167 if (forceEllipsis && i == len && len > 0) {
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -07001168 ellipsisStart = len - 1;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001169 ellipsisCount = 1;
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001170 } else {
1171 ellipsisStart = i;
1172 ellipsisCount = len - i;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001173 }
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001174 // Strip the potential hyphenation at end of line.
1175 hyphen &= ~Paint.HYPHENEDIT_MASK_END_OF_LINE;
1176 } else { // where = TextUtils.TruncateAt.MIDDLE
1177 // We only support middle ellipsis on a single line.
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001178 if (mMaximumVisibleLineCount == 1) {
1179 float lsum = 0, rsum = 0;
1180 int left = 0, right = len;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001181
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001182 final float ravail = (avail - ellipsisWidth) / 2;
Raph Levien0e3c5e82014-12-04 13:26:07 -08001183 for (right = len; right > 0; right--) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001184 final float w = widths[right - 1 + offset];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001185
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001186 if (w + rsum > ravail) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001187 while (right < len && widths[right + offset] == 0.0f) {
Keisuke Kuroyanagi82d1c442016-09-14 14:30:14 +09001188 right++;
1189 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001190 break;
1191 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001192 rsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001193 }
1194
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001195 final float lavail = avail - ellipsisWidth - rsum;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001196 for (left = 0; left < right; left++) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001197 final float w = widths[left + offset];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001198
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001199 if (w + lsum > lavail) {
1200 break;
1201 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001202
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001203 lsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001204 }
1205
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001206 ellipsisStart = left;
1207 ellipsisCount = right - left;
1208 } else {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001209 ellipsisStart = 0;
1210 ellipsisCount = 0;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001211 if (Log.isLoggable(TAG, Log.WARN)) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001212 Log.w(TAG, "Middle ellipsis only supported with one line");
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001213 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001214 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001215 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001216 mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
1217 mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001218
1219 if (ellipsisStart == 0 && (ellipsisCount == 0 || ellipsisCount == len)) {
1220 // Unsupported ellipsization mode or all text is ellipsized away. Return 0.
1221 return 0.0f;
1222 }
1223
1224 final boolean isSpanned = text instanceof Spanned;
1225 final Ellipsizer ellipsizedText = isSpanned
1226 ? new SpannedEllipsizer(text)
1227 : new Ellipsizer(text);
1228 ellipsizedText.mLayout = this;
1229 ellipsizedText.mMethod = where;
1230
1231 final boolean hasTabs = getLineContainsTab(line);
1232 final TabStops tabStops;
1233 if (hasTabs && isSpanned) {
1234 final TabStopSpan[] tabs = getParagraphSpans((Spanned) ellipsizedText, lineStart,
1235 lineEnd, TabStopSpan.class);
1236 if (tabs.length == 0) {
1237 tabStops = null;
1238 } else {
1239 tabStops = new TabStops(TAB_INCREMENT, tabs);
1240 }
1241 } else {
1242 tabStops = null;
1243 }
1244 paint.setHyphenEdit(hyphen);
1245 final TextLine textline = TextLine.obtain();
1246 textline.set(paint, ellipsizedText, lineStart, lineEnd, dir, getLineDirections(line),
1247 hasTabs, tabStops);
1248 // Since TextLine.metric() returns negative values for RTL text, multiplication by dir
1249 // converts it to an actual width. Note that we don't want to use the absolute value,
1250 // since we may actually have glyphs with negative advances, which by definition always
1251 // fit.
1252 final float ellipsizedWidth = textline.metrics(null) * dir;
1253 TextLine.recycle(textline);
1254 paint.setHyphenEdit(savedHyphenEdit);
1255 return ellipsizedWidth;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001256 }
1257
Selim Cinek365ec092017-03-09 00:10:52 -08001258 private float getTotalInsets(int line) {
1259 int totalIndent = 0;
1260 if (mLeftIndents != null) {
1261 totalIndent = mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1262 }
1263 if (mRightIndents != null) {
1264 totalIndent += mRightIndents[Math.min(line, mRightIndents.length - 1)];
1265 }
1266 return totalIndent;
1267 }
1268
Doug Felte8e45f22010-03-29 14:58:40 -07001269 // Override the base class so we can directly access our members,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001270 // rather than relying on member functions.
1271 // The logic mirrors that of Layout.getLineForVertical
1272 // FIXME: It may be faster to do a linear search for layouts without many lines.
Gilles Debunne66111472010-11-19 11:04:37 -08001273 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001274 public int getLineForVertical(int vertical) {
1275 int high = mLineCount;
1276 int low = -1;
1277 int guess;
1278 int[] lines = mLines;
1279 while (high - low > 1) {
1280 guess = (high + low) >> 1;
1281 if (lines[mColumns * guess + TOP] > vertical){
1282 high = guess;
1283 } else {
1284 low = guess;
1285 }
1286 }
1287 if (low < 0) {
1288 return 0;
1289 } else {
1290 return low;
1291 }
1292 }
1293
Gilles Debunne66111472010-11-19 11:04:37 -08001294 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001295 public int getLineCount() {
1296 return mLineCount;
1297 }
1298
Gilles Debunne66111472010-11-19 11:04:37 -08001299 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001300 public int getLineTop(int line) {
Raph Levien07e6c232016-04-04 12:34:06 -07001301 return mLines[mColumns * line + TOP];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001302 }
1303
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001304 /**
1305 * @hide
1306 */
1307 @Override
1308 public int getLineExtra(int line) {
1309 return mLines[mColumns * line + EXTRA];
1310 }
1311
Gilles Debunne66111472010-11-19 11:04:37 -08001312 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001313 public int getLineDescent(int line) {
Raph Levien07e6c232016-04-04 12:34:06 -07001314 return mLines[mColumns * line + DESCENT];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001315 }
1316
Gilles Debunne66111472010-11-19 11:04:37 -08001317 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001318 public int getLineStart(int line) {
1319 return mLines[mColumns * line + START] & START_MASK;
1320 }
1321
Gilles Debunne66111472010-11-19 11:04:37 -08001322 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001323 public int getParagraphDirection(int line) {
1324 return mLines[mColumns * line + DIR] >> DIR_SHIFT;
1325 }
1326
Gilles Debunne66111472010-11-19 11:04:37 -08001327 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001328 public boolean getLineContainsTab(int line) {
1329 return (mLines[mColumns * line + TAB] & TAB_MASK) != 0;
1330 }
1331
Gilles Debunne66111472010-11-19 11:04:37 -08001332 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001333 public final Directions getLineDirections(int line) {
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001334 if (line > getLineCount()) {
1335 throw new ArrayIndexOutOfBoundsException();
1336 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001337 return mLineDirections[line];
1338 }
1339
Gilles Debunne66111472010-11-19 11:04:37 -08001340 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001341 public int getTopPadding() {
1342 return mTopPadding;
1343 }
1344
Gilles Debunne66111472010-11-19 11:04:37 -08001345 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001346 public int getBottomPadding() {
1347 return mBottomPadding;
1348 }
1349
Raph Levien26d443a2015-03-30 14:18:32 -07001350 /**
1351 * @hide
1352 */
1353 @Override
1354 public int getHyphen(int line) {
Keisuke Kuroyanagif5af4a32016-08-31 21:40:53 +09001355 return mLines[mColumns * line + HYPHEN] & HYPHEN_MASK;
Raph Levien26d443a2015-03-30 14:18:32 -07001356 }
1357
Raph Levien2ea52902015-07-01 14:39:31 -07001358 /**
1359 * @hide
1360 */
1361 @Override
1362 public int getIndentAdjust(int line, Alignment align) {
1363 if (align == Alignment.ALIGN_LEFT) {
1364 if (mLeftIndents == null) {
1365 return 0;
1366 } else {
1367 return mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1368 }
1369 } else if (align == Alignment.ALIGN_RIGHT) {
1370 if (mRightIndents == null) {
1371 return 0;
1372 } else {
1373 return -mRightIndents[Math.min(line, mRightIndents.length - 1)];
1374 }
1375 } else if (align == Alignment.ALIGN_CENTER) {
1376 int left = 0;
1377 if (mLeftIndents != null) {
1378 left = mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1379 }
1380 int right = 0;
1381 if (mRightIndents != null) {
1382 right = mRightIndents[Math.min(line, mRightIndents.length - 1)];
1383 }
1384 return (left - right) >> 1;
1385 } else {
1386 throw new AssertionError("unhandled alignment " + align);
1387 }
1388 }
1389
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001390 @Override
1391 public int getEllipsisCount(int line) {
1392 if (mColumns < COLUMNS_ELLIPSIZE) {
1393 return 0;
1394 }
1395
1396 return mLines[mColumns * line + ELLIPSIS_COUNT];
1397 }
1398
1399 @Override
1400 public int getEllipsisStart(int line) {
1401 if (mColumns < COLUMNS_ELLIPSIZE) {
1402 return 0;
1403 }
1404
1405 return mLines[mColumns * line + ELLIPSIS_START];
1406 }
1407
1408 @Override
1409 public int getEllipsizedWidth() {
1410 return mEllipsizedWidth;
1411 }
1412
Siyamed Sinir0745c722016-05-31 20:39:33 -07001413 /**
1414 * Return the total height of this layout.
1415 *
1416 * @param cap if true and max lines is set, returns the height of the layout at the max lines.
1417 *
1418 * @hide
1419 */
1420 public int getHeight(boolean cap) {
1421 if (cap && mLineCount >= mMaximumVisibleLineCount && mMaxLineHeight == -1 &&
1422 Log.isLoggable(TAG, Log.WARN)) {
1423 Log.w(TAG, "maxLineHeight should not be -1. "
1424 + " maxLines:" + mMaximumVisibleLineCount
1425 + " lineCount:" + mLineCount);
1426 }
1427
1428 return cap && mLineCount >= mMaximumVisibleLineCount && mMaxLineHeight != -1 ?
1429 mMaxLineHeight : super.getHeight();
1430 }
1431
Seigo Nonakab90e08f2017-10-20 15:23:51 -07001432 @FastNative
1433 private static native long nInit(
1434 @BreakStrategy int breakStrategy,
1435 @HyphenationFrequency int hyphenationFrequency,
1436 boolean isJustified,
1437 @Nullable int[] indents,
1438 @Nullable int[] leftPaddings,
1439 @Nullable int[] rightPaddings);
1440
1441 @CriticalNative
1442 private static native void nFinish(long nativePtr);
1443
Anish Athalyec8f9e622014-07-21 15:26:34 -07001444 // populates LineBreaks and returns the number of breaks found
1445 //
1446 // the arrays inside the LineBreaks objects are passed in as well
1447 // to reduce the number of JNI calls in the common case where the
1448 // arrays do not have to be resized
Seigo Nonaka2dd1a552017-10-06 11:41:00 -07001449 // The individual character widths will be returned in charWidths. The length of charWidths must
1450 // be at least the length of the text.
Seigo Nonakab90e08f2017-10-20 15:23:51 -07001451 private static native int nComputeLineBreaks(
1452 /* non zero */ long nativePtr,
1453
1454 // Inputs
1455 @NonNull char[] text,
Seigo Nonaka6f1868b2017-12-01 16:24:19 -08001456 /* Non Zero */ long measuredTextPtr,
Seigo Nonakab90e08f2017-10-20 15:23:51 -07001457 @IntRange(from = 0) int length,
1458 @FloatRange(from = 0.0f) float firstWidth,
1459 @IntRange(from = 0) int firstWidthLineCount,
1460 @FloatRange(from = 0.0f) float restWidth,
1461 @Nullable int[] variableTabStops,
1462 int defaultTabStop,
1463 @IntRange(from = 0) int indentsOffset,
1464
1465 // Outputs
1466 @NonNull LineBreaks recycle,
1467 @IntRange(from = 0) int recycleLength,
1468 @NonNull int[] recycleBreaks,
1469 @NonNull float[] recycleWidths,
1470 @NonNull float[] recycleAscents,
1471 @NonNull float[] recycleDescents,
1472 @NonNull int[] recycleFlags,
1473 @NonNull float[] charWidths);
Anish Athalye88b5b0b2014-06-24 14:39:43 -07001474
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001475 private int mLineCount;
1476 private int mTopPadding, mBottomPadding;
1477 private int mColumns;
1478 private int mEllipsizedWidth;
1479
Siyamed Sinir0745c722016-05-31 20:39:33 -07001480 /**
1481 * Keeps track if ellipsize is applied to the text.
1482 */
1483 private boolean mEllipsized;
1484
1485 /**
1486 * If maxLines is set, ellipsize is not set, and the actual line count of text is greater than
1487 * or equal to maxLine, this variable holds the ideal visual height of the maxLine'th line
1488 * starting from the top of the layout. If maxLines is not set its value will be -1.
1489 *
1490 * The value is the same as getLineTop(maxLines) for ellipsized version where structurally no
1491 * more than maxLines is contained.
1492 */
Siyamed Sinira19cd512017-08-03 22:01:56 -07001493 private int mMaxLineHeight = DEFAULT_MAX_LINE_HEIGHT;
Siyamed Sinir0745c722016-05-31 20:39:33 -07001494
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001495 private TextPaint mWorkPaint = new TextPaint();
1496
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001497 private static final int COLUMNS_NORMAL = 5;
1498 private static final int COLUMNS_ELLIPSIZE = 7;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001499 private static final int START = 0;
1500 private static final int DIR = START;
1501 private static final int TAB = START;
1502 private static final int TOP = 1;
1503 private static final int DESCENT = 2;
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001504 private static final int EXTRA = 3;
1505 private static final int HYPHEN = 4;
1506 private static final int ELLIPSIS_START = 5;
1507 private static final int ELLIPSIS_COUNT = 6;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001508
1509 private int[] mLines;
1510 private Directions[] mLineDirections;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001511 private int mMaximumVisibleLineCount = Integer.MAX_VALUE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001512
1513 private static final int START_MASK = 0x1FFFFFFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001514 private static final int DIR_SHIFT = 30;
1515 private static final int TAB_MASK = 0x20000000;
Keisuke Kuroyanagif5af4a32016-08-31 21:40:53 +09001516 private static final int HYPHEN_MASK = 0xFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001517
Doug Feltc982f602010-05-25 11:51:40 -07001518 private static final int TAB_INCREMENT = 20; // same as Layout, but that's private
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001519
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001520 private static final char CHAR_NEW_LINE = '\n';
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001521
1522 private static final double EXTRA_ROUNDING = 0.5;
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001523
Siyamed Sinira19cd512017-08-03 22:01:56 -07001524 private static final int DEFAULT_MAX_LINE_HEIGHT = -1;
1525
Anish Athalyec8f9e622014-07-21 15:26:34 -07001526 // This is used to return three arrays from a single JNI call when
1527 // performing line breaking
Deepanshu Gupta70539192014-10-15 15:57:40 -07001528 /*package*/ static class LineBreaks {
Anish Athalyec8f9e622014-07-21 15:26:34 -07001529 private static final int INITIAL_SIZE = 16;
1530 public int[] breaks = new int[INITIAL_SIZE];
1531 public float[] widths = new float[INITIAL_SIZE];
Roozbeh Pournader737dfea2017-08-10 11:32:24 -07001532 public float[] ascents = new float[INITIAL_SIZE];
1533 public float[] descents = new float[INITIAL_SIZE];
Roozbeh Pournader112d9c72015-08-07 12:44:41 -07001534 public int[] flags = new int[INITIAL_SIZE]; // hasTab
Anish Athalyec8f9e622014-07-21 15:26:34 -07001535 // breaks, widths, and flags should all have the same length
1536 }
1537
Seigo Nonakabafe1972017-08-24 15:30:29 -07001538 @Nullable private int[] mLeftIndents;
1539 @Nullable private int[] mRightIndents;
Roozbeh Pournader9a9179d2017-08-29 14:14:28 -07001540 @Nullable private int[] mLeftPaddings;
1541 @Nullable private int[] mRightPaddings;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001542}