blob: 70d648657d9d2d4f7ca63fb51a865f5272aa90b7 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.text;
18
Roozbeh Pournader22a167c2017-08-21 12:53:44 -070019import android.annotation.FloatRange;
20import android.annotation.IntRange;
21import android.annotation.NonNull;
Raph Levien531c30c2015-04-30 16:29:59 -070022import android.annotation.Nullable;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080023import android.graphics.Paint;
Seigo Nonakaf1644f72017-11-27 22:09:49 -080024import android.text.AutoGrowArray.FloatArray;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080025import android.text.style.LeadingMarginSpan;
Gilles Debunne66111472010-11-19 11:04:37 -080026import android.text.style.LeadingMarginSpan.LeadingMarginSpan2;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080027import android.text.style.LineHeightSpan;
Doug Feltc982f602010-05-25 11:51:40 -070028import android.text.style.TabStopSpan;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -070029import android.util.Log;
Raph Levien39b4db72015-03-25 13:18:20 -070030import android.util.Pools.SynchronizedPool;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080031
Doug Feltcb3791202011-07-07 11:57:48 -070032import com.android.internal.util.ArrayUtils;
Adam Lesinski776abc22014-03-07 11:30:59 -050033import com.android.internal.util.GrowingArrayUtils;
Doug Feltcb3791202011-07-07 11:57:48 -070034
Seigo Nonakab90e08f2017-10-20 15:23:51 -070035import dalvik.annotation.optimization.CriticalNative;
36import dalvik.annotation.optimization.FastNative;
37
Anish Athalyec8f9e622014-07-21 15:26:34 -070038import java.util.Arrays;
39
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080040/**
41 * StaticLayout is a Layout for text that will not be edited after it
42 * is laid out. Use {@link DynamicLayout} for text that may change.
43 * <p>This is used by widgets to control text layout. You should not need
44 * to use this class directly unless you are implementing your own widget
45 * or custom display object, or would be tempted to call
Doug Felt4e0c5e52010-03-15 16:56:02 -070046 * {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int,
47 * float, float, android.graphics.Paint)
48 * Canvas.drawText()} directly.</p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080049 */
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -080050public class StaticLayout extends Layout {
Seigo Nonaka6f1868b2017-12-01 16:24:19 -080051 /*
52 * The break iteration is done in native code. The protocol for using the native code is as
53 * follows.
54 *
55 * First, call nInit to setup native line breaker object. Then, for each paragraph, do the
56 * following:
57 *
Seigo Nonaka9d3bd082018-01-11 10:02:12 -080058 * - Create MeasuredParagraph by MeasuredParagraph.buildForStaticLayout which measures in
59 * native.
Seigo Nonaka6f1868b2017-12-01 16:24:19 -080060 * - Run nComputeLineBreaks() to obtain line breaks for the paragraph.
61 *
62 * After all paragraphs, call finish() to release expensive buffers.
63 */
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -080064
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -070065 static final String TAG = "StaticLayout";
66
Raph Leviend3ab6922015-03-02 14:30:53 -080067 /**
Roozbeh Pournader22a167c2017-08-21 12:53:44 -070068 * Builder for static layouts. The builder is the preferred pattern for constructing
69 * StaticLayout objects and should be preferred over the constructors, particularly to access
70 * newer features. To build a static layout, first call {@link #obtain} with the required
71 * arguments (text, paint, and width), then call setters for optional parameters, and finally
72 * {@link #build} to build the StaticLayout object. Parameters not explicitly set will get
Raph Levien531c30c2015-04-30 16:29:59 -070073 * default values.
Raph Leviend3ab6922015-03-02 14:30:53 -080074 */
75 public final static class Builder {
Seigo Nonakab90e08f2017-10-20 15:23:51 -070076 private Builder() {}
Raph Levien4c1f12e2015-03-02 16:29:23 -080077
Raph Levien531c30c2015-04-30 16:29:59 -070078 /**
Roozbeh Pournader22a167c2017-08-21 12:53:44 -070079 * Obtain a builder for constructing StaticLayout objects.
Raph Levien531c30c2015-04-30 16:29:59 -070080 *
81 * @param source The text to be laid out, optionally with spans
82 * @param start The index of the start of the text
83 * @param end The index + 1 of the end of the text
84 * @param paint The base paint used for layout
85 * @param width The width in pixels
86 * @return a builder object used for constructing the StaticLayout
87 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -070088 @NonNull
89 public static Builder obtain(@NonNull CharSequence source, @IntRange(from = 0) int start,
90 @IntRange(from = 0) int end, @NonNull TextPaint paint,
91 @IntRange(from = 0) int width) {
Raph Levien39b4db72015-03-25 13:18:20 -070092 Builder b = sPool.acquire();
Raph Leviend3ab6922015-03-02 14:30:53 -080093 if (b == null) {
94 b = new Builder();
95 }
96
97 // set default initial values
Raph Levien39b4db72015-03-25 13:18:20 -070098 b.mText = source;
99 b.mStart = start;
100 b.mEnd = end;
Raph Levienebd66ca2015-04-30 15:27:57 -0700101 b.mPaint = paint;
Raph Levien39b4db72015-03-25 13:18:20 -0700102 b.mWidth = width;
103 b.mAlignment = Alignment.ALIGN_NORMAL;
Raph Leviend3ab6922015-03-02 14:30:53 -0800104 b.mTextDir = TextDirectionHeuristics.FIRSTSTRONG_LTR;
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700105 b.mSpacingMult = DEFAULT_LINESPACING_MULTIPLIER;
106 b.mSpacingAdd = DEFAULT_LINESPACING_ADDITION;
Raph Leviend3ab6922015-03-02 14:30:53 -0800107 b.mIncludePad = true;
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700108 b.mFallbackLineSpacing = false;
Raph Levien39b4db72015-03-25 13:18:20 -0700109 b.mEllipsizedWidth = width;
Raph Leviend3ab6922015-03-02 14:30:53 -0800110 b.mEllipsize = null;
111 b.mMaxLines = Integer.MAX_VALUE;
Raph Levien3bd60c72015-05-06 14:26:35 -0700112 b.mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700113 b.mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700114 b.mJustificationMode = Layout.JUSTIFICATION_MODE_NONE;
Raph Leviend3ab6922015-03-02 14:30:53 -0800115 return b;
116 }
117
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700118 /**
119 * This method should be called after the layout is finished getting constructed and the
120 * builder needs to be cleaned up and returned to the pool.
121 */
122 private static void recycle(@NonNull Builder b) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800123 b.mPaint = null;
124 b.mText = null;
Raph Levien2ea52902015-07-01 14:39:31 -0700125 b.mLeftIndents = null;
126 b.mRightIndents = null;
Roozbeh Pournader9a9179d2017-08-29 14:14:28 -0700127 b.mLeftPaddings = null;
128 b.mRightPaddings = null;
Raph Levien39b4db72015-03-25 13:18:20 -0700129 sPool.release(b);
Raph Leviend3ab6922015-03-02 14:30:53 -0800130 }
131
132 // release any expensive state
133 /* package */ void finish() {
Raph Levien22ba7862015-07-29 12:34:13 -0700134 mText = null;
135 mPaint = null;
136 mLeftIndents = null;
137 mRightIndents = null;
Roozbeh Pournader9a9179d2017-08-29 14:14:28 -0700138 mLeftPaddings = null;
139 mRightPaddings = null;
Raph Leviend3ab6922015-03-02 14:30:53 -0800140 }
141
142 public Builder setText(CharSequence source) {
143 return setText(source, 0, source.length());
144 }
145
Raph Levien531c30c2015-04-30 16:29:59 -0700146 /**
147 * Set the text. Only useful when re-using the builder, which is done for
148 * the internal implementation of {@link DynamicLayout} but not as part
149 * of normal {@link StaticLayout} usage.
150 *
151 * @param source The text to be laid out, optionally with spans
152 * @param start The index of the start of the text
153 * @param end The index + 1 of the end of the text
154 * @return this builder, useful for chaining
155 *
156 * @hide
157 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700158 @NonNull
159 public Builder setText(@NonNull CharSequence source, int start, int end) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800160 mText = source;
161 mStart = start;
162 mEnd = end;
163 return this;
164 }
165
Raph Levien531c30c2015-04-30 16:29:59 -0700166 /**
167 * Set the paint. Internal for reuse cases only.
168 *
169 * @param paint The base paint used for layout
170 * @return this builder, useful for chaining
171 *
172 * @hide
173 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700174 @NonNull
175 public Builder setPaint(@NonNull TextPaint paint) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800176 mPaint = paint;
177 return this;
178 }
179
Raph Levien531c30c2015-04-30 16:29:59 -0700180 /**
181 * Set the width. Internal for reuse cases only.
182 *
183 * @param width The width in pixels
184 * @return this builder, useful for chaining
185 *
186 * @hide
187 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700188 @NonNull
189 public Builder setWidth(@IntRange(from = 0) int width) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800190 mWidth = width;
191 if (mEllipsize == null) {
192 mEllipsizedWidth = width;
193 }
194 return this;
195 }
196
Raph Levien531c30c2015-04-30 16:29:59 -0700197 /**
198 * Set the alignment. The default is {@link Layout.Alignment#ALIGN_NORMAL}.
199 *
200 * @param alignment Alignment for the resulting {@link StaticLayout}
201 * @return this builder, useful for chaining
202 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700203 @NonNull
204 public Builder setAlignment(@NonNull Alignment alignment) {
Raph Levien39b4db72015-03-25 13:18:20 -0700205 mAlignment = alignment;
206 return this;
207 }
208
Raph Levien531c30c2015-04-30 16:29:59 -0700209 /**
210 * Set the text direction heuristic. The text direction heuristic is used to
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700211 * resolve text direction per-paragraph based on the input text. The default is
Raph Levien531c30c2015-04-30 16:29:59 -0700212 * {@link TextDirectionHeuristics#FIRSTSTRONG_LTR}.
213 *
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700214 * @param textDir text direction heuristic for resolving bidi behavior.
Raph Levien531c30c2015-04-30 16:29:59 -0700215 * @return this builder, useful for chaining
216 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700217 @NonNull
218 public Builder setTextDirection(@NonNull TextDirectionHeuristic textDir) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800219 mTextDir = textDir;
220 return this;
221 }
222
Raph Levien531c30c2015-04-30 16:29:59 -0700223 /**
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700224 * Set line spacing parameters. Each line will have its line spacing multiplied by
225 * {@code spacingMult} and then increased by {@code spacingAdd}. The default is 0.0 for
226 * {@code spacingAdd} and 1.0 for {@code spacingMult}.
Raph Levien531c30c2015-04-30 16:29:59 -0700227 *
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700228 * @param spacingAdd the amount of line spacing addition
229 * @param spacingMult the line spacing multiplier
Raph Levien531c30c2015-04-30 16:29:59 -0700230 * @return this builder, useful for chaining
231 * @see android.widget.TextView#setLineSpacing
232 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700233 @NonNull
234 public Builder setLineSpacing(float spacingAdd, @FloatRange(from = 0.0) float spacingMult) {
Raph Levien531c30c2015-04-30 16:29:59 -0700235 mSpacingAdd = spacingAdd;
Raph Leviend3ab6922015-03-02 14:30:53 -0800236 mSpacingMult = spacingMult;
237 return this;
238 }
239
Raph Levien531c30c2015-04-30 16:29:59 -0700240 /**
241 * Set whether to include extra space beyond font ascent and descent (which is
242 * needed to avoid clipping in some languages, such as Arabic and Kannada). The
243 * default is {@code true}.
244 *
245 * @param includePad whether to include padding
246 * @return this builder, useful for chaining
247 * @see android.widget.TextView#setIncludeFontPadding
248 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700249 @NonNull
Raph Leviend3ab6922015-03-02 14:30:53 -0800250 public Builder setIncludePad(boolean includePad) {
251 mIncludePad = includePad;
252 return this;
253 }
254
Raph Levien531c30c2015-04-30 16:29:59 -0700255 /**
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700256 * Set whether to respect the ascent and descent of the fallback fonts that are used in
257 * displaying the text (which is needed to avoid text from consecutive lines running into
258 * each other). If set, fallback fonts that end up getting used can increase the ascent
259 * and descent of the lines that they are used on.
260 *
261 * <p>For backward compatibility reasons, the default is {@code false}, but setting this to
262 * true is strongly recommended. It is required to be true if text could be in languages
263 * like Burmese or Tibetan where text is typically much taller or deeper than Latin text.
264 *
265 * @param useLineSpacingFromFallbacks whether to expand linespacing based on fallback fonts
266 * @return this builder, useful for chaining
267 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700268 @NonNull
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700269 public Builder setUseLineSpacingFromFallbacks(boolean useLineSpacingFromFallbacks) {
270 mFallbackLineSpacing = useLineSpacingFromFallbacks;
271 return this;
272 }
273
274 /**
Raph Levien531c30c2015-04-30 16:29:59 -0700275 * Set the width as used for ellipsizing purposes, if it differs from the
276 * normal layout width. The default is the {@code width}
277 * passed to {@link #obtain}.
278 *
279 * @param ellipsizedWidth width used for ellipsizing, in pixels
280 * @return this builder, useful for chaining
281 * @see android.widget.TextView#setEllipsize
282 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700283 @NonNull
284 public Builder setEllipsizedWidth(@IntRange(from = 0) int ellipsizedWidth) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800285 mEllipsizedWidth = ellipsizedWidth;
286 return this;
287 }
288
Raph Levien531c30c2015-04-30 16:29:59 -0700289 /**
290 * Set ellipsizing on the layout. Causes words that are longer than the view
291 * is wide, or exceeding the number of lines (see #setMaxLines) in the case
292 * of {@link android.text.TextUtils.TruncateAt#END} or
293 * {@link android.text.TextUtils.TruncateAt#MARQUEE}, to be ellipsized instead
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700294 * of broken. The default is {@code null}, indicating no ellipsis is to be applied.
Raph Levien531c30c2015-04-30 16:29:59 -0700295 *
296 * @param ellipsize type of ellipsis behavior
297 * @return this builder, useful for chaining
298 * @see android.widget.TextView#setEllipsize
299 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700300 @NonNull
Raph Levien531c30c2015-04-30 16:29:59 -0700301 public Builder setEllipsize(@Nullable TextUtils.TruncateAt ellipsize) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800302 mEllipsize = ellipsize;
303 return this;
304 }
305
Raph Levien531c30c2015-04-30 16:29:59 -0700306 /**
307 * Set maximum number of lines. This is particularly useful in the case of
308 * ellipsizing, where it changes the layout of the last line. The default is
309 * unlimited.
310 *
311 * @param maxLines maximum number of lines in the layout
312 * @return this builder, useful for chaining
313 * @see android.widget.TextView#setMaxLines
314 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700315 @NonNull
316 public Builder setMaxLines(@IntRange(from = 0) int maxLines) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800317 mMaxLines = maxLines;
318 return this;
319 }
320
Raph Levien531c30c2015-04-30 16:29:59 -0700321 /**
322 * Set break strategy, useful for selecting high quality or balanced paragraph
323 * layout options. The default is {@link Layout#BREAK_STRATEGY_SIMPLE}.
324 *
325 * @param breakStrategy break strategy for paragraph layout
326 * @return this builder, useful for chaining
327 * @see android.widget.TextView#setBreakStrategy
328 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700329 @NonNull
Raph Levien39b4db72015-03-25 13:18:20 -0700330 public Builder setBreakStrategy(@BreakStrategy int breakStrategy) {
331 mBreakStrategy = breakStrategy;
332 return this;
333 }
334
Raph Levien531c30c2015-04-30 16:29:59 -0700335 /**
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700336 * Set hyphenation frequency, to control the amount of automatic hyphenation used. The
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700337 * possible values are defined in {@link Layout}, by constants named with the pattern
338 * {@code HYPHENATION_FREQUENCY_*}. The default is
339 * {@link Layout#HYPHENATION_FREQUENCY_NONE}.
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700340 *
341 * @param hyphenationFrequency hyphenation frequency for the paragraph
342 * @return this builder, useful for chaining
343 * @see android.widget.TextView#setHyphenationFrequency
344 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700345 @NonNull
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700346 public Builder setHyphenationFrequency(@HyphenationFrequency int hyphenationFrequency) {
347 mHyphenationFrequency = hyphenationFrequency;
348 return this;
349 }
350
351 /**
Raph Levien531c30c2015-04-30 16:29:59 -0700352 * Set indents. Arguments are arrays holding an indent amount, one per line, measured in
353 * pixels. For lines past the last element in the array, the last element repeats.
354 *
355 * @param leftIndents array of indent values for left margin, in pixels
356 * @param rightIndents array of indent values for right margin, in pixels
357 * @return this builder, useful for chaining
Raph Levien531c30c2015-04-30 16:29:59 -0700358 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700359 @NonNull
360 public Builder setIndents(@Nullable int[] leftIndents, @Nullable int[] rightIndents) {
Raph Levien2ea52902015-07-01 14:39:31 -0700361 mLeftIndents = leftIndents;
362 mRightIndents = rightIndents;
Raph Leviene319d5a2015-04-14 23:51:07 -0700363 return this;
364 }
365
Raph Levien70616ec2015-03-04 10:41:30 -0800366 /**
Roozbeh Pournader9a9179d2017-08-29 14:14:28 -0700367 * Set available paddings to draw overhanging text on. Arguments are arrays holding the
368 * amount of padding available, one per line, measured in pixels. For lines past the last
369 * element in the array, the last element repeats.
370 *
371 * The individual padding amounts should be non-negative. The result of passing negative
372 * paddings is undefined.
373 *
374 * @param leftPaddings array of amounts of available padding for left margin, in pixels
375 * @param rightPaddings array of amounts of available padding for right margin, in pixels
376 * @return this builder, useful for chaining
377 *
378 * @hide
379 */
380 @NonNull
381 public Builder setAvailablePaddings(@Nullable int[] leftPaddings,
382 @Nullable int[] rightPaddings) {
383 mLeftPaddings = leftPaddings;
384 mRightPaddings = rightPaddings;
385 return this;
386 }
387
388 /**
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700389 * Set paragraph justification mode. The default value is
390 * {@link Layout#JUSTIFICATION_MODE_NONE}. If the last line is too short for justification,
391 * the last line will be displayed with the alignment set by {@link #setAlignment}.
Seigo Nonaka09da71a2016-11-28 16:24:14 +0900392 *
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700393 * @param justificationMode justification mode for the paragraph.
Seigo Nonaka09da71a2016-11-28 16:24:14 +0900394 * @return this builder, useful for chaining.
395 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700396 @NonNull
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700397 public Builder setJustificationMode(@JustificationMode int justificationMode) {
398 mJustificationMode = justificationMode;
Seigo Nonaka09da71a2016-11-28 16:24:14 +0900399 return this;
400 }
401
Siyamed Sinir442c1512017-07-24 12:18:27 -0700402 /**
403 * Sets whether the line spacing should be applied for the last line. Default value is
404 * {@code false}.
405 *
406 * @hide
407 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700408 @NonNull
Siyamed Sinir442c1512017-07-24 12:18:27 -0700409 /* package */ Builder setAddLastLineLineSpacing(boolean value) {
410 mAddLastLineLineSpacing = value;
411 return this;
412 }
413
Seigo Nonaka09da71a2016-11-28 16:24:14 +0900414 /**
Raph Levien531c30c2015-04-30 16:29:59 -0700415 * Build the {@link StaticLayout} after options have been set.
416 *
417 * <p>Note: the builder object must not be reused in any way after calling this
418 * method. Setting parameters after calling this method, or calling it a second
419 * time on the same builder object, will likely lead to unexpected results.
420 *
421 * @return the newly constructed {@link StaticLayout} object
422 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700423 @NonNull
Raph Leviend3ab6922015-03-02 14:30:53 -0800424 public StaticLayout build() {
Raph Levien39b4db72015-03-25 13:18:20 -0700425 StaticLayout result = new StaticLayout(this);
426 Builder.recycle(this);
Raph Leviend3ab6922015-03-02 14:30:53 -0800427 return result;
428 }
429
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700430 private CharSequence mText;
431 private int mStart;
432 private int mEnd;
433 private TextPaint mPaint;
434 private int mWidth;
435 private Alignment mAlignment;
436 private TextDirectionHeuristic mTextDir;
437 private float mSpacingMult;
438 private float mSpacingAdd;
439 private boolean mIncludePad;
440 private boolean mFallbackLineSpacing;
441 private int mEllipsizedWidth;
442 private TextUtils.TruncateAt mEllipsize;
443 private int mMaxLines;
444 private int mBreakStrategy;
445 private int mHyphenationFrequency;
Seigo Nonakabafe1972017-08-24 15:30:29 -0700446 @Nullable private int[] mLeftIndents;
447 @Nullable private int[] mRightIndents;
Roozbeh Pournader9a9179d2017-08-29 14:14:28 -0700448 @Nullable private int[] mLeftPaddings;
449 @Nullable private int[] mRightPaddings;
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700450 private int mJustificationMode;
451 private boolean mAddLastLineLineSpacing;
Raph Leviend3ab6922015-03-02 14:30:53 -0800452
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700453 private final Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
Raph Leviend3ab6922015-03-02 14:30:53 -0800454
Siyamed Sinira273a702017-10-05 11:22:12 -0700455 private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<>(3);
Raph Leviend3ab6922015-03-02 14:30:53 -0800456 }
457
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000458 /**
459 * @deprecated Use {@link Builder} instead.
460 */
461 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800462 public StaticLayout(CharSequence source, TextPaint paint,
463 int width,
464 Alignment align, float spacingmult, float spacingadd,
465 boolean includepad) {
466 this(source, 0, source.length(), paint, width, align,
467 spacingmult, spacingadd, includepad);
468 }
469
Doug Feltcb3791202011-07-07 11:57:48 -0700470 /**
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000471 * @deprecated Use {@link Builder} instead.
Doug Feltcb3791202011-07-07 11:57:48 -0700472 */
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000473 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800474 public StaticLayout(CharSequence source, int bufstart, int bufend,
475 TextPaint paint, int outerwidth,
476 Alignment align,
477 float spacingmult, float spacingadd,
478 boolean includepad) {
479 this(source, bufstart, bufend, paint, outerwidth, align,
480 spacingmult, spacingadd, includepad, null, 0);
481 }
482
Doug Feltcb3791202011-07-07 11:57:48 -0700483 /**
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000484 * @deprecated Use {@link Builder} instead.
Doug Feltcb3791202011-07-07 11:57:48 -0700485 */
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000486 @Deprecated
Doug Feltcb3791202011-07-07 11:57:48 -0700487 public StaticLayout(CharSequence source, int bufstart, int bufend,
488 TextPaint paint, int outerwidth,
489 Alignment align,
490 float spacingmult, float spacingadd,
491 boolean includepad,
492 TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
493 this(source, bufstart, bufend, paint, outerwidth, align,
494 TextDirectionHeuristics.FIRSTSTRONG_LTR,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700495 spacingmult, spacingadd, includepad, ellipsize, ellipsizedWidth, Integer.MAX_VALUE);
Doug Feltcb3791202011-07-07 11:57:48 -0700496 }
497
498 /**
499 * @hide
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000500 * @deprecated Use {@link Builder} instead.
Doug Feltcb3791202011-07-07 11:57:48 -0700501 */
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000502 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800503 public StaticLayout(CharSequence source, int bufstart, int bufend,
504 TextPaint paint, int outerwidth,
Doug Feltcb3791202011-07-07 11:57:48 -0700505 Alignment align, TextDirectionHeuristic textDir,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800506 float spacingmult, float spacingadd,
507 boolean includepad,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700508 TextUtils.TruncateAt ellipsize, int ellipsizedWidth, int maxLines) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800509 super((ellipsize == null)
Doug Felt4e0c5e52010-03-15 16:56:02 -0700510 ? source
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800511 : (source instanceof Spanned)
512 ? new SpannedEllipsizer(source)
513 : new Ellipsizer(source),
Doug Feltcb3791202011-07-07 11:57:48 -0700514 paint, outerwidth, align, textDir, spacingmult, spacingadd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800515
Raph Levienebd66ca2015-04-30 15:27:57 -0700516 Builder b = Builder.obtain(source, bufstart, bufend, paint, outerwidth)
Raph Levien39b4db72015-03-25 13:18:20 -0700517 .setAlignment(align)
Raph Leviena6a08282015-06-03 13:20:45 -0700518 .setTextDirection(textDir)
Raph Levien531c30c2015-04-30 16:29:59 -0700519 .setLineSpacing(spacingadd, spacingmult)
Raph Leviend3ab6922015-03-02 14:30:53 -0800520 .setIncludePad(includepad)
521 .setEllipsizedWidth(ellipsizedWidth)
522 .setEllipsize(ellipsize)
523 .setMaxLines(maxLines);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800524 /*
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700525 * This is annoying, but we can't refer to the layout until superclass construction is
526 * finished, and the superclass constructor wants the reference to the display text.
Doug Felt4e0c5e52010-03-15 16:56:02 -0700527 *
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700528 * In other words, the two Ellipsizer classes in Layout.java need a (Dynamic|Static)Layout
529 * as a parameter to do their calculations, but the Ellipsizers also need to be the input
530 * to the superclass's constructor (Layout). In order to go around the circular
531 * dependency, we construct the Ellipsizer with only one of the parameters, the text. And
532 * we fill in the rest of the needed information (layout, width, and method) later, here.
533 *
534 * This will break if the superclass constructor ever actually cares about the content
535 * instead of just holding the reference.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800536 */
537 if (ellipsize != null) {
538 Ellipsizer e = (Ellipsizer) getText();
539
540 e.mLayout = this;
541 e.mWidth = ellipsizedWidth;
542 e.mMethod = ellipsize;
543 mEllipsizedWidth = ellipsizedWidth;
544
545 mColumns = COLUMNS_ELLIPSIZE;
546 } else {
547 mColumns = COLUMNS_NORMAL;
548 mEllipsizedWidth = outerwidth;
549 }
550
Siyamed Siniraf398512017-07-25 19:08:42 -0700551 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2);
552 mLines = ArrayUtils.newUnpaddedIntArray(2 * mColumns);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700553 mMaximumVisibleLineCount = maxLines;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800554
Raph Levien70616ec2015-03-04 10:41:30 -0800555 generate(b, b.mIncludePad, b.mIncludePad);
Doug Felte8e45f22010-03-29 14:58:40 -0700556
Raph Leviend3ab6922015-03-02 14:30:53 -0800557 Builder.recycle(b);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800558 }
559
Clara Bayarri81a4f2e2017-12-19 16:46:56 +0000560 /**
561 * Used by DynamicLayout.
562 */
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700563 /* package */ StaticLayout(@Nullable CharSequence text) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700564 super(text, null, 0, null, 0, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800565
566 mColumns = COLUMNS_ELLIPSIZE;
Siyamed Siniraf398512017-07-25 19:08:42 -0700567 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2);
568 mLines = ArrayUtils.newUnpaddedIntArray(2 * mColumns);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800569 }
570
Raph Levien39b4db72015-03-25 13:18:20 -0700571 private StaticLayout(Builder b) {
572 super((b.mEllipsize == null)
573 ? b.mText
574 : (b.mText instanceof Spanned)
575 ? new SpannedEllipsizer(b.mText)
576 : new Ellipsizer(b.mText),
Roozbeh Pournader158dfaf2017-09-21 13:23:50 -0700577 b.mPaint, b.mWidth, b.mAlignment, b.mTextDir, b.mSpacingMult, b.mSpacingAdd);
Raph Levien39b4db72015-03-25 13:18:20 -0700578
579 if (b.mEllipsize != null) {
580 Ellipsizer e = (Ellipsizer) getText();
581
582 e.mLayout = this;
583 e.mWidth = b.mEllipsizedWidth;
584 e.mMethod = b.mEllipsize;
585 mEllipsizedWidth = b.mEllipsizedWidth;
586
587 mColumns = COLUMNS_ELLIPSIZE;
588 } else {
589 mColumns = COLUMNS_NORMAL;
590 mEllipsizedWidth = b.mWidth;
591 }
592
Siyamed Siniraf398512017-07-25 19:08:42 -0700593 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2);
594 mLines = ArrayUtils.newUnpaddedIntArray(2 * mColumns);
Raph Levien39b4db72015-03-25 13:18:20 -0700595 mMaximumVisibleLineCount = b.mMaxLines;
596
Raph Levien2ea52902015-07-01 14:39:31 -0700597 mLeftIndents = b.mLeftIndents;
598 mRightIndents = b.mRightIndents;
Roozbeh Pournader9a9179d2017-08-29 14:14:28 -0700599 mLeftPaddings = b.mLeftPaddings;
600 mRightPaddings = b.mRightPaddings;
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700601 setJustificationMode(b.mJustificationMode);
Raph Levien2ea52902015-07-01 14:39:31 -0700602
Raph Levien39b4db72015-03-25 13:18:20 -0700603 generate(b, b.mIncludePad, b.mIncludePad);
604 }
605
Raph Leviend3ab6922015-03-02 14:30:53 -0800606 /* package */ void generate(Builder b, boolean includepad, boolean trackpad) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700607 final CharSequence source = b.mText;
Seigo Nonakafbe63bd2017-12-02 19:28:05 -0800608 final int bufStart = b.mStart;
609 final int bufEnd = b.mEnd;
Raph Leviend3ab6922015-03-02 14:30:53 -0800610 TextPaint paint = b.mPaint;
611 int outerWidth = b.mWidth;
612 TextDirectionHeuristic textDir = b.mTextDir;
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700613 final boolean fallbackLineSpacing = b.mFallbackLineSpacing;
Raph Leviend3ab6922015-03-02 14:30:53 -0800614 float spacingmult = b.mSpacingMult;
615 float spacingadd = b.mSpacingAdd;
616 float ellipsizedWidth = b.mEllipsizedWidth;
617 TextUtils.TruncateAt ellipsize = b.mEllipsize;
Siyamed Sinir442c1512017-07-24 12:18:27 -0700618 final boolean addLastLineSpacing = b.mAddLastLineLineSpacing;
Raph Levien4c1f12e2015-03-02 16:29:23 -0800619 LineBreaks lineBreaks = new LineBreaks(); // TODO: move to builder to avoid allocation costs
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800620 FloatArray widths = new FloatArray();
Anish Athalye88b5b0b2014-06-24 14:39:43 -0700621
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800622 mLineCount = 0;
Siyamed Sinira19cd512017-08-03 22:01:56 -0700623 mEllipsized = false;
Siyamed Sinira8d982d2017-08-21 13:27:47 -0700624 mMaxLineHeight = mMaximumVisibleLineCount < 1 ? 0 : DEFAULT_MAX_LINE_HEIGHT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800625
626 int v = 0;
627 boolean needMultiply = (spacingmult != 1 || spacingadd != 0);
628
Raph Leviend3ab6922015-03-02 14:30:53 -0800629 Paint.FontMetricsInt fm = b.mFontMetricsInt;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800630 int[] chooseHtv = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800631
Seigo Nonakabafe1972017-08-24 15:30:29 -0700632 final int[] indents;
633 if (mLeftIndents != null || mRightIndents != null) {
634 final int leftLen = mLeftIndents == null ? 0 : mLeftIndents.length;
635 final int rightLen = mRightIndents == null ? 0 : mRightIndents.length;
636 final int indentsLen = Math.max(leftLen, rightLen);
637 indents = new int[indentsLen];
638 for (int i = 0; i < leftLen; i++) {
639 indents[i] = mLeftIndents[i];
640 }
641 for (int i = 0; i < rightLen; i++) {
642 indents[i] += mRightIndents[i];
643 }
644 } else {
645 indents = null;
646 }
647
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700648 final long nativePtr = nInit(
649 b.mBreakStrategy, b.mHyphenationFrequency,
650 // TODO: Support more justification mode, e.g. letter spacing, stretching.
651 b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE,
652 indents, mLeftPaddings, mRightPaddings);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800653
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800654 MeasuredText measured = null;
Seigo Nonakafbe63bd2017-12-02 19:28:05 -0800655 final Spanned spanned;
Seigo Nonaka87b15472018-01-12 14:06:29 -0800656 final boolean canUseMeasuredText;
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800657 if (source instanceof MeasuredText) {
658 measured = (MeasuredText) source;
Seigo Nonakafbe63bd2017-12-02 19:28:05 -0800659
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800660 if (bufStart != measured.getStart() || bufEnd != measured.getEnd()) {
Seigo Nonakafbe63bd2017-12-02 19:28:05 -0800661 // The buffer position has changed. Re-measure here.
Seigo Nonaka87b15472018-01-12 14:06:29 -0800662 canUseMeasuredText = false;
663 } else if (b.mBreakStrategy != measured.getBreakStrategy()
664 || b.mHyphenationFrequency != measured.getHyphenationFrequency()) {
665 // The computed hyphenation pieces may not be able to used. Re-measure it.
666 canUseMeasuredText = false;
Seigo Nonakafbe63bd2017-12-02 19:28:05 -0800667 } else {
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800668 // We can use measured information.
Seigo Nonaka87b15472018-01-12 14:06:29 -0800669 canUseMeasuredText = true;
Seigo Nonakafbe63bd2017-12-02 19:28:05 -0800670 }
671 } else {
Seigo Nonaka87b15472018-01-12 14:06:29 -0800672 canUseMeasuredText = false;
673 }
674
675 if (!canUseMeasuredText) {
676 measured = new MeasuredText.Builder(source, paint)
677 .setRange(bufStart, bufEnd)
678 .setTextDirection(textDir)
679 .setBreakStrategy(b.mBreakStrategy)
680 .setHyphenationFrequency(b.mHyphenationFrequency)
681 .build();
Seigo Nonakafbe63bd2017-12-02 19:28:05 -0800682 spanned = (source instanceof Spanned) ? (Spanned) source : null;
Seigo Nonaka87b15472018-01-12 14:06:29 -0800683 } else {
684 final CharSequence original = measured.getText();
685 spanned = (original instanceof Spanned) ? (Spanned) original : null;
686 // Overwrite with the one when measured.
687 // TODO: Give an option for developer not to overwrite and measure again here?
688 textDir = measured.getTextDir();
689 paint = measured.getPaint();
Seigo Nonakafbe63bd2017-12-02 19:28:05 -0800690 }
691
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700692 try {
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800693 for (int paraIndex = 0; paraIndex < measured.getParagraphCount(); paraIndex++) {
694 final int paraStart = measured.getParagraphStart(paraIndex);
695 final int paraEnd = measured.getParagraphEnd(paraIndex);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800696
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700697 int firstWidthLineCount = 1;
698 int firstWidth = outerWidth;
699 int restWidth = outerWidth;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800700
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700701 LineHeightSpan[] chooseHt = null;
Doug Feltcb3791202011-07-07 11:57:48 -0700702
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700703 if (spanned != null) {
704 LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
705 LeadingMarginSpan.class);
706 for (int i = 0; i < sp.length; i++) {
707 LeadingMarginSpan lms = sp[i];
708 firstWidth -= sp[i].getLeadingMargin(true);
709 restWidth -= sp[i].getLeadingMargin(false);
710
711 // LeadingMarginSpan2 is odd. The count affects all
712 // leading margin spans, not just this particular one
713 if (lms instanceof LeadingMarginSpan2) {
714 LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
715 firstWidthLineCount = Math.max(firstWidthLineCount,
716 lms2.getLeadingMarginLineCount());
717 }
718 }
719
720 chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
721
722 if (chooseHt.length == 0) {
723 chooseHt = null; // So that out() would not assume it has any contents
724 } else {
725 if (chooseHtv == null || chooseHtv.length < chooseHt.length) {
726 chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
727 }
728
729 for (int i = 0; i < chooseHt.length; i++) {
730 int o = spanned.getSpanStart(chooseHt[i]);
731
732 if (o < paraStart) {
733 // starts in this layout, before the
734 // current paragraph
735
736 chooseHtv[i] = getLineTop(getLineForOffset(o));
737 } else {
738 // starts in this paragraph
739
740 chooseHtv[i] = v;
741 }
742 }
Mark Wagner7b5676e2009-10-16 11:44:23 -0700743 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800744 }
745
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700746 // tab stop locations
747 int[] variableTabStops = null;
748 if (spanned != null) {
749 TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
750 paraEnd, TabStopSpan.class);
751 if (spans.length > 0) {
752 int[] stops = new int[spans.length];
753 for (int i = 0; i < spans.length; i++) {
754 stops[i] = spans[i].getTabStop();
755 }
756 Arrays.sort(stops, 0, stops.length);
757 variableTabStops = stops;
758 }
759 }
760
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800761 final MeasuredParagraph measuredPara = measured.getMeasuredParagraph(paraIndex);
762 final char[] chs = measuredPara.getChars();
763 final int[] spanEndCache = measuredPara.getSpanEndCache().getRawArray();
764 final int[] fmCache = measuredPara.getFontMetrics().getRawArray();
Seigo Nonaka6f1868b2017-12-01 16:24:19 -0800765 // TODO: Stop keeping duplicated width copy in native and Java.
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800766 widths.resize(chs.length);
767
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700768 // measurement has to be done before performing line breaking
769 // but we don't want to recompute fontmetrics or span ranges the
770 // second time, so we cache those and then use those stored values
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700771
772 int breakCount = nComputeLineBreaks(
773 nativePtr,
774
775 // Inputs
776 chs,
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800777 measuredPara.getNativePtr(),
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700778 paraEnd - paraStart,
779 firstWidth,
780 firstWidthLineCount,
781 restWidth,
782 variableTabStops,
783 TAB_INCREMENT,
784 mLineCount,
785
786 // Outputs
787 lineBreaks,
788 lineBreaks.breaks.length,
789 lineBreaks.breaks,
790 lineBreaks.widths,
791 lineBreaks.ascents,
792 lineBreaks.descents,
793 lineBreaks.flags,
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800794 widths.getRawArray());
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700795
796 final int[] breaks = lineBreaks.breaks;
797 final float[] lineWidths = lineBreaks.widths;
798 final float[] ascents = lineBreaks.ascents;
799 final float[] descents = lineBreaks.descents;
800 final int[] flags = lineBreaks.flags;
801
802 final int remainingLineCount = mMaximumVisibleLineCount - mLineCount;
803 final boolean ellipsisMayBeApplied = ellipsize != null
804 && (ellipsize == TextUtils.TruncateAt.END
805 || (mMaximumVisibleLineCount == 1
806 && ellipsize != TextUtils.TruncateAt.MARQUEE));
807 if (0 < remainingLineCount && remainingLineCount < breakCount
808 && ellipsisMayBeApplied) {
809 // Calculate width and flag.
810 float width = 0;
811 int flag = 0; // XXX May need to also have starting hyphen edit
812 for (int i = remainingLineCount - 1; i < breakCount; i++) {
813 if (i == breakCount - 1) {
814 width += lineWidths[i];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800815 } else {
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700816 for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) {
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800817 width += widths.get(j);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700818 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800819 }
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700820 flag |= flags[i] & TAB_MASK;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800821 }
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700822 // Treat the last line and overflowed lines as a single line.
823 breaks[remainingLineCount - 1] = breaks[breakCount - 1];
824 lineWidths[remainingLineCount - 1] = width;
825 flags[remainingLineCount - 1] = flag;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800826
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700827 breakCount = remainingLineCount;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700828 }
829
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700830 // here is the offset of the starting character of the line we are currently
831 // measuring
832 int here = paraStart;
Doug Felte8e45f22010-03-29 14:58:40 -0700833
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700834 int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
835 int fmCacheIndex = 0;
836 int spanEndCacheIndex = 0;
837 int breakIndex = 0;
838 for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
839 // retrieve end of span
840 spanEnd = spanEndCache[spanEndCacheIndex++];
Doug Felt23241882010-06-02 14:41:06 -0700841
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700842 // retrieve cached metrics, order matches above
843 fm.top = fmCache[fmCacheIndex * 4 + 0];
844 fm.bottom = fmCache[fmCacheIndex * 4 + 1];
845 fm.ascent = fmCache[fmCacheIndex * 4 + 2];
846 fm.descent = fmCache[fmCacheIndex * 4 + 3];
847 fmCacheIndex++;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800848
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700849 if (fm.top < fmTop) {
Anish Athalyec8f9e622014-07-21 15:26:34 -0700850 fmTop = fm.top;
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700851 }
852 if (fm.ascent < fmAscent) {
Anish Athalyec8f9e622014-07-21 15:26:34 -0700853 fmAscent = fm.ascent;
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700854 }
855 if (fm.descent > fmDescent) {
Anish Athalyec8f9e622014-07-21 15:26:34 -0700856 fmDescent = fm.descent;
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700857 }
858 if (fm.bottom > fmBottom) {
859 fmBottom = fm.bottom;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700860 }
861
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700862 // skip breaks ending before current span range
863 while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
864 breakIndex++;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700865 }
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700866
867 while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
868 int endPos = paraStart + breaks[breakIndex];
869
870 boolean moreChars = (endPos < bufEnd);
871
872 final int ascent = fallbackLineSpacing
873 ? Math.min(fmAscent, Math.round(ascents[breakIndex]))
874 : fmAscent;
875 final int descent = fallbackLineSpacing
876 ? Math.max(fmDescent, Math.round(descents[breakIndex]))
877 : fmDescent;
878 v = out(source, here, endPos,
879 ascent, descent, fmTop, fmBottom,
880 v, spacingmult, spacingadd, chooseHt, chooseHtv, fm,
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800881 flags[breakIndex], needMultiply, measuredPara, bufEnd,
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800882 includepad, trackpad, addLastLineSpacing, chs, widths.getRawArray(),
883 paraStart, ellipsize, ellipsizedWidth, lineWidths[breakIndex],
884 paint, moreChars);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700885
886 if (endPos < spanEnd) {
887 // preserve metrics for current span
888 fmTop = fm.top;
889 fmBottom = fm.bottom;
890 fmAscent = fm.ascent;
891 fmDescent = fm.descent;
892 } else {
893 fmTop = fmBottom = fmAscent = fmDescent = 0;
894 }
895
896 here = endPos;
897 breakIndex++;
898
899 if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) {
900 return;
901 }
902 }
903 }
904
905 if (paraEnd == bufEnd) {
906 break;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700907 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800908 }
909
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700910 if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE)
911 && mLineCount < mMaximumVisibleLineCount) {
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800912 final MeasuredParagraph measuredPara =
913 MeasuredParagraph.buildForBidi(source, bufEnd, bufEnd, textDir, null);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700914 paint.getFontMetricsInt(fm);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700915 v = out(source,
916 bufEnd, bufEnd, fm.ascent, fm.descent,
917 fm.top, fm.bottom,
918 v,
919 spacingmult, spacingadd, null,
920 null, fm, 0,
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800921 needMultiply, measuredPara, bufEnd,
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700922 includepad, trackpad, addLastLineSpacing, null,
923 null, bufStart, ellipsize,
924 ellipsizedWidth, 0, paint, false);
925 }
926 } finally {
927 nFinish(nativePtr);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800928 }
929 }
930
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700931 // The parameters that are not changed in the method are marked as final to make the code
932 // easier to understand.
933 private int out(final CharSequence text, final int start, final int end, int above, int below,
934 int top, int bottom, int v, final float spacingmult, final float spacingadd,
935 final LineHeightSpan[] chooseHt, final int[] chooseHtv, final Paint.FontMetricsInt fm,
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800936 final int flags, final boolean needMultiply, @NonNull final MeasuredParagraph measured,
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800937 final int bufEnd, final boolean includePad, final boolean trackPad,
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700938 final boolean addLastLineLineSpacing, final char[] chs, final float[] widths,
939 final int widthStart, final TextUtils.TruncateAt ellipsize, final float ellipsisWidth,
940 final float textWidth, final TextPaint paint, final boolean moreChars) {
Siyamed Siniraf398512017-07-25 19:08:42 -0700941 final int j = mLineCount;
942 final int off = j * mColumns;
943 final int want = off + mColumns + TOP;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800944 int[] lines = mLines;
Seigo Nonaka4b6bcee2017-12-11 11:39:51 -0800945 final int dir = measured.getParagraphDir();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800946
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800947 if (want >= lines.length) {
Siyamed Siniraf398512017-07-25 19:08:42 -0700948 final int[] grow = ArrayUtils.newUnpaddedIntArray(GrowingArrayUtils.growSize(want));
Adam Lesinski776abc22014-03-07 11:30:59 -0500949 System.arraycopy(lines, 0, grow, 0, lines.length);
950 mLines = grow;
951 lines = grow;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800952 }
953
Siyamed Siniraf398512017-07-25 19:08:42 -0700954 if (j >= mLineDirections.length) {
955 final Directions[] grow = ArrayUtils.newUnpaddedArray(Directions.class,
956 GrowingArrayUtils.growSize(j));
957 System.arraycopy(mLineDirections, 0, grow, 0, mLineDirections.length);
958 mLineDirections = grow;
959 }
960
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700961 lines[off + START] = start;
962 lines[off + TOP] = v;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800963
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700964 // Information about hyphenation, tabs, and directions are needed for determining
965 // ellipsization, so the values should be assigned before ellipsization.
Eric Fischera9f1dd02009-08-12 15:00:10 -0700966
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700967 // TODO: could move TAB to share same column as HYPHEN, simplifying this code and gaining
968 // one bit for start field
969 lines[off + TAB] |= flags & TAB_MASK;
970 lines[off + HYPHEN] = flags;
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700971 lines[off + DIR] |= dir << DIR_SHIFT;
Seigo Nonaka4b6bcee2017-12-11 11:39:51 -0800972 mLineDirections[j] = measured.getDirections(start - widthStart, end - widthStart);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800973
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700974 final boolean firstLine = (j == 0);
975 final boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);
Siyamed Sinir0745c722016-05-31 20:39:33 -0700976
977 if (ellipsize != null) {
978 // If there is only one line, then do any type of ellipsis except when it is MARQUEE
979 // if there are multiple lines, just allow END ellipsis on the last line
980 boolean forceEllipsis = moreChars && (mLineCount + 1 == mMaximumVisibleLineCount);
981
982 boolean doEllipsis =
983 (((mMaximumVisibleLineCount == 1 && moreChars) || (firstLine && !moreChars)) &&
984 ellipsize != TextUtils.TruncateAt.MARQUEE) ||
985 (!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
986 ellipsize == TextUtils.TruncateAt.END);
987 if (doEllipsis) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700988 calculateEllipsis(text, start, end, widths, widthStart,
989 ellipsisWidth - getTotalInsets(j), ellipsize, j,
990 textWidth, paint, forceEllipsis, dir);
Siyamed Sinir0745c722016-05-31 20:39:33 -0700991 }
992 }
993
Siyamed Sinirfb0b2dc2017-07-26 22:08:24 -0700994 final boolean lastLine;
995 if (mEllipsized) {
996 lastLine = true;
997 } else {
998 final boolean lastCharIsNewLine = widthStart != bufEnd && bufEnd > 0
999 && text.charAt(bufEnd - 1) == CHAR_NEW_LINE;
1000 if (end == bufEnd && !lastCharIsNewLine) {
1001 lastLine = true;
1002 } else if (start == bufEnd && lastCharIsNewLine) {
1003 lastLine = true;
1004 } else {
1005 lastLine = false;
1006 }
1007 }
Raph Leviend97b0972014-04-24 12:51:35 -07001008
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001009 if (chooseHt != null) {
1010 fm.ascent = above;
1011 fm.descent = below;
1012 fm.top = top;
1013 fm.bottom = bottom;
1014
1015 for (int i = 0; i < chooseHt.length; i++) {
1016 if (chooseHt[i] instanceof LineHeightSpan.WithDensity) {
1017 ((LineHeightSpan.WithDensity) chooseHt[i])
1018 .chooseHeight(text, start, end, chooseHtv[i], v, fm, paint);
1019
1020 } else {
1021 chooseHt[i].chooseHeight(text, start, end, chooseHtv[i], v, fm);
1022 }
1023 }
1024
1025 above = fm.ascent;
1026 below = fm.descent;
1027 top = fm.top;
1028 bottom = fm.bottom;
1029 }
1030
Raph Leviend97b0972014-04-24 12:51:35 -07001031 if (firstLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001032 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001033 mTopPadding = top - above;
1034 }
1035
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001036 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001037 above = top;
1038 }
1039 }
Raph Leviend97b0972014-04-24 12:51:35 -07001040
Raph Leviend97b0972014-04-24 12:51:35 -07001041 if (lastLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001042 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001043 mBottomPadding = bottom - below;
1044 }
1045
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001046 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001047 below = bottom;
1048 }
1049 }
1050
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001051 final int extra;
Siyamed Sinir442c1512017-07-24 12:18:27 -07001052 if (needMultiply && (addLastLineLineSpacing || !lastLine)) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001053 final double ex = (below - above) * (spacingmult - 1) + spacingadd;
Doug Felt10657582010-02-22 11:19:01 -08001054 if (ex >= 0) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001055 extra = (int)(ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -08001056 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001057 extra = -(int)(-ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -08001058 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001059 } else {
1060 extra = 0;
1061 }
1062
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001063 lines[off + DESCENT] = below + extra;
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001064 lines[off + EXTRA] = extra;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001065
Siyamed Sinir0745c722016-05-31 20:39:33 -07001066 // special case for non-ellipsized last visible line when maxLines is set
1067 // store the height as if it was ellipsized
1068 if (!mEllipsized && currentLineIsTheLastVisibleOne) {
1069 // below calculation as if it was the last line
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001070 final int maxLineBelow = includePad ? bottom : below;
Siyamed Sinir0745c722016-05-31 20:39:33 -07001071 // similar to the calculation of v below, without the extra.
1072 mMaxLineHeight = v + (maxLineBelow - above);
1073 }
1074
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001075 v += (below - above) + extra;
1076 lines[off + mColumns + START] = end;
1077 lines[off + mColumns + TOP] = v;
1078
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001079 mLineCount++;
1080 return v;
1081 }
1082
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001083 private void calculateEllipsis(CharSequence text, int lineStart, int lineEnd, float[] widths,
1084 int widthStart, float avail, TextUtils.TruncateAt where, int line, float textWidth,
1085 TextPaint paint, boolean forceEllipsis, int dir) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001086 if (textWidth <= avail && !forceEllipsis) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001087 // Everything fits!
1088 mLines[mColumns * line + ELLIPSIS_START] = 0;
1089 mLines[mColumns * line + ELLIPSIS_COUNT] = 0;
1090 return;
1091 }
1092
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001093 float tempAvail = avail;
1094 int numberOfTries = 0;
1095 boolean lineFits = false;
1096 mWorkPaint.set(paint);
1097 do {
1098 final float ellipsizedWidth = guessEllipsis(text, lineStart, lineEnd, widths,
Siyamed Sinira273a702017-10-05 11:22:12 -07001099 widthStart, tempAvail, where, line, mWorkPaint, forceEllipsis, dir);
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001100 if (ellipsizedWidth <= avail) {
1101 lineFits = true;
1102 } else {
1103 numberOfTries++;
1104 if (numberOfTries > 10) {
1105 // If the text still doesn't fit after ten tries, assume it will never fit and
1106 // ellipsize it all.
1107 mLines[mColumns * line + ELLIPSIS_START] = 0;
1108 mLines[mColumns * line + ELLIPSIS_COUNT] = lineEnd - lineStart;
1109 lineFits = true;
1110 } else {
1111 // Some side effect of ellipsization has caused the text to go over the
1112 // available width. Let's make the available width shorter by exactly that
1113 // amount and retry.
1114 tempAvail -= ellipsizedWidth - avail;
1115 }
1116 }
1117 } while (!lineFits);
1118 mEllipsized = true;
1119 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001120
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001121 // Returns the width of the ellipsized line which in some rare cases can actually be larger
1122 // than 'avail' (due to kerning or other context-based effect of replacement of text by
1123 // ellipsis). If all the line needs to ellipsized away, or it's an invalud hyphenation mode,
1124 // returns 0 so the caller can stop iterating.
1125 //
1126 // This method temporarily modifies the TextPaint passed to it, so the TextPaint passed to it
1127 // should not be accessed while the method is running.
1128 private float guessEllipsis(CharSequence text, int lineStart, int lineEnd, float[] widths,
Siyamed Sinira273a702017-10-05 11:22:12 -07001129 int widthStart, float avail, TextUtils.TruncateAt where, int line,
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001130 TextPaint paint, boolean forceEllipsis, int dir) {
1131 final int savedHyphenEdit = paint.getHyphenEdit();
1132 paint.setHyphenEdit(0);
1133 final float ellipsisWidth = paint.measureText(TextUtils.getEllipsisString(where));
1134 final int ellipsisStart;
1135 final int ellipsisCount;
1136 final int len = lineEnd - lineStart;
1137 final int offset = lineStart - widthStart;
1138
1139 int hyphen = getHyphen(line);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001140 // We only support start ellipsis on a single line
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001141 if (where == TextUtils.TruncateAt.START) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001142 if (mMaximumVisibleLineCount == 1) {
1143 float sum = 0;
1144 int i;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001145
Keisuke Kuroyanagied2eea12015-04-14 18:18:35 +09001146 for (i = len; i > 0; i--) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001147 final float w = widths[i - 1 + offset];
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001148 if (w + sum + ellipsisWidth > avail) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001149 while (i < len && widths[i + offset] == 0.0f) {
Keisuke Kuroyanagi82d1c442016-09-14 14:30:14 +09001150 i++;
1151 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001152 break;
1153 }
1154
1155 sum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001156 }
1157
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001158 ellipsisStart = 0;
1159 ellipsisCount = i;
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001160 // Strip the potential hyphenation at beginning of line.
1161 hyphen &= ~Paint.HYPHENEDIT_MASK_START_OF_LINE;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001162 } else {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001163 ellipsisStart = 0;
1164 ellipsisCount = 0;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001165 if (Log.isLoggable(TAG, Log.WARN)) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001166 Log.w(TAG, "Start ellipsis only supported with one line");
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001167 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001168 }
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001169 } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE ||
1170 where == TextUtils.TruncateAt.END_SMALL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001171 float sum = 0;
1172 int i;
1173
1174 for (i = 0; i < len; i++) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001175 final float w = widths[i + offset];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001176
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001177 if (w + sum + ellipsisWidth > avail) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001178 break;
1179 }
1180
1181 sum += w;
1182 }
1183
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001184 if (forceEllipsis && i == len && len > 0) {
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -07001185 ellipsisStart = len - 1;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001186 ellipsisCount = 1;
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001187 } else {
1188 ellipsisStart = i;
1189 ellipsisCount = len - i;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001190 }
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001191 // Strip the potential hyphenation at end of line.
1192 hyphen &= ~Paint.HYPHENEDIT_MASK_END_OF_LINE;
1193 } else { // where = TextUtils.TruncateAt.MIDDLE
1194 // We only support middle ellipsis on a single line.
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001195 if (mMaximumVisibleLineCount == 1) {
1196 float lsum = 0, rsum = 0;
1197 int left = 0, right = len;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001198
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001199 final float ravail = (avail - ellipsisWidth) / 2;
Raph Levien0e3c5e82014-12-04 13:26:07 -08001200 for (right = len; right > 0; right--) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001201 final float w = widths[right - 1 + offset];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001202
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001203 if (w + rsum > ravail) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001204 while (right < len && widths[right + offset] == 0.0f) {
Keisuke Kuroyanagi82d1c442016-09-14 14:30:14 +09001205 right++;
1206 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001207 break;
1208 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001209 rsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001210 }
1211
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001212 final float lavail = avail - ellipsisWidth - rsum;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001213 for (left = 0; left < right; left++) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001214 final float w = widths[left + offset];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001215
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001216 if (w + lsum > lavail) {
1217 break;
1218 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001219
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001220 lsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001221 }
1222
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001223 ellipsisStart = left;
1224 ellipsisCount = right - left;
1225 } else {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001226 ellipsisStart = 0;
1227 ellipsisCount = 0;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001228 if (Log.isLoggable(TAG, Log.WARN)) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001229 Log.w(TAG, "Middle ellipsis only supported with one line");
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001230 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001231 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001232 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001233 mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
1234 mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001235
1236 if (ellipsisStart == 0 && (ellipsisCount == 0 || ellipsisCount == len)) {
1237 // Unsupported ellipsization mode or all text is ellipsized away. Return 0.
1238 return 0.0f;
1239 }
1240
1241 final boolean isSpanned = text instanceof Spanned;
1242 final Ellipsizer ellipsizedText = isSpanned
1243 ? new SpannedEllipsizer(text)
1244 : new Ellipsizer(text);
1245 ellipsizedText.mLayout = this;
1246 ellipsizedText.mMethod = where;
1247
1248 final boolean hasTabs = getLineContainsTab(line);
1249 final TabStops tabStops;
1250 if (hasTabs && isSpanned) {
1251 final TabStopSpan[] tabs = getParagraphSpans((Spanned) ellipsizedText, lineStart,
1252 lineEnd, TabStopSpan.class);
1253 if (tabs.length == 0) {
1254 tabStops = null;
1255 } else {
1256 tabStops = new TabStops(TAB_INCREMENT, tabs);
1257 }
1258 } else {
1259 tabStops = null;
1260 }
1261 paint.setHyphenEdit(hyphen);
1262 final TextLine textline = TextLine.obtain();
1263 textline.set(paint, ellipsizedText, lineStart, lineEnd, dir, getLineDirections(line),
1264 hasTabs, tabStops);
1265 // Since TextLine.metric() returns negative values for RTL text, multiplication by dir
1266 // converts it to an actual width. Note that we don't want to use the absolute value,
1267 // since we may actually have glyphs with negative advances, which by definition always
1268 // fit.
1269 final float ellipsizedWidth = textline.metrics(null) * dir;
1270 TextLine.recycle(textline);
1271 paint.setHyphenEdit(savedHyphenEdit);
1272 return ellipsizedWidth;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001273 }
1274
Selim Cinek365ec092017-03-09 00:10:52 -08001275 private float getTotalInsets(int line) {
1276 int totalIndent = 0;
1277 if (mLeftIndents != null) {
1278 totalIndent = mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1279 }
1280 if (mRightIndents != null) {
1281 totalIndent += mRightIndents[Math.min(line, mRightIndents.length - 1)];
1282 }
1283 return totalIndent;
1284 }
1285
Doug Felte8e45f22010-03-29 14:58:40 -07001286 // Override the base class so we can directly access our members,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001287 // rather than relying on member functions.
1288 // The logic mirrors that of Layout.getLineForVertical
1289 // FIXME: It may be faster to do a linear search for layouts without many lines.
Gilles Debunne66111472010-11-19 11:04:37 -08001290 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001291 public int getLineForVertical(int vertical) {
1292 int high = mLineCount;
1293 int low = -1;
1294 int guess;
1295 int[] lines = mLines;
1296 while (high - low > 1) {
1297 guess = (high + low) >> 1;
1298 if (lines[mColumns * guess + TOP] > vertical){
1299 high = guess;
1300 } else {
1301 low = guess;
1302 }
1303 }
1304 if (low < 0) {
1305 return 0;
1306 } else {
1307 return low;
1308 }
1309 }
1310
Gilles Debunne66111472010-11-19 11:04:37 -08001311 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001312 public int getLineCount() {
1313 return mLineCount;
1314 }
1315
Gilles Debunne66111472010-11-19 11:04:37 -08001316 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001317 public int getLineTop(int line) {
Raph Levien07e6c232016-04-04 12:34:06 -07001318 return mLines[mColumns * line + TOP];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001319 }
1320
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001321 /**
1322 * @hide
1323 */
1324 @Override
1325 public int getLineExtra(int line) {
1326 return mLines[mColumns * line + EXTRA];
1327 }
1328
Gilles Debunne66111472010-11-19 11:04:37 -08001329 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001330 public int getLineDescent(int line) {
Raph Levien07e6c232016-04-04 12:34:06 -07001331 return mLines[mColumns * line + DESCENT];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001332 }
1333
Gilles Debunne66111472010-11-19 11:04:37 -08001334 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001335 public int getLineStart(int line) {
1336 return mLines[mColumns * line + START] & START_MASK;
1337 }
1338
Gilles Debunne66111472010-11-19 11:04:37 -08001339 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001340 public int getParagraphDirection(int line) {
1341 return mLines[mColumns * line + DIR] >> DIR_SHIFT;
1342 }
1343
Gilles Debunne66111472010-11-19 11:04:37 -08001344 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001345 public boolean getLineContainsTab(int line) {
1346 return (mLines[mColumns * line + TAB] & TAB_MASK) != 0;
1347 }
1348
Gilles Debunne66111472010-11-19 11:04:37 -08001349 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001350 public final Directions getLineDirections(int line) {
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001351 if (line > getLineCount()) {
1352 throw new ArrayIndexOutOfBoundsException();
1353 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001354 return mLineDirections[line];
1355 }
1356
Gilles Debunne66111472010-11-19 11:04:37 -08001357 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001358 public int getTopPadding() {
1359 return mTopPadding;
1360 }
1361
Gilles Debunne66111472010-11-19 11:04:37 -08001362 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001363 public int getBottomPadding() {
1364 return mBottomPadding;
1365 }
1366
Raph Levien26d443a2015-03-30 14:18:32 -07001367 /**
1368 * @hide
1369 */
1370 @Override
1371 public int getHyphen(int line) {
Keisuke Kuroyanagif5af4a32016-08-31 21:40:53 +09001372 return mLines[mColumns * line + HYPHEN] & HYPHEN_MASK;
Raph Levien26d443a2015-03-30 14:18:32 -07001373 }
1374
Raph Levien2ea52902015-07-01 14:39:31 -07001375 /**
1376 * @hide
1377 */
1378 @Override
1379 public int getIndentAdjust(int line, Alignment align) {
1380 if (align == Alignment.ALIGN_LEFT) {
1381 if (mLeftIndents == null) {
1382 return 0;
1383 } else {
1384 return mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1385 }
1386 } else if (align == Alignment.ALIGN_RIGHT) {
1387 if (mRightIndents == null) {
1388 return 0;
1389 } else {
1390 return -mRightIndents[Math.min(line, mRightIndents.length - 1)];
1391 }
1392 } else if (align == Alignment.ALIGN_CENTER) {
1393 int left = 0;
1394 if (mLeftIndents != null) {
1395 left = mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1396 }
1397 int right = 0;
1398 if (mRightIndents != null) {
1399 right = mRightIndents[Math.min(line, mRightIndents.length - 1)];
1400 }
1401 return (left - right) >> 1;
1402 } else {
1403 throw new AssertionError("unhandled alignment " + align);
1404 }
1405 }
1406
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001407 @Override
1408 public int getEllipsisCount(int line) {
1409 if (mColumns < COLUMNS_ELLIPSIZE) {
1410 return 0;
1411 }
1412
1413 return mLines[mColumns * line + ELLIPSIS_COUNT];
1414 }
1415
1416 @Override
1417 public int getEllipsisStart(int line) {
1418 if (mColumns < COLUMNS_ELLIPSIZE) {
1419 return 0;
1420 }
1421
1422 return mLines[mColumns * line + ELLIPSIS_START];
1423 }
1424
1425 @Override
1426 public int getEllipsizedWidth() {
1427 return mEllipsizedWidth;
1428 }
1429
Siyamed Sinir0745c722016-05-31 20:39:33 -07001430 /**
1431 * Return the total height of this layout.
1432 *
1433 * @param cap if true and max lines is set, returns the height of the layout at the max lines.
1434 *
1435 * @hide
1436 */
1437 public int getHeight(boolean cap) {
1438 if (cap && mLineCount >= mMaximumVisibleLineCount && mMaxLineHeight == -1 &&
1439 Log.isLoggable(TAG, Log.WARN)) {
1440 Log.w(TAG, "maxLineHeight should not be -1. "
1441 + " maxLines:" + mMaximumVisibleLineCount
1442 + " lineCount:" + mLineCount);
1443 }
1444
1445 return cap && mLineCount >= mMaximumVisibleLineCount && mMaxLineHeight != -1 ?
1446 mMaxLineHeight : super.getHeight();
1447 }
1448
Seigo Nonakab90e08f2017-10-20 15:23:51 -07001449 @FastNative
1450 private static native long nInit(
1451 @BreakStrategy int breakStrategy,
1452 @HyphenationFrequency int hyphenationFrequency,
1453 boolean isJustified,
1454 @Nullable int[] indents,
1455 @Nullable int[] leftPaddings,
1456 @Nullable int[] rightPaddings);
1457
1458 @CriticalNative
1459 private static native void nFinish(long nativePtr);
1460
Anish Athalyec8f9e622014-07-21 15:26:34 -07001461 // populates LineBreaks and returns the number of breaks found
1462 //
1463 // the arrays inside the LineBreaks objects are passed in as well
1464 // to reduce the number of JNI calls in the common case where the
1465 // arrays do not have to be resized
Seigo Nonaka2dd1a552017-10-06 11:41:00 -07001466 // The individual character widths will be returned in charWidths. The length of charWidths must
1467 // be at least the length of the text.
Seigo Nonakab90e08f2017-10-20 15:23:51 -07001468 private static native int nComputeLineBreaks(
1469 /* non zero */ long nativePtr,
1470
1471 // Inputs
1472 @NonNull char[] text,
Seigo Nonaka6f1868b2017-12-01 16:24:19 -08001473 /* Non Zero */ long measuredTextPtr,
Seigo Nonakab90e08f2017-10-20 15:23:51 -07001474 @IntRange(from = 0) int length,
1475 @FloatRange(from = 0.0f) float firstWidth,
1476 @IntRange(from = 0) int firstWidthLineCount,
1477 @FloatRange(from = 0.0f) float restWidth,
1478 @Nullable int[] variableTabStops,
1479 int defaultTabStop,
1480 @IntRange(from = 0) int indentsOffset,
1481
1482 // Outputs
1483 @NonNull LineBreaks recycle,
1484 @IntRange(from = 0) int recycleLength,
1485 @NonNull int[] recycleBreaks,
1486 @NonNull float[] recycleWidths,
1487 @NonNull float[] recycleAscents,
1488 @NonNull float[] recycleDescents,
1489 @NonNull int[] recycleFlags,
1490 @NonNull float[] charWidths);
Anish Athalye88b5b0b2014-06-24 14:39:43 -07001491
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001492 private int mLineCount;
1493 private int mTopPadding, mBottomPadding;
1494 private int mColumns;
1495 private int mEllipsizedWidth;
1496
Siyamed Sinir0745c722016-05-31 20:39:33 -07001497 /**
1498 * Keeps track if ellipsize is applied to the text.
1499 */
1500 private boolean mEllipsized;
1501
1502 /**
1503 * If maxLines is set, ellipsize is not set, and the actual line count of text is greater than
1504 * or equal to maxLine, this variable holds the ideal visual height of the maxLine'th line
1505 * starting from the top of the layout. If maxLines is not set its value will be -1.
1506 *
1507 * The value is the same as getLineTop(maxLines) for ellipsized version where structurally no
1508 * more than maxLines is contained.
1509 */
Siyamed Sinira19cd512017-08-03 22:01:56 -07001510 private int mMaxLineHeight = DEFAULT_MAX_LINE_HEIGHT;
Siyamed Sinir0745c722016-05-31 20:39:33 -07001511
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001512 private TextPaint mWorkPaint = new TextPaint();
1513
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001514 private static final int COLUMNS_NORMAL = 5;
1515 private static final int COLUMNS_ELLIPSIZE = 7;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001516 private static final int START = 0;
1517 private static final int DIR = START;
1518 private static final int TAB = START;
1519 private static final int TOP = 1;
1520 private static final int DESCENT = 2;
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001521 private static final int EXTRA = 3;
1522 private static final int HYPHEN = 4;
1523 private static final int ELLIPSIS_START = 5;
1524 private static final int ELLIPSIS_COUNT = 6;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001525
1526 private int[] mLines;
1527 private Directions[] mLineDirections;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001528 private int mMaximumVisibleLineCount = Integer.MAX_VALUE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001529
1530 private static final int START_MASK = 0x1FFFFFFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001531 private static final int DIR_SHIFT = 30;
1532 private static final int TAB_MASK = 0x20000000;
Keisuke Kuroyanagif5af4a32016-08-31 21:40:53 +09001533 private static final int HYPHEN_MASK = 0xFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001534
Doug Feltc982f602010-05-25 11:51:40 -07001535 private static final int TAB_INCREMENT = 20; // same as Layout, but that's private
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001536
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001537 private static final char CHAR_NEW_LINE = '\n';
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001538
1539 private static final double EXTRA_ROUNDING = 0.5;
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001540
Siyamed Sinira19cd512017-08-03 22:01:56 -07001541 private static final int DEFAULT_MAX_LINE_HEIGHT = -1;
1542
Anish Athalyec8f9e622014-07-21 15:26:34 -07001543 // This is used to return three arrays from a single JNI call when
1544 // performing line breaking
Deepanshu Gupta70539192014-10-15 15:57:40 -07001545 /*package*/ static class LineBreaks {
Anish Athalyec8f9e622014-07-21 15:26:34 -07001546 private static final int INITIAL_SIZE = 16;
1547 public int[] breaks = new int[INITIAL_SIZE];
1548 public float[] widths = new float[INITIAL_SIZE];
Roozbeh Pournader737dfea2017-08-10 11:32:24 -07001549 public float[] ascents = new float[INITIAL_SIZE];
1550 public float[] descents = new float[INITIAL_SIZE];
Roozbeh Pournader112d9c72015-08-07 12:44:41 -07001551 public int[] flags = new int[INITIAL_SIZE]; // hasTab
Anish Athalyec8f9e622014-07-21 15:26:34 -07001552 // breaks, widths, and flags should all have the same length
1553 }
1554
Seigo Nonakabafe1972017-08-24 15:30:29 -07001555 @Nullable private int[] mLeftIndents;
1556 @Nullable private int[] mRightIndents;
Roozbeh Pournader9a9179d2017-08-29 14:14:28 -07001557 @Nullable private int[] mLeftPaddings;
1558 @Nullable private int[] mRightPaddings;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001559}