blob: 36bec863e7ac035d3cc330cf1110c072fd23c082 [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 Nonaka9d3bd082018-01-11 10:02:12 -0800656 if (source instanceof MeasuredText) {
657 measured = (MeasuredText) source;
Seigo Nonakafbe63bd2017-12-02 19:28:05 -0800658
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800659 final CharSequence original = measured.getText();
Seigo Nonakafbe63bd2017-12-02 19:28:05 -0800660 spanned = (original instanceof Spanned) ? (Spanned) original : null;
661
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800662 if (bufStart != measured.getStart() || bufEnd != measured.getEnd()) {
Seigo Nonakafbe63bd2017-12-02 19:28:05 -0800663 // The buffer position has changed. Re-measure here.
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800664 measured = MeasuredText.build(original, paint, textDir, bufStart, bufEnd);
Seigo Nonakafbe63bd2017-12-02 19:28:05 -0800665 } else {
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800666 // We can use measured information.
Seigo Nonakafbe63bd2017-12-02 19:28:05 -0800667
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800668 // Overwrite with the one when emeasured.
Seigo Nonakafbe63bd2017-12-02 19:28:05 -0800669 // TODO: Give an option for developer not to overwrite and measure again here?
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800670 textDir = measured.getTextDir();
671 paint = measured.getPaint();
Seigo Nonakafbe63bd2017-12-02 19:28:05 -0800672 }
673 } else {
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800674 measured = MeasuredText.build(source, paint, textDir, bufStart, bufEnd);
Seigo Nonakafbe63bd2017-12-02 19:28:05 -0800675 spanned = (source instanceof Spanned) ? (Spanned) source : null;
676 }
677
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700678 try {
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800679 for (int paraIndex = 0; paraIndex < measured.getParagraphCount(); paraIndex++) {
680 final int paraStart = measured.getParagraphStart(paraIndex);
681 final int paraEnd = measured.getParagraphEnd(paraIndex);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800682
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700683 int firstWidthLineCount = 1;
684 int firstWidth = outerWidth;
685 int restWidth = outerWidth;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800686
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700687 LineHeightSpan[] chooseHt = null;
Doug Feltcb3791202011-07-07 11:57:48 -0700688
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700689 if (spanned != null) {
690 LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
691 LeadingMarginSpan.class);
692 for (int i = 0; i < sp.length; i++) {
693 LeadingMarginSpan lms = sp[i];
694 firstWidth -= sp[i].getLeadingMargin(true);
695 restWidth -= sp[i].getLeadingMargin(false);
696
697 // LeadingMarginSpan2 is odd. The count affects all
698 // leading margin spans, not just this particular one
699 if (lms instanceof LeadingMarginSpan2) {
700 LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
701 firstWidthLineCount = Math.max(firstWidthLineCount,
702 lms2.getLeadingMarginLineCount());
703 }
704 }
705
706 chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
707
708 if (chooseHt.length == 0) {
709 chooseHt = null; // So that out() would not assume it has any contents
710 } else {
711 if (chooseHtv == null || chooseHtv.length < chooseHt.length) {
712 chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
713 }
714
715 for (int i = 0; i < chooseHt.length; i++) {
716 int o = spanned.getSpanStart(chooseHt[i]);
717
718 if (o < paraStart) {
719 // starts in this layout, before the
720 // current paragraph
721
722 chooseHtv[i] = getLineTop(getLineForOffset(o));
723 } else {
724 // starts in this paragraph
725
726 chooseHtv[i] = v;
727 }
728 }
Mark Wagner7b5676e2009-10-16 11:44:23 -0700729 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800730 }
731
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700732 // tab stop locations
733 int[] variableTabStops = null;
734 if (spanned != null) {
735 TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
736 paraEnd, TabStopSpan.class);
737 if (spans.length > 0) {
738 int[] stops = new int[spans.length];
739 for (int i = 0; i < spans.length; i++) {
740 stops[i] = spans[i].getTabStop();
741 }
742 Arrays.sort(stops, 0, stops.length);
743 variableTabStops = stops;
744 }
745 }
746
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800747 final MeasuredParagraph measuredPara = measured.getMeasuredParagraph(paraIndex);
748 final char[] chs = measuredPara.getChars();
749 final int[] spanEndCache = measuredPara.getSpanEndCache().getRawArray();
750 final int[] fmCache = measuredPara.getFontMetrics().getRawArray();
Seigo Nonaka6f1868b2017-12-01 16:24:19 -0800751 // TODO: Stop keeping duplicated width copy in native and Java.
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800752 widths.resize(chs.length);
753
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700754 // measurement has to be done before performing line breaking
755 // but we don't want to recompute fontmetrics or span ranges the
756 // second time, so we cache those and then use those stored values
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700757
758 int breakCount = nComputeLineBreaks(
759 nativePtr,
760
761 // Inputs
762 chs,
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800763 measuredPara.getNativePtr(),
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700764 paraEnd - paraStart,
765 firstWidth,
766 firstWidthLineCount,
767 restWidth,
768 variableTabStops,
769 TAB_INCREMENT,
770 mLineCount,
771
772 // Outputs
773 lineBreaks,
774 lineBreaks.breaks.length,
775 lineBreaks.breaks,
776 lineBreaks.widths,
777 lineBreaks.ascents,
778 lineBreaks.descents,
779 lineBreaks.flags,
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800780 widths.getRawArray());
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700781
782 final int[] breaks = lineBreaks.breaks;
783 final float[] lineWidths = lineBreaks.widths;
784 final float[] ascents = lineBreaks.ascents;
785 final float[] descents = lineBreaks.descents;
786 final int[] flags = lineBreaks.flags;
787
788 final int remainingLineCount = mMaximumVisibleLineCount - mLineCount;
789 final boolean ellipsisMayBeApplied = ellipsize != null
790 && (ellipsize == TextUtils.TruncateAt.END
791 || (mMaximumVisibleLineCount == 1
792 && ellipsize != TextUtils.TruncateAt.MARQUEE));
793 if (0 < remainingLineCount && remainingLineCount < breakCount
794 && ellipsisMayBeApplied) {
795 // Calculate width and flag.
796 float width = 0;
797 int flag = 0; // XXX May need to also have starting hyphen edit
798 for (int i = remainingLineCount - 1; i < breakCount; i++) {
799 if (i == breakCount - 1) {
800 width += lineWidths[i];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800801 } else {
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700802 for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) {
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800803 width += widths.get(j);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700804 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800805 }
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700806 flag |= flags[i] & TAB_MASK;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800807 }
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700808 // Treat the last line and overflowed lines as a single line.
809 breaks[remainingLineCount - 1] = breaks[breakCount - 1];
810 lineWidths[remainingLineCount - 1] = width;
811 flags[remainingLineCount - 1] = flag;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800812
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700813 breakCount = remainingLineCount;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700814 }
815
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700816 // here is the offset of the starting character of the line we are currently
817 // measuring
818 int here = paraStart;
Doug Felte8e45f22010-03-29 14:58:40 -0700819
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700820 int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
821 int fmCacheIndex = 0;
822 int spanEndCacheIndex = 0;
823 int breakIndex = 0;
824 for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
825 // retrieve end of span
826 spanEnd = spanEndCache[spanEndCacheIndex++];
Doug Felt23241882010-06-02 14:41:06 -0700827
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700828 // retrieve cached metrics, order matches above
829 fm.top = fmCache[fmCacheIndex * 4 + 0];
830 fm.bottom = fmCache[fmCacheIndex * 4 + 1];
831 fm.ascent = fmCache[fmCacheIndex * 4 + 2];
832 fm.descent = fmCache[fmCacheIndex * 4 + 3];
833 fmCacheIndex++;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800834
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700835 if (fm.top < fmTop) {
Anish Athalyec8f9e622014-07-21 15:26:34 -0700836 fmTop = fm.top;
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700837 }
838 if (fm.ascent < fmAscent) {
Anish Athalyec8f9e622014-07-21 15:26:34 -0700839 fmAscent = fm.ascent;
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700840 }
841 if (fm.descent > fmDescent) {
Anish Athalyec8f9e622014-07-21 15:26:34 -0700842 fmDescent = fm.descent;
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700843 }
844 if (fm.bottom > fmBottom) {
845 fmBottom = fm.bottom;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700846 }
847
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700848 // skip breaks ending before current span range
849 while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
850 breakIndex++;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700851 }
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700852
853 while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
854 int endPos = paraStart + breaks[breakIndex];
855
856 boolean moreChars = (endPos < bufEnd);
857
858 final int ascent = fallbackLineSpacing
859 ? Math.min(fmAscent, Math.round(ascents[breakIndex]))
860 : fmAscent;
861 final int descent = fallbackLineSpacing
862 ? Math.max(fmDescent, Math.round(descents[breakIndex]))
863 : fmDescent;
864 v = out(source, here, endPos,
865 ascent, descent, fmTop, fmBottom,
866 v, spacingmult, spacingadd, chooseHt, chooseHtv, fm,
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800867 flags[breakIndex], needMultiply, measuredPara, bufEnd,
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800868 includepad, trackpad, addLastLineSpacing, chs, widths.getRawArray(),
869 paraStart, ellipsize, ellipsizedWidth, lineWidths[breakIndex],
870 paint, moreChars);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700871
872 if (endPos < spanEnd) {
873 // preserve metrics for current span
874 fmTop = fm.top;
875 fmBottom = fm.bottom;
876 fmAscent = fm.ascent;
877 fmDescent = fm.descent;
878 } else {
879 fmTop = fmBottom = fmAscent = fmDescent = 0;
880 }
881
882 here = endPos;
883 breakIndex++;
884
885 if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) {
886 return;
887 }
888 }
889 }
890
891 if (paraEnd == bufEnd) {
892 break;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700893 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800894 }
895
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700896 if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE)
897 && mLineCount < mMaximumVisibleLineCount) {
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800898 final MeasuredParagraph measuredPara =
899 MeasuredParagraph.buildForBidi(source, bufEnd, bufEnd, textDir, null);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700900 paint.getFontMetricsInt(fm);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700901 v = out(source,
902 bufEnd, bufEnd, fm.ascent, fm.descent,
903 fm.top, fm.bottom,
904 v,
905 spacingmult, spacingadd, null,
906 null, fm, 0,
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800907 needMultiply, measuredPara, bufEnd,
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700908 includepad, trackpad, addLastLineSpacing, null,
909 null, bufStart, ellipsize,
910 ellipsizedWidth, 0, paint, false);
911 }
912 } finally {
913 nFinish(nativePtr);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800914 }
915 }
916
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700917 // The parameters that are not changed in the method are marked as final to make the code
918 // easier to understand.
919 private int out(final CharSequence text, final int start, final int end, int above, int below,
920 int top, int bottom, int v, final float spacingmult, final float spacingadd,
921 final LineHeightSpan[] chooseHt, final int[] chooseHtv, final Paint.FontMetricsInt fm,
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800922 final int flags, final boolean needMultiply, @NonNull final MeasuredParagraph measured,
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800923 final int bufEnd, final boolean includePad, final boolean trackPad,
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700924 final boolean addLastLineLineSpacing, final char[] chs, final float[] widths,
925 final int widthStart, final TextUtils.TruncateAt ellipsize, final float ellipsisWidth,
926 final float textWidth, final TextPaint paint, final boolean moreChars) {
Siyamed Siniraf398512017-07-25 19:08:42 -0700927 final int j = mLineCount;
928 final int off = j * mColumns;
929 final int want = off + mColumns + TOP;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800930 int[] lines = mLines;
Seigo Nonaka4b6bcee2017-12-11 11:39:51 -0800931 final int dir = measured.getParagraphDir();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800932
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800933 if (want >= lines.length) {
Siyamed Siniraf398512017-07-25 19:08:42 -0700934 final int[] grow = ArrayUtils.newUnpaddedIntArray(GrowingArrayUtils.growSize(want));
Adam Lesinski776abc22014-03-07 11:30:59 -0500935 System.arraycopy(lines, 0, grow, 0, lines.length);
936 mLines = grow;
937 lines = grow;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800938 }
939
Siyamed Siniraf398512017-07-25 19:08:42 -0700940 if (j >= mLineDirections.length) {
941 final Directions[] grow = ArrayUtils.newUnpaddedArray(Directions.class,
942 GrowingArrayUtils.growSize(j));
943 System.arraycopy(mLineDirections, 0, grow, 0, mLineDirections.length);
944 mLineDirections = grow;
945 }
946
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700947 lines[off + START] = start;
948 lines[off + TOP] = v;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800949
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700950 // Information about hyphenation, tabs, and directions are needed for determining
951 // ellipsization, so the values should be assigned before ellipsization.
Eric Fischera9f1dd02009-08-12 15:00:10 -0700952
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700953 // TODO: could move TAB to share same column as HYPHEN, simplifying this code and gaining
954 // one bit for start field
955 lines[off + TAB] |= flags & TAB_MASK;
956 lines[off + HYPHEN] = flags;
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700957 lines[off + DIR] |= dir << DIR_SHIFT;
Seigo Nonaka4b6bcee2017-12-11 11:39:51 -0800958 mLineDirections[j] = measured.getDirections(start - widthStart, end - widthStart);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800959
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700960 final boolean firstLine = (j == 0);
961 final boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);
Siyamed Sinir0745c722016-05-31 20:39:33 -0700962
963 if (ellipsize != null) {
964 // If there is only one line, then do any type of ellipsis except when it is MARQUEE
965 // if there are multiple lines, just allow END ellipsis on the last line
966 boolean forceEllipsis = moreChars && (mLineCount + 1 == mMaximumVisibleLineCount);
967
968 boolean doEllipsis =
969 (((mMaximumVisibleLineCount == 1 && moreChars) || (firstLine && !moreChars)) &&
970 ellipsize != TextUtils.TruncateAt.MARQUEE) ||
971 (!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
972 ellipsize == TextUtils.TruncateAt.END);
973 if (doEllipsis) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700974 calculateEllipsis(text, start, end, widths, widthStart,
975 ellipsisWidth - getTotalInsets(j), ellipsize, j,
976 textWidth, paint, forceEllipsis, dir);
Siyamed Sinir0745c722016-05-31 20:39:33 -0700977 }
978 }
979
Siyamed Sinirfb0b2dc2017-07-26 22:08:24 -0700980 final boolean lastLine;
981 if (mEllipsized) {
982 lastLine = true;
983 } else {
984 final boolean lastCharIsNewLine = widthStart != bufEnd && bufEnd > 0
985 && text.charAt(bufEnd - 1) == CHAR_NEW_LINE;
986 if (end == bufEnd && !lastCharIsNewLine) {
987 lastLine = true;
988 } else if (start == bufEnd && lastCharIsNewLine) {
989 lastLine = true;
990 } else {
991 lastLine = false;
992 }
993 }
Raph Leviend97b0972014-04-24 12:51:35 -0700994
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700995 if (chooseHt != null) {
996 fm.ascent = above;
997 fm.descent = below;
998 fm.top = top;
999 fm.bottom = bottom;
1000
1001 for (int i = 0; i < chooseHt.length; i++) {
1002 if (chooseHt[i] instanceof LineHeightSpan.WithDensity) {
1003 ((LineHeightSpan.WithDensity) chooseHt[i])
1004 .chooseHeight(text, start, end, chooseHtv[i], v, fm, paint);
1005
1006 } else {
1007 chooseHt[i].chooseHeight(text, start, end, chooseHtv[i], v, fm);
1008 }
1009 }
1010
1011 above = fm.ascent;
1012 below = fm.descent;
1013 top = fm.top;
1014 bottom = fm.bottom;
1015 }
1016
Raph Leviend97b0972014-04-24 12:51:35 -07001017 if (firstLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001018 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001019 mTopPadding = top - above;
1020 }
1021
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001022 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001023 above = top;
1024 }
1025 }
Raph Leviend97b0972014-04-24 12:51:35 -07001026
Raph Leviend97b0972014-04-24 12:51:35 -07001027 if (lastLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001028 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001029 mBottomPadding = bottom - below;
1030 }
1031
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001032 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001033 below = bottom;
1034 }
1035 }
1036
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001037 final int extra;
Siyamed Sinir442c1512017-07-24 12:18:27 -07001038 if (needMultiply && (addLastLineLineSpacing || !lastLine)) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001039 final double ex = (below - above) * (spacingmult - 1) + spacingadd;
Doug Felt10657582010-02-22 11:19:01 -08001040 if (ex >= 0) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001041 extra = (int)(ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -08001042 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001043 extra = -(int)(-ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -08001044 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001045 } else {
1046 extra = 0;
1047 }
1048
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001049 lines[off + DESCENT] = below + extra;
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001050 lines[off + EXTRA] = extra;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001051
Siyamed Sinir0745c722016-05-31 20:39:33 -07001052 // special case for non-ellipsized last visible line when maxLines is set
1053 // store the height as if it was ellipsized
1054 if (!mEllipsized && currentLineIsTheLastVisibleOne) {
1055 // below calculation as if it was the last line
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001056 final int maxLineBelow = includePad ? bottom : below;
Siyamed Sinir0745c722016-05-31 20:39:33 -07001057 // similar to the calculation of v below, without the extra.
1058 mMaxLineHeight = v + (maxLineBelow - above);
1059 }
1060
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001061 v += (below - above) + extra;
1062 lines[off + mColumns + START] = end;
1063 lines[off + mColumns + TOP] = v;
1064
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001065 mLineCount++;
1066 return v;
1067 }
1068
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001069 private void calculateEllipsis(CharSequence text, int lineStart, int lineEnd, float[] widths,
1070 int widthStart, float avail, TextUtils.TruncateAt where, int line, float textWidth,
1071 TextPaint paint, boolean forceEllipsis, int dir) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001072 if (textWidth <= avail && !forceEllipsis) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001073 // Everything fits!
1074 mLines[mColumns * line + ELLIPSIS_START] = 0;
1075 mLines[mColumns * line + ELLIPSIS_COUNT] = 0;
1076 return;
1077 }
1078
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001079 float tempAvail = avail;
1080 int numberOfTries = 0;
1081 boolean lineFits = false;
1082 mWorkPaint.set(paint);
1083 do {
1084 final float ellipsizedWidth = guessEllipsis(text, lineStart, lineEnd, widths,
Siyamed Sinira273a702017-10-05 11:22:12 -07001085 widthStart, tempAvail, where, line, mWorkPaint, forceEllipsis, dir);
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001086 if (ellipsizedWidth <= avail) {
1087 lineFits = true;
1088 } else {
1089 numberOfTries++;
1090 if (numberOfTries > 10) {
1091 // If the text still doesn't fit after ten tries, assume it will never fit and
1092 // ellipsize it all.
1093 mLines[mColumns * line + ELLIPSIS_START] = 0;
1094 mLines[mColumns * line + ELLIPSIS_COUNT] = lineEnd - lineStart;
1095 lineFits = true;
1096 } else {
1097 // Some side effect of ellipsization has caused the text to go over the
1098 // available width. Let's make the available width shorter by exactly that
1099 // amount and retry.
1100 tempAvail -= ellipsizedWidth - avail;
1101 }
1102 }
1103 } while (!lineFits);
1104 mEllipsized = true;
1105 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001106
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001107 // Returns the width of the ellipsized line which in some rare cases can actually be larger
1108 // than 'avail' (due to kerning or other context-based effect of replacement of text by
1109 // ellipsis). If all the line needs to ellipsized away, or it's an invalud hyphenation mode,
1110 // returns 0 so the caller can stop iterating.
1111 //
1112 // This method temporarily modifies the TextPaint passed to it, so the TextPaint passed to it
1113 // should not be accessed while the method is running.
1114 private float guessEllipsis(CharSequence text, int lineStart, int lineEnd, float[] widths,
Siyamed Sinira273a702017-10-05 11:22:12 -07001115 int widthStart, float avail, TextUtils.TruncateAt where, int line,
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001116 TextPaint paint, boolean forceEllipsis, int dir) {
1117 final int savedHyphenEdit = paint.getHyphenEdit();
1118 paint.setHyphenEdit(0);
1119 final float ellipsisWidth = paint.measureText(TextUtils.getEllipsisString(where));
1120 final int ellipsisStart;
1121 final int ellipsisCount;
1122 final int len = lineEnd - lineStart;
1123 final int offset = lineStart - widthStart;
1124
1125 int hyphen = getHyphen(line);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001126 // We only support start ellipsis on a single line
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001127 if (where == TextUtils.TruncateAt.START) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001128 if (mMaximumVisibleLineCount == 1) {
1129 float sum = 0;
1130 int i;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001131
Keisuke Kuroyanagied2eea12015-04-14 18:18:35 +09001132 for (i = len; i > 0; i--) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001133 final float w = widths[i - 1 + offset];
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001134 if (w + sum + ellipsisWidth > avail) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001135 while (i < len && widths[i + offset] == 0.0f) {
Keisuke Kuroyanagi82d1c442016-09-14 14:30:14 +09001136 i++;
1137 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001138 break;
1139 }
1140
1141 sum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001142 }
1143
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001144 ellipsisStart = 0;
1145 ellipsisCount = i;
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001146 // Strip the potential hyphenation at beginning of line.
1147 hyphen &= ~Paint.HYPHENEDIT_MASK_START_OF_LINE;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001148 } else {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001149 ellipsisStart = 0;
1150 ellipsisCount = 0;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001151 if (Log.isLoggable(TAG, Log.WARN)) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001152 Log.w(TAG, "Start ellipsis only supported with one line");
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001153 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001154 }
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001155 } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE ||
1156 where == TextUtils.TruncateAt.END_SMALL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001157 float sum = 0;
1158 int i;
1159
1160 for (i = 0; i < len; i++) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001161 final float w = widths[i + offset];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001162
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001163 if (w + sum + ellipsisWidth > avail) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001164 break;
1165 }
1166
1167 sum += w;
1168 }
1169
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001170 if (forceEllipsis && i == len && len > 0) {
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -07001171 ellipsisStart = len - 1;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001172 ellipsisCount = 1;
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001173 } else {
1174 ellipsisStart = i;
1175 ellipsisCount = len - i;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001176 }
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001177 // Strip the potential hyphenation at end of line.
1178 hyphen &= ~Paint.HYPHENEDIT_MASK_END_OF_LINE;
1179 } else { // where = TextUtils.TruncateAt.MIDDLE
1180 // We only support middle ellipsis on a single line.
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001181 if (mMaximumVisibleLineCount == 1) {
1182 float lsum = 0, rsum = 0;
1183 int left = 0, right = len;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001184
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001185 final float ravail = (avail - ellipsisWidth) / 2;
Raph Levien0e3c5e82014-12-04 13:26:07 -08001186 for (right = len; right > 0; right--) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001187 final float w = widths[right - 1 + offset];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001188
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001189 if (w + rsum > ravail) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001190 while (right < len && widths[right + offset] == 0.0f) {
Keisuke Kuroyanagi82d1c442016-09-14 14:30:14 +09001191 right++;
1192 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001193 break;
1194 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001195 rsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001196 }
1197
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001198 final float lavail = avail - ellipsisWidth - rsum;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001199 for (left = 0; left < right; left++) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001200 final float w = widths[left + offset];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001201
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001202 if (w + lsum > lavail) {
1203 break;
1204 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001205
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001206 lsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001207 }
1208
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001209 ellipsisStart = left;
1210 ellipsisCount = right - left;
1211 } else {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001212 ellipsisStart = 0;
1213 ellipsisCount = 0;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001214 if (Log.isLoggable(TAG, Log.WARN)) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001215 Log.w(TAG, "Middle ellipsis only supported with one line");
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001216 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001217 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001218 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001219 mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
1220 mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001221
1222 if (ellipsisStart == 0 && (ellipsisCount == 0 || ellipsisCount == len)) {
1223 // Unsupported ellipsization mode or all text is ellipsized away. Return 0.
1224 return 0.0f;
1225 }
1226
1227 final boolean isSpanned = text instanceof Spanned;
1228 final Ellipsizer ellipsizedText = isSpanned
1229 ? new SpannedEllipsizer(text)
1230 : new Ellipsizer(text);
1231 ellipsizedText.mLayout = this;
1232 ellipsizedText.mMethod = where;
1233
1234 final boolean hasTabs = getLineContainsTab(line);
1235 final TabStops tabStops;
1236 if (hasTabs && isSpanned) {
1237 final TabStopSpan[] tabs = getParagraphSpans((Spanned) ellipsizedText, lineStart,
1238 lineEnd, TabStopSpan.class);
1239 if (tabs.length == 0) {
1240 tabStops = null;
1241 } else {
1242 tabStops = new TabStops(TAB_INCREMENT, tabs);
1243 }
1244 } else {
1245 tabStops = null;
1246 }
1247 paint.setHyphenEdit(hyphen);
1248 final TextLine textline = TextLine.obtain();
1249 textline.set(paint, ellipsizedText, lineStart, lineEnd, dir, getLineDirections(line),
1250 hasTabs, tabStops);
1251 // Since TextLine.metric() returns negative values for RTL text, multiplication by dir
1252 // converts it to an actual width. Note that we don't want to use the absolute value,
1253 // since we may actually have glyphs with negative advances, which by definition always
1254 // fit.
1255 final float ellipsizedWidth = textline.metrics(null) * dir;
1256 TextLine.recycle(textline);
1257 paint.setHyphenEdit(savedHyphenEdit);
1258 return ellipsizedWidth;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001259 }
1260
Selim Cinek365ec092017-03-09 00:10:52 -08001261 private float getTotalInsets(int line) {
1262 int totalIndent = 0;
1263 if (mLeftIndents != null) {
1264 totalIndent = mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1265 }
1266 if (mRightIndents != null) {
1267 totalIndent += mRightIndents[Math.min(line, mRightIndents.length - 1)];
1268 }
1269 return totalIndent;
1270 }
1271
Doug Felte8e45f22010-03-29 14:58:40 -07001272 // Override the base class so we can directly access our members,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001273 // rather than relying on member functions.
1274 // The logic mirrors that of Layout.getLineForVertical
1275 // FIXME: It may be faster to do a linear search for layouts without many lines.
Gilles Debunne66111472010-11-19 11:04:37 -08001276 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001277 public int getLineForVertical(int vertical) {
1278 int high = mLineCount;
1279 int low = -1;
1280 int guess;
1281 int[] lines = mLines;
1282 while (high - low > 1) {
1283 guess = (high + low) >> 1;
1284 if (lines[mColumns * guess + TOP] > vertical){
1285 high = guess;
1286 } else {
1287 low = guess;
1288 }
1289 }
1290 if (low < 0) {
1291 return 0;
1292 } else {
1293 return low;
1294 }
1295 }
1296
Gilles Debunne66111472010-11-19 11:04:37 -08001297 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001298 public int getLineCount() {
1299 return mLineCount;
1300 }
1301
Gilles Debunne66111472010-11-19 11:04:37 -08001302 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001303 public int getLineTop(int line) {
Raph Levien07e6c232016-04-04 12:34:06 -07001304 return mLines[mColumns * line + TOP];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001305 }
1306
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001307 /**
1308 * @hide
1309 */
1310 @Override
1311 public int getLineExtra(int line) {
1312 return mLines[mColumns * line + EXTRA];
1313 }
1314
Gilles Debunne66111472010-11-19 11:04:37 -08001315 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001316 public int getLineDescent(int line) {
Raph Levien07e6c232016-04-04 12:34:06 -07001317 return mLines[mColumns * line + DESCENT];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001318 }
1319
Gilles Debunne66111472010-11-19 11:04:37 -08001320 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001321 public int getLineStart(int line) {
1322 return mLines[mColumns * line + START] & START_MASK;
1323 }
1324
Gilles Debunne66111472010-11-19 11:04:37 -08001325 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001326 public int getParagraphDirection(int line) {
1327 return mLines[mColumns * line + DIR] >> DIR_SHIFT;
1328 }
1329
Gilles Debunne66111472010-11-19 11:04:37 -08001330 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001331 public boolean getLineContainsTab(int line) {
1332 return (mLines[mColumns * line + TAB] & TAB_MASK) != 0;
1333 }
1334
Gilles Debunne66111472010-11-19 11:04:37 -08001335 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001336 public final Directions getLineDirections(int line) {
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001337 if (line > getLineCount()) {
1338 throw new ArrayIndexOutOfBoundsException();
1339 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001340 return mLineDirections[line];
1341 }
1342
Gilles Debunne66111472010-11-19 11:04:37 -08001343 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001344 public int getTopPadding() {
1345 return mTopPadding;
1346 }
1347
Gilles Debunne66111472010-11-19 11:04:37 -08001348 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001349 public int getBottomPadding() {
1350 return mBottomPadding;
1351 }
1352
Raph Levien26d443a2015-03-30 14:18:32 -07001353 /**
1354 * @hide
1355 */
1356 @Override
1357 public int getHyphen(int line) {
Keisuke Kuroyanagif5af4a32016-08-31 21:40:53 +09001358 return mLines[mColumns * line + HYPHEN] & HYPHEN_MASK;
Raph Levien26d443a2015-03-30 14:18:32 -07001359 }
1360
Raph Levien2ea52902015-07-01 14:39:31 -07001361 /**
1362 * @hide
1363 */
1364 @Override
1365 public int getIndentAdjust(int line, Alignment align) {
1366 if (align == Alignment.ALIGN_LEFT) {
1367 if (mLeftIndents == null) {
1368 return 0;
1369 } else {
1370 return mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1371 }
1372 } else if (align == Alignment.ALIGN_RIGHT) {
1373 if (mRightIndents == null) {
1374 return 0;
1375 } else {
1376 return -mRightIndents[Math.min(line, mRightIndents.length - 1)];
1377 }
1378 } else if (align == Alignment.ALIGN_CENTER) {
1379 int left = 0;
1380 if (mLeftIndents != null) {
1381 left = mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1382 }
1383 int right = 0;
1384 if (mRightIndents != null) {
1385 right = mRightIndents[Math.min(line, mRightIndents.length - 1)];
1386 }
1387 return (left - right) >> 1;
1388 } else {
1389 throw new AssertionError("unhandled alignment " + align);
1390 }
1391 }
1392
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001393 @Override
1394 public int getEllipsisCount(int line) {
1395 if (mColumns < COLUMNS_ELLIPSIZE) {
1396 return 0;
1397 }
1398
1399 return mLines[mColumns * line + ELLIPSIS_COUNT];
1400 }
1401
1402 @Override
1403 public int getEllipsisStart(int line) {
1404 if (mColumns < COLUMNS_ELLIPSIZE) {
1405 return 0;
1406 }
1407
1408 return mLines[mColumns * line + ELLIPSIS_START];
1409 }
1410
1411 @Override
1412 public int getEllipsizedWidth() {
1413 return mEllipsizedWidth;
1414 }
1415
Siyamed Sinir0745c722016-05-31 20:39:33 -07001416 /**
1417 * Return the total height of this layout.
1418 *
1419 * @param cap if true and max lines is set, returns the height of the layout at the max lines.
1420 *
1421 * @hide
1422 */
1423 public int getHeight(boolean cap) {
1424 if (cap && mLineCount >= mMaximumVisibleLineCount && mMaxLineHeight == -1 &&
1425 Log.isLoggable(TAG, Log.WARN)) {
1426 Log.w(TAG, "maxLineHeight should not be -1. "
1427 + " maxLines:" + mMaximumVisibleLineCount
1428 + " lineCount:" + mLineCount);
1429 }
1430
1431 return cap && mLineCount >= mMaximumVisibleLineCount && mMaxLineHeight != -1 ?
1432 mMaxLineHeight : super.getHeight();
1433 }
1434
Seigo Nonakab90e08f2017-10-20 15:23:51 -07001435 @FastNative
1436 private static native long nInit(
1437 @BreakStrategy int breakStrategy,
1438 @HyphenationFrequency int hyphenationFrequency,
1439 boolean isJustified,
1440 @Nullable int[] indents,
1441 @Nullable int[] leftPaddings,
1442 @Nullable int[] rightPaddings);
1443
1444 @CriticalNative
1445 private static native void nFinish(long nativePtr);
1446
Anish Athalyec8f9e622014-07-21 15:26:34 -07001447 // populates LineBreaks and returns the number of breaks found
1448 //
1449 // the arrays inside the LineBreaks objects are passed in as well
1450 // to reduce the number of JNI calls in the common case where the
1451 // arrays do not have to be resized
Seigo Nonaka2dd1a552017-10-06 11:41:00 -07001452 // The individual character widths will be returned in charWidths. The length of charWidths must
1453 // be at least the length of the text.
Seigo Nonakab90e08f2017-10-20 15:23:51 -07001454 private static native int nComputeLineBreaks(
1455 /* non zero */ long nativePtr,
1456
1457 // Inputs
1458 @NonNull char[] text,
Seigo Nonaka6f1868b2017-12-01 16:24:19 -08001459 /* Non Zero */ long measuredTextPtr,
Seigo Nonakab90e08f2017-10-20 15:23:51 -07001460 @IntRange(from = 0) int length,
1461 @FloatRange(from = 0.0f) float firstWidth,
1462 @IntRange(from = 0) int firstWidthLineCount,
1463 @FloatRange(from = 0.0f) float restWidth,
1464 @Nullable int[] variableTabStops,
1465 int defaultTabStop,
1466 @IntRange(from = 0) int indentsOffset,
1467
1468 // Outputs
1469 @NonNull LineBreaks recycle,
1470 @IntRange(from = 0) int recycleLength,
1471 @NonNull int[] recycleBreaks,
1472 @NonNull float[] recycleWidths,
1473 @NonNull float[] recycleAscents,
1474 @NonNull float[] recycleDescents,
1475 @NonNull int[] recycleFlags,
1476 @NonNull float[] charWidths);
Anish Athalye88b5b0b2014-06-24 14:39:43 -07001477
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001478 private int mLineCount;
1479 private int mTopPadding, mBottomPadding;
1480 private int mColumns;
1481 private int mEllipsizedWidth;
1482
Siyamed Sinir0745c722016-05-31 20:39:33 -07001483 /**
1484 * Keeps track if ellipsize is applied to the text.
1485 */
1486 private boolean mEllipsized;
1487
1488 /**
1489 * If maxLines is set, ellipsize is not set, and the actual line count of text is greater than
1490 * or equal to maxLine, this variable holds the ideal visual height of the maxLine'th line
1491 * starting from the top of the layout. If maxLines is not set its value will be -1.
1492 *
1493 * The value is the same as getLineTop(maxLines) for ellipsized version where structurally no
1494 * more than maxLines is contained.
1495 */
Siyamed Sinira19cd512017-08-03 22:01:56 -07001496 private int mMaxLineHeight = DEFAULT_MAX_LINE_HEIGHT;
Siyamed Sinir0745c722016-05-31 20:39:33 -07001497
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001498 private TextPaint mWorkPaint = new TextPaint();
1499
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001500 private static final int COLUMNS_NORMAL = 5;
1501 private static final int COLUMNS_ELLIPSIZE = 7;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001502 private static final int START = 0;
1503 private static final int DIR = START;
1504 private static final int TAB = START;
1505 private static final int TOP = 1;
1506 private static final int DESCENT = 2;
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001507 private static final int EXTRA = 3;
1508 private static final int HYPHEN = 4;
1509 private static final int ELLIPSIS_START = 5;
1510 private static final int ELLIPSIS_COUNT = 6;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001511
1512 private int[] mLines;
1513 private Directions[] mLineDirections;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001514 private int mMaximumVisibleLineCount = Integer.MAX_VALUE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001515
1516 private static final int START_MASK = 0x1FFFFFFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001517 private static final int DIR_SHIFT = 30;
1518 private static final int TAB_MASK = 0x20000000;
Keisuke Kuroyanagif5af4a32016-08-31 21:40:53 +09001519 private static final int HYPHEN_MASK = 0xFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001520
Doug Feltc982f602010-05-25 11:51:40 -07001521 private static final int TAB_INCREMENT = 20; // same as Layout, but that's private
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001522
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001523 private static final char CHAR_NEW_LINE = '\n';
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001524
1525 private static final double EXTRA_ROUNDING = 0.5;
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001526
Siyamed Sinira19cd512017-08-03 22:01:56 -07001527 private static final int DEFAULT_MAX_LINE_HEIGHT = -1;
1528
Anish Athalyec8f9e622014-07-21 15:26:34 -07001529 // This is used to return three arrays from a single JNI call when
1530 // performing line breaking
Deepanshu Gupta70539192014-10-15 15:57:40 -07001531 /*package*/ static class LineBreaks {
Anish Athalyec8f9e622014-07-21 15:26:34 -07001532 private static final int INITIAL_SIZE = 16;
1533 public int[] breaks = new int[INITIAL_SIZE];
1534 public float[] widths = new float[INITIAL_SIZE];
Roozbeh Pournader737dfea2017-08-10 11:32:24 -07001535 public float[] ascents = new float[INITIAL_SIZE];
1536 public float[] descents = new float[INITIAL_SIZE];
Roozbeh Pournader112d9c72015-08-07 12:44:41 -07001537 public int[] flags = new int[INITIAL_SIZE]; // hasTab
Anish Athalyec8f9e622014-07-21 15:26:34 -07001538 // breaks, widths, and flags should all have the same length
1539 }
1540
Seigo Nonakabafe1972017-08-24 15:30:29 -07001541 @Nullable private int[] mLeftIndents;
1542 @Nullable private int[] mRightIndents;
Roozbeh Pournader9a9179d2017-08-29 14:14:28 -07001543 @Nullable private int[] mLeftPaddings;
1544 @Nullable private int[] mRightPaddings;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001545}