blob: 2e10fe8d4267618efc0214839e72dd2a4084efba [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;
Seigo Nonakafbe63bd2017-12-02 19:28:05 -0800613 final int bufStart = b.mStart;
614 final int bufEnd = b.mEnd;
Raph Leviend3ab6922015-03-02 14:30:53 -0800615 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
Seigo Nonakabafe1972017-08-24 15:30:29 -0700637 final int[] indents;
638 if (mLeftIndents != null || mRightIndents != null) {
639 final int leftLen = mLeftIndents == null ? 0 : mLeftIndents.length;
640 final int rightLen = mRightIndents == null ? 0 : mRightIndents.length;
641 final int indentsLen = Math.max(leftLen, rightLen);
642 indents = new int[indentsLen];
643 for (int i = 0; i < leftLen; i++) {
644 indents[i] = mLeftIndents[i];
645 }
646 for (int i = 0; i < rightLen; i++) {
647 indents[i] += mRightIndents[i];
648 }
649 } else {
650 indents = null;
651 }
652
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700653 final long nativePtr = nInit(
654 b.mBreakStrategy, b.mHyphenationFrequency,
655 // TODO: Support more justification mode, e.g. letter spacing, stretching.
656 b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE,
657 indents, mLeftPaddings, mRightPaddings);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800658
Seigo Nonakafbe63bd2017-12-02 19:28:05 -0800659 PremeasuredText premeasured = null;
660 final Spanned spanned;
661 if (source instanceof PremeasuredText) {
662 premeasured = (PremeasuredText) source;
663
664 final CharSequence original = premeasured.getText();
665 spanned = (original instanceof Spanned) ? (Spanned) original : null;
666
667 if (bufStart != premeasured.getStart() || bufEnd != premeasured.getEnd()) {
668 // The buffer position has changed. Re-measure here.
669 premeasured = PremeasuredText.build(original, paint, textDir, bufStart, bufEnd);
670 } else {
671 // We can use premeasured information.
672
673 // Overwrite with the one when premeasured.
674 // TODO: Give an option for developer not to overwrite and measure again here?
675 textDir = premeasured.getTextDir();
676 paint = premeasured.getPaint();
677 }
678 } else {
679 premeasured = PremeasuredText.build(source, paint, textDir, bufStart, bufEnd);
680 spanned = (source instanceof Spanned) ? (Spanned) source : null;
681 }
682
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700683 try {
Seigo Nonakafbe63bd2017-12-02 19:28:05 -0800684 for (int paraIndex = 0; paraIndex < premeasured.getParagraphCount(); paraIndex++) {
685 final int paraStart = premeasured.getParagraphStart(paraIndex);
686 final int paraEnd = premeasured.getParagraphEnd(paraIndex);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800687
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700688 int firstWidthLineCount = 1;
689 int firstWidth = outerWidth;
690 int restWidth = outerWidth;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800691
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700692 LineHeightSpan[] chooseHt = null;
Doug Feltcb3791202011-07-07 11:57:48 -0700693
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700694 if (spanned != null) {
695 LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
696 LeadingMarginSpan.class);
697 for (int i = 0; i < sp.length; i++) {
698 LeadingMarginSpan lms = sp[i];
699 firstWidth -= sp[i].getLeadingMargin(true);
700 restWidth -= sp[i].getLeadingMargin(false);
701
702 // LeadingMarginSpan2 is odd. The count affects all
703 // leading margin spans, not just this particular one
704 if (lms instanceof LeadingMarginSpan2) {
705 LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
706 firstWidthLineCount = Math.max(firstWidthLineCount,
707 lms2.getLeadingMarginLineCount());
708 }
709 }
710
711 chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
712
713 if (chooseHt.length == 0) {
714 chooseHt = null; // So that out() would not assume it has any contents
715 } else {
716 if (chooseHtv == null || chooseHtv.length < chooseHt.length) {
717 chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
718 }
719
720 for (int i = 0; i < chooseHt.length; i++) {
721 int o = spanned.getSpanStart(chooseHt[i]);
722
723 if (o < paraStart) {
724 // starts in this layout, before the
725 // current paragraph
726
727 chooseHtv[i] = getLineTop(getLineForOffset(o));
728 } else {
729 // starts in this paragraph
730
731 chooseHtv[i] = v;
732 }
733 }
Mark Wagner7b5676e2009-10-16 11:44:23 -0700734 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800735 }
736
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700737 // tab stop locations
738 int[] variableTabStops = null;
739 if (spanned != null) {
740 TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
741 paraEnd, TabStopSpan.class);
742 if (spans.length > 0) {
743 int[] stops = new int[spans.length];
744 for (int i = 0; i < spans.length; i++) {
745 stops[i] = spans[i].getTabStop();
746 }
747 Arrays.sort(stops, 0, stops.length);
748 variableTabStops = stops;
749 }
750 }
751
Seigo Nonakafbe63bd2017-12-02 19:28:05 -0800752 final MeasuredText measured = premeasured.getMeasuredText(paraIndex);
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800753 final char[] chs = measured.getChars();
754 final int[] spanEndCache = measured.getSpanEndCache().getRawArray();
755 final int[] fmCache = measured.getFontMetrics().getRawArray();
Seigo Nonaka6f1868b2017-12-01 16:24:19 -0800756 // TODO: Stop keeping duplicated width copy in native and Java.
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800757 widths.resize(chs.length);
758
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700759 // measurement has to be done before performing line breaking
760 // but we don't want to recompute fontmetrics or span ranges the
761 // second time, so we cache those and then use those stored values
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700762
763 int breakCount = nComputeLineBreaks(
764 nativePtr,
765
766 // Inputs
767 chs,
Seigo Nonaka6f1868b2017-12-01 16:24:19 -0800768 measured.getNativePtr(),
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700769 paraEnd - paraStart,
770 firstWidth,
771 firstWidthLineCount,
772 restWidth,
773 variableTabStops,
774 TAB_INCREMENT,
775 mLineCount,
776
777 // Outputs
778 lineBreaks,
779 lineBreaks.breaks.length,
780 lineBreaks.breaks,
781 lineBreaks.widths,
782 lineBreaks.ascents,
783 lineBreaks.descents,
784 lineBreaks.flags,
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800785 widths.getRawArray());
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700786
787 final int[] breaks = lineBreaks.breaks;
788 final float[] lineWidths = lineBreaks.widths;
789 final float[] ascents = lineBreaks.ascents;
790 final float[] descents = lineBreaks.descents;
791 final int[] flags = lineBreaks.flags;
792
793 final int remainingLineCount = mMaximumVisibleLineCount - mLineCount;
794 final boolean ellipsisMayBeApplied = ellipsize != null
795 && (ellipsize == TextUtils.TruncateAt.END
796 || (mMaximumVisibleLineCount == 1
797 && ellipsize != TextUtils.TruncateAt.MARQUEE));
798 if (0 < remainingLineCount && remainingLineCount < breakCount
799 && ellipsisMayBeApplied) {
800 // Calculate width and flag.
801 float width = 0;
802 int flag = 0; // XXX May need to also have starting hyphen edit
803 for (int i = remainingLineCount - 1; i < breakCount; i++) {
804 if (i == breakCount - 1) {
805 width += lineWidths[i];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800806 } else {
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700807 for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) {
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800808 width += widths.get(j);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700809 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800810 }
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700811 flag |= flags[i] & TAB_MASK;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800812 }
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700813 // Treat the last line and overflowed lines as a single line.
814 breaks[remainingLineCount - 1] = breaks[breakCount - 1];
815 lineWidths[remainingLineCount - 1] = width;
816 flags[remainingLineCount - 1] = flag;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800817
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700818 breakCount = remainingLineCount;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700819 }
820
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700821 // here is the offset of the starting character of the line we are currently
822 // measuring
823 int here = paraStart;
Doug Felte8e45f22010-03-29 14:58:40 -0700824
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700825 int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
826 int fmCacheIndex = 0;
827 int spanEndCacheIndex = 0;
828 int breakIndex = 0;
829 for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
830 // retrieve end of span
831 spanEnd = spanEndCache[spanEndCacheIndex++];
Doug Felt23241882010-06-02 14:41:06 -0700832
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700833 // retrieve cached metrics, order matches above
834 fm.top = fmCache[fmCacheIndex * 4 + 0];
835 fm.bottom = fmCache[fmCacheIndex * 4 + 1];
836 fm.ascent = fmCache[fmCacheIndex * 4 + 2];
837 fm.descent = fmCache[fmCacheIndex * 4 + 3];
838 fmCacheIndex++;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800839
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700840 if (fm.top < fmTop) {
Anish Athalyec8f9e622014-07-21 15:26:34 -0700841 fmTop = fm.top;
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700842 }
843 if (fm.ascent < fmAscent) {
Anish Athalyec8f9e622014-07-21 15:26:34 -0700844 fmAscent = fm.ascent;
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700845 }
846 if (fm.descent > fmDescent) {
Anish Athalyec8f9e622014-07-21 15:26:34 -0700847 fmDescent = fm.descent;
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700848 }
849 if (fm.bottom > fmBottom) {
850 fmBottom = fm.bottom;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700851 }
852
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700853 // skip breaks ending before current span range
854 while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
855 breakIndex++;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700856 }
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700857
858 while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
859 int endPos = paraStart + breaks[breakIndex];
860
861 boolean moreChars = (endPos < bufEnd);
862
863 final int ascent = fallbackLineSpacing
864 ? Math.min(fmAscent, Math.round(ascents[breakIndex]))
865 : fmAscent;
866 final int descent = fallbackLineSpacing
867 ? Math.max(fmDescent, Math.round(descents[breakIndex]))
868 : fmDescent;
869 v = out(source, here, endPos,
870 ascent, descent, fmTop, fmBottom,
871 v, spacingmult, spacingadd, chooseHt, chooseHtv, fm,
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800872 flags[breakIndex], needMultiply, measured, bufEnd,
873 includepad, trackpad, addLastLineSpacing, chs, widths.getRawArray(),
874 paraStart, ellipsize, ellipsizedWidth, lineWidths[breakIndex],
875 paint, moreChars);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700876
877 if (endPos < spanEnd) {
878 // preserve metrics for current span
879 fmTop = fm.top;
880 fmBottom = fm.bottom;
881 fmAscent = fm.ascent;
882 fmDescent = fm.descent;
883 } else {
884 fmTop = fmBottom = fmAscent = fmDescent = 0;
885 }
886
887 here = endPos;
888 breakIndex++;
889
890 if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) {
891 return;
892 }
893 }
894 }
895
896 if (paraEnd == bufEnd) {
897 break;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700898 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800899 }
900
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700901 if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE)
902 && mLineCount < mMaximumVisibleLineCount) {
Seigo Nonakafbe63bd2017-12-02 19:28:05 -0800903 final MeasuredText measured =
904 MeasuredText.buildForBidi(source, bufEnd, bufEnd, textDir, null);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700905 paint.getFontMetricsInt(fm);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700906 v = out(source,
907 bufEnd, bufEnd, fm.ascent, fm.descent,
908 fm.top, fm.bottom,
909 v,
910 spacingmult, spacingadd, null,
911 null, fm, 0,
Seigo Nonaka4b6bcee2017-12-11 11:39:51 -0800912 needMultiply, measured, bufEnd,
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700913 includepad, trackpad, addLastLineSpacing, null,
914 null, bufStart, ellipsize,
915 ellipsizedWidth, 0, paint, false);
916 }
917 } finally {
918 nFinish(nativePtr);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800919 }
920 }
921
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700922 // The parameters that are not changed in the method are marked as final to make the code
923 // easier to understand.
924 private int out(final CharSequence text, final int start, final int end, int above, int below,
925 int top, int bottom, int v, final float spacingmult, final float spacingadd,
926 final LineHeightSpan[] chooseHt, final int[] chooseHtv, final Paint.FontMetricsInt fm,
Seigo Nonaka4b6bcee2017-12-11 11:39:51 -0800927 final int flags, final boolean needMultiply, @NonNull final MeasuredText measured,
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800928 final int bufEnd, final boolean includePad, final boolean trackPad,
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700929 final boolean addLastLineLineSpacing, final char[] chs, final float[] widths,
930 final int widthStart, final TextUtils.TruncateAt ellipsize, final float ellipsisWidth,
931 final float textWidth, final TextPaint paint, final boolean moreChars) {
Siyamed Siniraf398512017-07-25 19:08:42 -0700932 final int j = mLineCount;
933 final int off = j * mColumns;
934 final int want = off + mColumns + TOP;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800935 int[] lines = mLines;
Seigo Nonaka4b6bcee2017-12-11 11:39:51 -0800936 final int dir = measured.getParagraphDir();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800937
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800938 if (want >= lines.length) {
Siyamed Siniraf398512017-07-25 19:08:42 -0700939 final int[] grow = ArrayUtils.newUnpaddedIntArray(GrowingArrayUtils.growSize(want));
Adam Lesinski776abc22014-03-07 11:30:59 -0500940 System.arraycopy(lines, 0, grow, 0, lines.length);
941 mLines = grow;
942 lines = grow;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800943 }
944
Siyamed Siniraf398512017-07-25 19:08:42 -0700945 if (j >= mLineDirections.length) {
946 final Directions[] grow = ArrayUtils.newUnpaddedArray(Directions.class,
947 GrowingArrayUtils.growSize(j));
948 System.arraycopy(mLineDirections, 0, grow, 0, mLineDirections.length);
949 mLineDirections = grow;
950 }
951
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700952 lines[off + START] = start;
953 lines[off + TOP] = v;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800954
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700955 // Information about hyphenation, tabs, and directions are needed for determining
956 // ellipsization, so the values should be assigned before ellipsization.
Eric Fischera9f1dd02009-08-12 15:00:10 -0700957
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700958 // TODO: could move TAB to share same column as HYPHEN, simplifying this code and gaining
959 // one bit for start field
960 lines[off + TAB] |= flags & TAB_MASK;
961 lines[off + HYPHEN] = flags;
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700962 lines[off + DIR] |= dir << DIR_SHIFT;
Seigo Nonaka4b6bcee2017-12-11 11:39:51 -0800963 mLineDirections[j] = measured.getDirections(start - widthStart, end - widthStart);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800964
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700965 final boolean firstLine = (j == 0);
966 final boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);
Siyamed Sinir0745c722016-05-31 20:39:33 -0700967
968 if (ellipsize != null) {
969 // If there is only one line, then do any type of ellipsis except when it is MARQUEE
970 // if there are multiple lines, just allow END ellipsis on the last line
971 boolean forceEllipsis = moreChars && (mLineCount + 1 == mMaximumVisibleLineCount);
972
973 boolean doEllipsis =
974 (((mMaximumVisibleLineCount == 1 && moreChars) || (firstLine && !moreChars)) &&
975 ellipsize != TextUtils.TruncateAt.MARQUEE) ||
976 (!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
977 ellipsize == TextUtils.TruncateAt.END);
978 if (doEllipsis) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700979 calculateEllipsis(text, start, end, widths, widthStart,
980 ellipsisWidth - getTotalInsets(j), ellipsize, j,
981 textWidth, paint, forceEllipsis, dir);
Siyamed Sinir0745c722016-05-31 20:39:33 -0700982 }
983 }
984
Siyamed Sinirfb0b2dc2017-07-26 22:08:24 -0700985 final boolean lastLine;
986 if (mEllipsized) {
987 lastLine = true;
988 } else {
989 final boolean lastCharIsNewLine = widthStart != bufEnd && bufEnd > 0
990 && text.charAt(bufEnd - 1) == CHAR_NEW_LINE;
991 if (end == bufEnd && !lastCharIsNewLine) {
992 lastLine = true;
993 } else if (start == bufEnd && lastCharIsNewLine) {
994 lastLine = true;
995 } else {
996 lastLine = false;
997 }
998 }
Raph Leviend97b0972014-04-24 12:51:35 -0700999
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001000 if (chooseHt != null) {
1001 fm.ascent = above;
1002 fm.descent = below;
1003 fm.top = top;
1004 fm.bottom = bottom;
1005
1006 for (int i = 0; i < chooseHt.length; i++) {
1007 if (chooseHt[i] instanceof LineHeightSpan.WithDensity) {
1008 ((LineHeightSpan.WithDensity) chooseHt[i])
1009 .chooseHeight(text, start, end, chooseHtv[i], v, fm, paint);
1010
1011 } else {
1012 chooseHt[i].chooseHeight(text, start, end, chooseHtv[i], v, fm);
1013 }
1014 }
1015
1016 above = fm.ascent;
1017 below = fm.descent;
1018 top = fm.top;
1019 bottom = fm.bottom;
1020 }
1021
Raph Leviend97b0972014-04-24 12:51:35 -07001022 if (firstLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001023 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001024 mTopPadding = top - above;
1025 }
1026
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001027 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001028 above = top;
1029 }
1030 }
Raph Leviend97b0972014-04-24 12:51:35 -07001031
Raph Leviend97b0972014-04-24 12:51:35 -07001032 if (lastLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001033 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001034 mBottomPadding = bottom - below;
1035 }
1036
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001037 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001038 below = bottom;
1039 }
1040 }
1041
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001042 final int extra;
Siyamed Sinir442c1512017-07-24 12:18:27 -07001043 if (needMultiply && (addLastLineLineSpacing || !lastLine)) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001044 final double ex = (below - above) * (spacingmult - 1) + spacingadd;
Doug Felt10657582010-02-22 11:19:01 -08001045 if (ex >= 0) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001046 extra = (int)(ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -08001047 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001048 extra = -(int)(-ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -08001049 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001050 } else {
1051 extra = 0;
1052 }
1053
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001054 lines[off + DESCENT] = below + extra;
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001055 lines[off + EXTRA] = extra;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001056
Siyamed Sinir0745c722016-05-31 20:39:33 -07001057 // special case for non-ellipsized last visible line when maxLines is set
1058 // store the height as if it was ellipsized
1059 if (!mEllipsized && currentLineIsTheLastVisibleOne) {
1060 // below calculation as if it was the last line
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001061 final int maxLineBelow = includePad ? bottom : below;
Siyamed Sinir0745c722016-05-31 20:39:33 -07001062 // similar to the calculation of v below, without the extra.
1063 mMaxLineHeight = v + (maxLineBelow - above);
1064 }
1065
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001066 v += (below - above) + extra;
1067 lines[off + mColumns + START] = end;
1068 lines[off + mColumns + TOP] = v;
1069
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001070 mLineCount++;
1071 return v;
1072 }
1073
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001074 private void calculateEllipsis(CharSequence text, int lineStart, int lineEnd, float[] widths,
1075 int widthStart, float avail, TextUtils.TruncateAt where, int line, float textWidth,
1076 TextPaint paint, boolean forceEllipsis, int dir) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001077 if (textWidth <= avail && !forceEllipsis) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001078 // Everything fits!
1079 mLines[mColumns * line + ELLIPSIS_START] = 0;
1080 mLines[mColumns * line + ELLIPSIS_COUNT] = 0;
1081 return;
1082 }
1083
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001084 float tempAvail = avail;
1085 int numberOfTries = 0;
1086 boolean lineFits = false;
1087 mWorkPaint.set(paint);
1088 do {
1089 final float ellipsizedWidth = guessEllipsis(text, lineStart, lineEnd, widths,
Siyamed Sinira273a702017-10-05 11:22:12 -07001090 widthStart, tempAvail, where, line, mWorkPaint, forceEllipsis, dir);
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001091 if (ellipsizedWidth <= avail) {
1092 lineFits = true;
1093 } else {
1094 numberOfTries++;
1095 if (numberOfTries > 10) {
1096 // If the text still doesn't fit after ten tries, assume it will never fit and
1097 // ellipsize it all.
1098 mLines[mColumns * line + ELLIPSIS_START] = 0;
1099 mLines[mColumns * line + ELLIPSIS_COUNT] = lineEnd - lineStart;
1100 lineFits = true;
1101 } else {
1102 // Some side effect of ellipsization has caused the text to go over the
1103 // available width. Let's make the available width shorter by exactly that
1104 // amount and retry.
1105 tempAvail -= ellipsizedWidth - avail;
1106 }
1107 }
1108 } while (!lineFits);
1109 mEllipsized = true;
1110 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001111
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001112 // Returns the width of the ellipsized line which in some rare cases can actually be larger
1113 // than 'avail' (due to kerning or other context-based effect of replacement of text by
1114 // ellipsis). If all the line needs to ellipsized away, or it's an invalud hyphenation mode,
1115 // returns 0 so the caller can stop iterating.
1116 //
1117 // This method temporarily modifies the TextPaint passed to it, so the TextPaint passed to it
1118 // should not be accessed while the method is running.
1119 private float guessEllipsis(CharSequence text, int lineStart, int lineEnd, float[] widths,
Siyamed Sinira273a702017-10-05 11:22:12 -07001120 int widthStart, float avail, TextUtils.TruncateAt where, int line,
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001121 TextPaint paint, boolean forceEllipsis, int dir) {
1122 final int savedHyphenEdit = paint.getHyphenEdit();
1123 paint.setHyphenEdit(0);
1124 final float ellipsisWidth = paint.measureText(TextUtils.getEllipsisString(where));
1125 final int ellipsisStart;
1126 final int ellipsisCount;
1127 final int len = lineEnd - lineStart;
1128 final int offset = lineStart - widthStart;
1129
1130 int hyphen = getHyphen(line);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001131 // We only support start ellipsis on a single line
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001132 if (where == TextUtils.TruncateAt.START) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001133 if (mMaximumVisibleLineCount == 1) {
1134 float sum = 0;
1135 int i;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001136
Keisuke Kuroyanagied2eea12015-04-14 18:18:35 +09001137 for (i = len; i > 0; i--) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001138 final float w = widths[i - 1 + offset];
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001139 if (w + sum + ellipsisWidth > avail) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001140 while (i < len && widths[i + offset] == 0.0f) {
Keisuke Kuroyanagi82d1c442016-09-14 14:30:14 +09001141 i++;
1142 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001143 break;
1144 }
1145
1146 sum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001147 }
1148
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001149 ellipsisStart = 0;
1150 ellipsisCount = i;
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001151 // Strip the potential hyphenation at beginning of line.
1152 hyphen &= ~Paint.HYPHENEDIT_MASK_START_OF_LINE;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001153 } else {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001154 ellipsisStart = 0;
1155 ellipsisCount = 0;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001156 if (Log.isLoggable(TAG, Log.WARN)) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001157 Log.w(TAG, "Start ellipsis only supported with one line");
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001158 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001159 }
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001160 } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE ||
1161 where == TextUtils.TruncateAt.END_SMALL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001162 float sum = 0;
1163 int i;
1164
1165 for (i = 0; i < len; i++) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001166 final float w = widths[i + offset];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001167
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001168 if (w + sum + ellipsisWidth > avail) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001169 break;
1170 }
1171
1172 sum += w;
1173 }
1174
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001175 if (forceEllipsis && i == len && len > 0) {
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -07001176 ellipsisStart = len - 1;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001177 ellipsisCount = 1;
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001178 } else {
1179 ellipsisStart = i;
1180 ellipsisCount = len - i;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001181 }
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001182 // Strip the potential hyphenation at end of line.
1183 hyphen &= ~Paint.HYPHENEDIT_MASK_END_OF_LINE;
1184 } else { // where = TextUtils.TruncateAt.MIDDLE
1185 // We only support middle ellipsis on a single line.
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001186 if (mMaximumVisibleLineCount == 1) {
1187 float lsum = 0, rsum = 0;
1188 int left = 0, right = len;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001189
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001190 final float ravail = (avail - ellipsisWidth) / 2;
Raph Levien0e3c5e82014-12-04 13:26:07 -08001191 for (right = len; right > 0; right--) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001192 final float w = widths[right - 1 + offset];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001193
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001194 if (w + rsum > ravail) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001195 while (right < len && widths[right + offset] == 0.0f) {
Keisuke Kuroyanagi82d1c442016-09-14 14:30:14 +09001196 right++;
1197 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001198 break;
1199 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001200 rsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001201 }
1202
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001203 final float lavail = avail - ellipsisWidth - rsum;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001204 for (left = 0; left < right; left++) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001205 final float w = widths[left + offset];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001206
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001207 if (w + lsum > lavail) {
1208 break;
1209 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001210
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001211 lsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001212 }
1213
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001214 ellipsisStart = left;
1215 ellipsisCount = right - left;
1216 } else {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001217 ellipsisStart = 0;
1218 ellipsisCount = 0;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001219 if (Log.isLoggable(TAG, Log.WARN)) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001220 Log.w(TAG, "Middle ellipsis only supported with one line");
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001221 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001222 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001223 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001224 mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
1225 mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001226
1227 if (ellipsisStart == 0 && (ellipsisCount == 0 || ellipsisCount == len)) {
1228 // Unsupported ellipsization mode or all text is ellipsized away. Return 0.
1229 return 0.0f;
1230 }
1231
1232 final boolean isSpanned = text instanceof Spanned;
1233 final Ellipsizer ellipsizedText = isSpanned
1234 ? new SpannedEllipsizer(text)
1235 : new Ellipsizer(text);
1236 ellipsizedText.mLayout = this;
1237 ellipsizedText.mMethod = where;
1238
1239 final boolean hasTabs = getLineContainsTab(line);
1240 final TabStops tabStops;
1241 if (hasTabs && isSpanned) {
1242 final TabStopSpan[] tabs = getParagraphSpans((Spanned) ellipsizedText, lineStart,
1243 lineEnd, TabStopSpan.class);
1244 if (tabs.length == 0) {
1245 tabStops = null;
1246 } else {
1247 tabStops = new TabStops(TAB_INCREMENT, tabs);
1248 }
1249 } else {
1250 tabStops = null;
1251 }
1252 paint.setHyphenEdit(hyphen);
1253 final TextLine textline = TextLine.obtain();
1254 textline.set(paint, ellipsizedText, lineStart, lineEnd, dir, getLineDirections(line),
1255 hasTabs, tabStops);
1256 // Since TextLine.metric() returns negative values for RTL text, multiplication by dir
1257 // converts it to an actual width. Note that we don't want to use the absolute value,
1258 // since we may actually have glyphs with negative advances, which by definition always
1259 // fit.
1260 final float ellipsizedWidth = textline.metrics(null) * dir;
1261 TextLine.recycle(textline);
1262 paint.setHyphenEdit(savedHyphenEdit);
1263 return ellipsizedWidth;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001264 }
1265
Selim Cinek365ec092017-03-09 00:10:52 -08001266 private float getTotalInsets(int line) {
1267 int totalIndent = 0;
1268 if (mLeftIndents != null) {
1269 totalIndent = mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1270 }
1271 if (mRightIndents != null) {
1272 totalIndent += mRightIndents[Math.min(line, mRightIndents.length - 1)];
1273 }
1274 return totalIndent;
1275 }
1276
Doug Felte8e45f22010-03-29 14:58:40 -07001277 // Override the base class so we can directly access our members,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001278 // rather than relying on member functions.
1279 // The logic mirrors that of Layout.getLineForVertical
1280 // FIXME: It may be faster to do a linear search for layouts without many lines.
Gilles Debunne66111472010-11-19 11:04:37 -08001281 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001282 public int getLineForVertical(int vertical) {
1283 int high = mLineCount;
1284 int low = -1;
1285 int guess;
1286 int[] lines = mLines;
1287 while (high - low > 1) {
1288 guess = (high + low) >> 1;
1289 if (lines[mColumns * guess + TOP] > vertical){
1290 high = guess;
1291 } else {
1292 low = guess;
1293 }
1294 }
1295 if (low < 0) {
1296 return 0;
1297 } else {
1298 return low;
1299 }
1300 }
1301
Gilles Debunne66111472010-11-19 11:04:37 -08001302 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001303 public int getLineCount() {
1304 return mLineCount;
1305 }
1306
Gilles Debunne66111472010-11-19 11:04:37 -08001307 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001308 public int getLineTop(int line) {
Raph Levien07e6c232016-04-04 12:34:06 -07001309 return mLines[mColumns * line + TOP];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001310 }
1311
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001312 /**
1313 * @hide
1314 */
1315 @Override
1316 public int getLineExtra(int line) {
1317 return mLines[mColumns * line + EXTRA];
1318 }
1319
Gilles Debunne66111472010-11-19 11:04:37 -08001320 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001321 public int getLineDescent(int line) {
Raph Levien07e6c232016-04-04 12:34:06 -07001322 return mLines[mColumns * line + DESCENT];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001323 }
1324
Gilles Debunne66111472010-11-19 11:04:37 -08001325 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001326 public int getLineStart(int line) {
1327 return mLines[mColumns * line + START] & START_MASK;
1328 }
1329
Gilles Debunne66111472010-11-19 11:04:37 -08001330 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001331 public int getParagraphDirection(int line) {
1332 return mLines[mColumns * line + DIR] >> DIR_SHIFT;
1333 }
1334
Gilles Debunne66111472010-11-19 11:04:37 -08001335 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001336 public boolean getLineContainsTab(int line) {
1337 return (mLines[mColumns * line + TAB] & TAB_MASK) != 0;
1338 }
1339
Gilles Debunne66111472010-11-19 11:04:37 -08001340 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001341 public final Directions getLineDirections(int line) {
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001342 if (line > getLineCount()) {
1343 throw new ArrayIndexOutOfBoundsException();
1344 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001345 return mLineDirections[line];
1346 }
1347
Gilles Debunne66111472010-11-19 11:04:37 -08001348 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001349 public int getTopPadding() {
1350 return mTopPadding;
1351 }
1352
Gilles Debunne66111472010-11-19 11:04:37 -08001353 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001354 public int getBottomPadding() {
1355 return mBottomPadding;
1356 }
1357
Raph Levien26d443a2015-03-30 14:18:32 -07001358 /**
1359 * @hide
1360 */
1361 @Override
1362 public int getHyphen(int line) {
Keisuke Kuroyanagif5af4a32016-08-31 21:40:53 +09001363 return mLines[mColumns * line + HYPHEN] & HYPHEN_MASK;
Raph Levien26d443a2015-03-30 14:18:32 -07001364 }
1365
Raph Levien2ea52902015-07-01 14:39:31 -07001366 /**
1367 * @hide
1368 */
1369 @Override
1370 public int getIndentAdjust(int line, Alignment align) {
1371 if (align == Alignment.ALIGN_LEFT) {
1372 if (mLeftIndents == null) {
1373 return 0;
1374 } else {
1375 return mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1376 }
1377 } else if (align == Alignment.ALIGN_RIGHT) {
1378 if (mRightIndents == null) {
1379 return 0;
1380 } else {
1381 return -mRightIndents[Math.min(line, mRightIndents.length - 1)];
1382 }
1383 } else if (align == Alignment.ALIGN_CENTER) {
1384 int left = 0;
1385 if (mLeftIndents != null) {
1386 left = mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1387 }
1388 int right = 0;
1389 if (mRightIndents != null) {
1390 right = mRightIndents[Math.min(line, mRightIndents.length - 1)];
1391 }
1392 return (left - right) >> 1;
1393 } else {
1394 throw new AssertionError("unhandled alignment " + align);
1395 }
1396 }
1397
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001398 @Override
1399 public int getEllipsisCount(int line) {
1400 if (mColumns < COLUMNS_ELLIPSIZE) {
1401 return 0;
1402 }
1403
1404 return mLines[mColumns * line + ELLIPSIS_COUNT];
1405 }
1406
1407 @Override
1408 public int getEllipsisStart(int line) {
1409 if (mColumns < COLUMNS_ELLIPSIZE) {
1410 return 0;
1411 }
1412
1413 return mLines[mColumns * line + ELLIPSIS_START];
1414 }
1415
1416 @Override
1417 public int getEllipsizedWidth() {
1418 return mEllipsizedWidth;
1419 }
1420
Siyamed Sinir0745c722016-05-31 20:39:33 -07001421 /**
1422 * Return the total height of this layout.
1423 *
1424 * @param cap if true and max lines is set, returns the height of the layout at the max lines.
1425 *
1426 * @hide
1427 */
1428 public int getHeight(boolean cap) {
1429 if (cap && mLineCount >= mMaximumVisibleLineCount && mMaxLineHeight == -1 &&
1430 Log.isLoggable(TAG, Log.WARN)) {
1431 Log.w(TAG, "maxLineHeight should not be -1. "
1432 + " maxLines:" + mMaximumVisibleLineCount
1433 + " lineCount:" + mLineCount);
1434 }
1435
1436 return cap && mLineCount >= mMaximumVisibleLineCount && mMaxLineHeight != -1 ?
1437 mMaxLineHeight : super.getHeight();
1438 }
1439
Seigo Nonakab90e08f2017-10-20 15:23:51 -07001440 @FastNative
1441 private static native long nInit(
1442 @BreakStrategy int breakStrategy,
1443 @HyphenationFrequency int hyphenationFrequency,
1444 boolean isJustified,
1445 @Nullable int[] indents,
1446 @Nullable int[] leftPaddings,
1447 @Nullable int[] rightPaddings);
1448
1449 @CriticalNative
1450 private static native void nFinish(long nativePtr);
1451
Anish Athalyec8f9e622014-07-21 15:26:34 -07001452 // populates LineBreaks and returns the number of breaks found
1453 //
1454 // the arrays inside the LineBreaks objects are passed in as well
1455 // to reduce the number of JNI calls in the common case where the
1456 // arrays do not have to be resized
Seigo Nonaka2dd1a552017-10-06 11:41:00 -07001457 // The individual character widths will be returned in charWidths. The length of charWidths must
1458 // be at least the length of the text.
Seigo Nonakab90e08f2017-10-20 15:23:51 -07001459 private static native int nComputeLineBreaks(
1460 /* non zero */ long nativePtr,
1461
1462 // Inputs
1463 @NonNull char[] text,
Seigo Nonaka6f1868b2017-12-01 16:24:19 -08001464 /* Non Zero */ long measuredTextPtr,
Seigo Nonakab90e08f2017-10-20 15:23:51 -07001465 @IntRange(from = 0) int length,
1466 @FloatRange(from = 0.0f) float firstWidth,
1467 @IntRange(from = 0) int firstWidthLineCount,
1468 @FloatRange(from = 0.0f) float restWidth,
1469 @Nullable int[] variableTabStops,
1470 int defaultTabStop,
1471 @IntRange(from = 0) int indentsOffset,
1472
1473 // Outputs
1474 @NonNull LineBreaks recycle,
1475 @IntRange(from = 0) int recycleLength,
1476 @NonNull int[] recycleBreaks,
1477 @NonNull float[] recycleWidths,
1478 @NonNull float[] recycleAscents,
1479 @NonNull float[] recycleDescents,
1480 @NonNull int[] recycleFlags,
1481 @NonNull float[] charWidths);
Anish Athalye88b5b0b2014-06-24 14:39:43 -07001482
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001483 private int mLineCount;
1484 private int mTopPadding, mBottomPadding;
1485 private int mColumns;
1486 private int mEllipsizedWidth;
1487
Siyamed Sinir0745c722016-05-31 20:39:33 -07001488 /**
1489 * Keeps track if ellipsize is applied to the text.
1490 */
1491 private boolean mEllipsized;
1492
1493 /**
1494 * If maxLines is set, ellipsize is not set, and the actual line count of text is greater than
1495 * or equal to maxLine, this variable holds the ideal visual height of the maxLine'th line
1496 * starting from the top of the layout. If maxLines is not set its value will be -1.
1497 *
1498 * The value is the same as getLineTop(maxLines) for ellipsized version where structurally no
1499 * more than maxLines is contained.
1500 */
Siyamed Sinira19cd512017-08-03 22:01:56 -07001501 private int mMaxLineHeight = DEFAULT_MAX_LINE_HEIGHT;
Siyamed Sinir0745c722016-05-31 20:39:33 -07001502
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001503 private TextPaint mWorkPaint = new TextPaint();
1504
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001505 private static final int COLUMNS_NORMAL = 5;
1506 private static final int COLUMNS_ELLIPSIZE = 7;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001507 private static final int START = 0;
1508 private static final int DIR = START;
1509 private static final int TAB = START;
1510 private static final int TOP = 1;
1511 private static final int DESCENT = 2;
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001512 private static final int EXTRA = 3;
1513 private static final int HYPHEN = 4;
1514 private static final int ELLIPSIS_START = 5;
1515 private static final int ELLIPSIS_COUNT = 6;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001516
1517 private int[] mLines;
1518 private Directions[] mLineDirections;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001519 private int mMaximumVisibleLineCount = Integer.MAX_VALUE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001520
1521 private static final int START_MASK = 0x1FFFFFFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001522 private static final int DIR_SHIFT = 30;
1523 private static final int TAB_MASK = 0x20000000;
Keisuke Kuroyanagif5af4a32016-08-31 21:40:53 +09001524 private static final int HYPHEN_MASK = 0xFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001525
Doug Feltc982f602010-05-25 11:51:40 -07001526 private static final int TAB_INCREMENT = 20; // same as Layout, but that's private
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001527
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001528 private static final char CHAR_NEW_LINE = '\n';
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001529
1530 private static final double EXTRA_ROUNDING = 0.5;
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001531
Siyamed Sinira19cd512017-08-03 22:01:56 -07001532 private static final int DEFAULT_MAX_LINE_HEIGHT = -1;
1533
Anish Athalyec8f9e622014-07-21 15:26:34 -07001534 // This is used to return three arrays from a single JNI call when
1535 // performing line breaking
Deepanshu Gupta70539192014-10-15 15:57:40 -07001536 /*package*/ static class LineBreaks {
Anish Athalyec8f9e622014-07-21 15:26:34 -07001537 private static final int INITIAL_SIZE = 16;
1538 public int[] breaks = new int[INITIAL_SIZE];
1539 public float[] widths = new float[INITIAL_SIZE];
Roozbeh Pournader737dfea2017-08-10 11:32:24 -07001540 public float[] ascents = new float[INITIAL_SIZE];
1541 public float[] descents = new float[INITIAL_SIZE];
Roozbeh Pournader112d9c72015-08-07 12:44:41 -07001542 public int[] flags = new int[INITIAL_SIZE]; // hasTab
Anish Athalyec8f9e622014-07-21 15:26:34 -07001543 // breaks, widths, and flags should all have the same length
1544 }
1545
Seigo Nonakabafe1972017-08-24 15:30:29 -07001546 @Nullable private int[] mLeftIndents;
1547 @Nullable private int[] mRightIndents;
Roozbeh Pournader9a9179d2017-08-29 14:14:28 -07001548 @Nullable private int[] mLeftPaddings;
1549 @Nullable private int[] mRightPaddings;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001550}