blob: 0899074174a2c535ab6b973a616dffb545080651 [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) {
Seigo Nonaka4e90fa22018-02-13 21:40:01 +0000493 this(source, bufstart, bufend, paint, outerwidth, align,
Doug Feltcb3791202011-07-07 11:57:48 -0700494 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
Seigo Nonaka4e90fa22018-02-13 21:40:01 +0000503 public StaticLayout(CharSequence source, int bufstart, int bufend,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800504 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 Nonakac3328d62018-03-20 15:18:59 -0700654 PrecomputedText.ParagraphInfo[] paragraphInfo = null;
655 final Spanned spanned = (source instanceof Spanned) ? (Spanned) source : null;
Seigo Nonakabeafa1f2018-02-01 21:39:24 -0800656 if (source instanceof PrecomputedText) {
Seigo Nonakac3328d62018-03-20 15:18:59 -0700657 PrecomputedText precomputed = (PrecomputedText) source;
658 if (precomputed.canUseMeasuredResult(bufStart, bufEnd, textDir, paint,
659 b.mBreakStrategy, b.mHyphenationFrequency)) {
Seigo Nonakabeafa1f2018-02-01 21:39:24 -0800660 // Some parameters are different from the ones when measured text is created.
Seigo Nonakac3328d62018-03-20 15:18:59 -0700661 paragraphInfo = precomputed.getParagraphInfo();
Seigo Nonakafbe63bd2017-12-02 19:28:05 -0800662 }
Seigo Nonaka87b15472018-01-12 14:06:29 -0800663 }
664
Seigo Nonakac3328d62018-03-20 15:18:59 -0700665 if (paragraphInfo == null) {
Seigo Nonakabeafa1f2018-02-01 21:39:24 -0800666 final PrecomputedText.Params param = new PrecomputedText.Params(paint, textDir,
667 b.mBreakStrategy, b.mHyphenationFrequency);
Seigo Nonakac3328d62018-03-20 15:18:59 -0700668 paragraphInfo = PrecomputedText.createMeasuredParagraphs(source, param, bufStart,
669 bufEnd, false /* computeLayout */);
Seigo Nonaka4e90fa22018-02-13 21:40:01 +0000670 }
Seigo Nonakafbe63bd2017-12-02 19:28:05 -0800671
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700672 try {
Seigo Nonakac3328d62018-03-20 15:18:59 -0700673 for (int paraIndex = 0; paraIndex < paragraphInfo.length; paraIndex++) {
674 final int paraStart = paraIndex == 0
675 ? bufStart : paragraphInfo[paraIndex - 1].paragraphEnd;
676 final int paraEnd = paragraphInfo[paraIndex].paragraphEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800677
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700678 int firstWidthLineCount = 1;
679 int firstWidth = outerWidth;
680 int restWidth = outerWidth;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800681
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700682 LineHeightSpan[] chooseHt = null;
Doug Feltcb3791202011-07-07 11:57:48 -0700683
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700684 if (spanned != null) {
685 LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
686 LeadingMarginSpan.class);
687 for (int i = 0; i < sp.length; i++) {
688 LeadingMarginSpan lms = sp[i];
689 firstWidth -= sp[i].getLeadingMargin(true);
690 restWidth -= sp[i].getLeadingMargin(false);
691
692 // LeadingMarginSpan2 is odd. The count affects all
693 // leading margin spans, not just this particular one
694 if (lms instanceof LeadingMarginSpan2) {
695 LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
696 firstWidthLineCount = Math.max(firstWidthLineCount,
697 lms2.getLeadingMarginLineCount());
698 }
699 }
700
701 chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
702
703 if (chooseHt.length == 0) {
704 chooseHt = null; // So that out() would not assume it has any contents
705 } else {
706 if (chooseHtv == null || chooseHtv.length < chooseHt.length) {
707 chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
708 }
709
710 for (int i = 0; i < chooseHt.length; i++) {
711 int o = spanned.getSpanStart(chooseHt[i]);
712
713 if (o < paraStart) {
714 // starts in this layout, before the
715 // current paragraph
716
717 chooseHtv[i] = getLineTop(getLineForOffset(o));
718 } else {
719 // starts in this paragraph
720
721 chooseHtv[i] = v;
722 }
723 }
Mark Wagner7b5676e2009-10-16 11:44:23 -0700724 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800725 }
726
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700727 // tab stop locations
728 int[] variableTabStops = null;
729 if (spanned != null) {
730 TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
731 paraEnd, TabStopSpan.class);
732 if (spans.length > 0) {
733 int[] stops = new int[spans.length];
734 for (int i = 0; i < spans.length; i++) {
735 stops[i] = spans[i].getTabStop();
736 }
737 Arrays.sort(stops, 0, stops.length);
738 variableTabStops = stops;
739 }
740 }
741
Seigo Nonakac3328d62018-03-20 15:18:59 -0700742 final MeasuredParagraph measuredPara = paragraphInfo[paraIndex].measured;
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800743 final char[] chs = measuredPara.getChars();
744 final int[] spanEndCache = measuredPara.getSpanEndCache().getRawArray();
745 final int[] fmCache = measuredPara.getFontMetrics().getRawArray();
Seigo Nonaka6f1868b2017-12-01 16:24:19 -0800746 // TODO: Stop keeping duplicated width copy in native and Java.
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800747 widths.resize(chs.length);
748
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700749 // measurement has to be done before performing line breaking
750 // but we don't want to recompute fontmetrics or span ranges the
751 // second time, so we cache those and then use those stored values
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700752
753 int breakCount = nComputeLineBreaks(
754 nativePtr,
755
756 // Inputs
757 chs,
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800758 measuredPara.getNativePtr(),
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700759 paraEnd - paraStart,
760 firstWidth,
761 firstWidthLineCount,
762 restWidth,
763 variableTabStops,
764 TAB_INCREMENT,
765 mLineCount,
766
767 // Outputs
768 lineBreaks,
769 lineBreaks.breaks.length,
770 lineBreaks.breaks,
771 lineBreaks.widths,
772 lineBreaks.ascents,
773 lineBreaks.descents,
774 lineBreaks.flags,
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800775 widths.getRawArray());
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700776
777 final int[] breaks = lineBreaks.breaks;
778 final float[] lineWidths = lineBreaks.widths;
779 final float[] ascents = lineBreaks.ascents;
780 final float[] descents = lineBreaks.descents;
781 final int[] flags = lineBreaks.flags;
782
783 final int remainingLineCount = mMaximumVisibleLineCount - mLineCount;
784 final boolean ellipsisMayBeApplied = ellipsize != null
785 && (ellipsize == TextUtils.TruncateAt.END
786 || (mMaximumVisibleLineCount == 1
787 && ellipsize != TextUtils.TruncateAt.MARQUEE));
788 if (0 < remainingLineCount && remainingLineCount < breakCount
789 && ellipsisMayBeApplied) {
790 // Calculate width and flag.
791 float width = 0;
792 int flag = 0; // XXX May need to also have starting hyphen edit
793 for (int i = remainingLineCount - 1; i < breakCount; i++) {
794 if (i == breakCount - 1) {
795 width += lineWidths[i];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800796 } else {
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700797 for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) {
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800798 width += widths.get(j);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700799 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800800 }
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700801 flag |= flags[i] & TAB_MASK;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800802 }
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700803 // Treat the last line and overflowed lines as a single line.
804 breaks[remainingLineCount - 1] = breaks[breakCount - 1];
805 lineWidths[remainingLineCount - 1] = width;
806 flags[remainingLineCount - 1] = flag;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800807
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700808 breakCount = remainingLineCount;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700809 }
810
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700811 // here is the offset of the starting character of the line we are currently
812 // measuring
813 int here = paraStart;
Doug Felte8e45f22010-03-29 14:58:40 -0700814
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700815 int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
816 int fmCacheIndex = 0;
817 int spanEndCacheIndex = 0;
818 int breakIndex = 0;
819 for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
820 // retrieve end of span
821 spanEnd = spanEndCache[spanEndCacheIndex++];
Doug Felt23241882010-06-02 14:41:06 -0700822
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700823 // retrieve cached metrics, order matches above
824 fm.top = fmCache[fmCacheIndex * 4 + 0];
825 fm.bottom = fmCache[fmCacheIndex * 4 + 1];
826 fm.ascent = fmCache[fmCacheIndex * 4 + 2];
827 fm.descent = fmCache[fmCacheIndex * 4 + 3];
828 fmCacheIndex++;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800829
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700830 if (fm.top < fmTop) {
Anish Athalyec8f9e622014-07-21 15:26:34 -0700831 fmTop = fm.top;
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700832 }
833 if (fm.ascent < fmAscent) {
Anish Athalyec8f9e622014-07-21 15:26:34 -0700834 fmAscent = fm.ascent;
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700835 }
836 if (fm.descent > fmDescent) {
Anish Athalyec8f9e622014-07-21 15:26:34 -0700837 fmDescent = fm.descent;
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700838 }
839 if (fm.bottom > fmBottom) {
840 fmBottom = fm.bottom;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700841 }
842
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700843 // skip breaks ending before current span range
844 while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
845 breakIndex++;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700846 }
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700847
848 while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
849 int endPos = paraStart + breaks[breakIndex];
850
851 boolean moreChars = (endPos < bufEnd);
852
853 final int ascent = fallbackLineSpacing
854 ? Math.min(fmAscent, Math.round(ascents[breakIndex]))
855 : fmAscent;
856 final int descent = fallbackLineSpacing
857 ? Math.max(fmDescent, Math.round(descents[breakIndex]))
858 : fmDescent;
859 v = out(source, here, endPos,
860 ascent, descent, fmTop, fmBottom,
861 v, spacingmult, spacingadd, chooseHt, chooseHtv, fm,
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800862 flags[breakIndex], needMultiply, measuredPara, bufEnd,
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800863 includepad, trackpad, addLastLineSpacing, chs, widths.getRawArray(),
864 paraStart, ellipsize, ellipsizedWidth, lineWidths[breakIndex],
865 paint, moreChars);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700866
867 if (endPos < spanEnd) {
868 // preserve metrics for current span
869 fmTop = fm.top;
870 fmBottom = fm.bottom;
871 fmAscent = fm.ascent;
872 fmDescent = fm.descent;
873 } else {
874 fmTop = fmBottom = fmAscent = fmDescent = 0;
875 }
876
877 here = endPos;
878 breakIndex++;
879
880 if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) {
881 return;
882 }
883 }
884 }
885
886 if (paraEnd == bufEnd) {
887 break;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700888 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800889 }
890
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700891 if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE)
892 && mLineCount < mMaximumVisibleLineCount) {
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800893 final MeasuredParagraph measuredPara =
894 MeasuredParagraph.buildForBidi(source, bufEnd, bufEnd, textDir, null);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700895 paint.getFontMetricsInt(fm);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700896 v = out(source,
897 bufEnd, bufEnd, fm.ascent, fm.descent,
898 fm.top, fm.bottom,
899 v,
900 spacingmult, spacingadd, null,
901 null, fm, 0,
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800902 needMultiply, measuredPara, bufEnd,
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700903 includepad, trackpad, addLastLineSpacing, null,
904 null, bufStart, ellipsize,
905 ellipsizedWidth, 0, paint, false);
906 }
907 } finally {
908 nFinish(nativePtr);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800909 }
910 }
911
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700912 private int out(final CharSequence text, final int start, final int end, int above, int below,
913 int top, int bottom, int v, final float spacingmult, final float spacingadd,
914 final LineHeightSpan[] chooseHt, final int[] chooseHtv, final Paint.FontMetricsInt fm,
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800915 final int flags, final boolean needMultiply, @NonNull final MeasuredParagraph measured,
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800916 final int bufEnd, final boolean includePad, final boolean trackPad,
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700917 final boolean addLastLineLineSpacing, final char[] chs, final float[] widths,
918 final int widthStart, final TextUtils.TruncateAt ellipsize, final float ellipsisWidth,
919 final float textWidth, final TextPaint paint, final boolean moreChars) {
Siyamed Siniraf398512017-07-25 19:08:42 -0700920 final int j = mLineCount;
921 final int off = j * mColumns;
922 final int want = off + mColumns + TOP;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800923 int[] lines = mLines;
Seigo Nonaka4b6bcee2017-12-11 11:39:51 -0800924 final int dir = measured.getParagraphDir();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800925
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800926 if (want >= lines.length) {
Siyamed Siniraf398512017-07-25 19:08:42 -0700927 final int[] grow = ArrayUtils.newUnpaddedIntArray(GrowingArrayUtils.growSize(want));
Adam Lesinski776abc22014-03-07 11:30:59 -0500928 System.arraycopy(lines, 0, grow, 0, lines.length);
929 mLines = grow;
930 lines = grow;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800931 }
932
Siyamed Siniraf398512017-07-25 19:08:42 -0700933 if (j >= mLineDirections.length) {
934 final Directions[] grow = ArrayUtils.newUnpaddedArray(Directions.class,
935 GrowingArrayUtils.growSize(j));
936 System.arraycopy(mLineDirections, 0, grow, 0, mLineDirections.length);
937 mLineDirections = grow;
938 }
939
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800940 if (chooseHt != null) {
941 fm.ascent = above;
942 fm.descent = below;
943 fm.top = top;
944 fm.bottom = bottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800945
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800946 for (int i = 0; i < chooseHt.length; i++) {
947 if (chooseHt[i] instanceof LineHeightSpan.WithDensity) {
948 ((LineHeightSpan.WithDensity) chooseHt[i])
949 .chooseHeight(text, start, end, chooseHtv[i], v, fm, paint);
950 } else {
951 chooseHt[i].chooseHeight(text, start, end, chooseHtv[i], v, fm);
952 }
953 }
Eric Fischera9f1dd02009-08-12 15:00:10 -0700954
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800955 above = fm.ascent;
956 below = fm.descent;
957 top = fm.top;
958 bottom = fm.bottom;
959 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800960
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800961 boolean firstLine = (j == 0);
962 boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);
Siyamed Sinir0745c722016-05-31 20:39:33 -0700963
964 if (ellipsize != null) {
965 // If there is only one line, then do any type of ellipsis except when it is MARQUEE
966 // if there are multiple lines, just allow END ellipsis on the last line
967 boolean forceEllipsis = moreChars && (mLineCount + 1 == mMaximumVisibleLineCount);
968
969 boolean doEllipsis =
970 (((mMaximumVisibleLineCount == 1 && moreChars) || (firstLine && !moreChars)) &&
971 ellipsize != TextUtils.TruncateAt.MARQUEE) ||
972 (!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
973 ellipsize == TextUtils.TruncateAt.END);
974 if (doEllipsis) {
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800975 calculateEllipsis(start, end, widths, widthStart,
976 ellipsisWidth, ellipsize, j,
977 textWidth, paint, forceEllipsis);
Siyamed Sinir0745c722016-05-31 20:39:33 -0700978 }
979 }
980
Siyamed Sinirfb0b2dc2017-07-26 22:08:24 -0700981 final boolean lastLine;
982 if (mEllipsized) {
983 lastLine = true;
984 } else {
985 final boolean lastCharIsNewLine = widthStart != bufEnd && bufEnd > 0
986 && text.charAt(bufEnd - 1) == CHAR_NEW_LINE;
987 if (end == bufEnd && !lastCharIsNewLine) {
988 lastLine = true;
989 } else if (start == bufEnd && lastCharIsNewLine) {
990 lastLine = true;
991 } else {
992 lastLine = false;
993 }
994 }
Raph Leviend97b0972014-04-24 12:51:35 -0700995
996 if (firstLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800997 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800998 mTopPadding = top - above;
999 }
1000
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001001 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001002 above = top;
1003 }
1004 }
Raph Leviend97b0972014-04-24 12:51:35 -07001005
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001006 int extra;
1007
Raph Leviend97b0972014-04-24 12:51:35 -07001008 if (lastLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001009 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001010 mBottomPadding = bottom - below;
1011 }
1012
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001013 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001014 below = bottom;
1015 }
1016 }
1017
Siyamed Sinir442c1512017-07-24 12:18:27 -07001018 if (needMultiply && (addLastLineLineSpacing || !lastLine)) {
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001019 double ex = (below - above) * (spacingmult - 1) + spacingadd;
Doug Felt10657582010-02-22 11:19:01 -08001020 if (ex >= 0) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001021 extra = (int)(ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -08001022 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001023 extra = -(int)(-ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -08001024 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001025 } else {
1026 extra = 0;
1027 }
1028
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001029 lines[off + START] = start;
1030 lines[off + TOP] = v;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001031 lines[off + DESCENT] = below + extra;
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001032 lines[off + EXTRA] = extra;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001033
Siyamed Sinir0745c722016-05-31 20:39:33 -07001034 // special case for non-ellipsized last visible line when maxLines is set
1035 // store the height as if it was ellipsized
1036 if (!mEllipsized && currentLineIsTheLastVisibleOne) {
1037 // below calculation as if it was the last line
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001038 int maxLineBelow = includePad ? bottom : below;
Siyamed Sinir0745c722016-05-31 20:39:33 -07001039 // similar to the calculation of v below, without the extra.
1040 mMaxLineHeight = v + (maxLineBelow - above);
1041 }
1042
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001043 v += (below - above) + extra;
1044 lines[off + mColumns + START] = end;
1045 lines[off + mColumns + TOP] = v;
1046
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001047 // TODO: could move TAB to share same column as HYPHEN, simplifying this code and gaining
1048 // one bit for start field
1049 lines[off + TAB] |= flags & TAB_MASK;
1050 lines[off + HYPHEN] = flags;
1051 lines[off + DIR] |= dir << DIR_SHIFT;
1052 mLineDirections[j] = measured.getDirections(start - widthStart, end - widthStart);
1053
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001054 mLineCount++;
1055 return v;
1056 }
1057
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001058 private void calculateEllipsis(int lineStart, int lineEnd,
1059 float[] widths, int widthStart,
1060 float avail, TextUtils.TruncateAt where,
1061 int line, float textWidth, TextPaint paint,
1062 boolean forceEllipsis) {
1063 avail -= getTotalInsets(line);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001064 if (textWidth <= avail && !forceEllipsis) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001065 // Everything fits!
1066 mLines[mColumns * line + ELLIPSIS_START] = 0;
1067 mLines[mColumns * line + ELLIPSIS_COUNT] = 0;
1068 return;
1069 }
1070
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001071 float ellipsisWidth = paint.measureText(TextUtils.getEllipsisString(where));
1072 int ellipsisStart = 0;
1073 int ellipsisCount = 0;
1074 int len = lineEnd - lineStart;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001075
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001076 // We only support start ellipsis on a single line
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001077 if (where == TextUtils.TruncateAt.START) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001078 if (mMaximumVisibleLineCount == 1) {
1079 float sum = 0;
1080 int i;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001081
Keisuke Kuroyanagied2eea12015-04-14 18:18:35 +09001082 for (i = len; i > 0; i--) {
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001083 float w = widths[i - 1 + lineStart - widthStart];
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001084 if (w + sum + ellipsisWidth > avail) {
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001085 while (i < len && widths[i + lineStart - widthStart] == 0.0f) {
Keisuke Kuroyanagi82d1c442016-09-14 14:30:14 +09001086 i++;
1087 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001088 break;
1089 }
1090
1091 sum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001092 }
1093
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001094 ellipsisStart = 0;
1095 ellipsisCount = i;
1096 } else {
1097 if (Log.isLoggable(TAG, Log.WARN)) {
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001098 Log.w(TAG, "Start Ellipsis only supported with one line");
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001099 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001100 }
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001101 } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE ||
1102 where == TextUtils.TruncateAt.END_SMALL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001103 float sum = 0;
1104 int i;
1105
1106 for (i = 0; i < len; i++) {
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001107 float w = widths[i + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001108
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001109 if (w + sum + ellipsisWidth > avail) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001110 break;
1111 }
1112
1113 sum += w;
1114 }
1115
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001116 ellipsisStart = i;
1117 ellipsisCount = len - i;
1118 if (forceEllipsis && ellipsisCount == 0 && len > 0) {
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -07001119 ellipsisStart = len - 1;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001120 ellipsisCount = 1;
1121 }
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001122 } else {
1123 // where = TextUtils.TruncateAt.MIDDLE We only support middle ellipsis on a single line
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001124 if (mMaximumVisibleLineCount == 1) {
1125 float lsum = 0, rsum = 0;
1126 int left = 0, right = len;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001127
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001128 float ravail = (avail - ellipsisWidth) / 2;
Raph Levien0e3c5e82014-12-04 13:26:07 -08001129 for (right = len; right > 0; right--) {
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001130 float w = widths[right - 1 + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001131
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001132 if (w + rsum > ravail) {
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001133 while (right < len && widths[right + lineStart - widthStart] == 0.0f) {
Keisuke Kuroyanagi82d1c442016-09-14 14:30:14 +09001134 right++;
1135 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001136 break;
1137 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001138 rsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001139 }
1140
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001141 float lavail = avail - ellipsisWidth - rsum;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001142 for (left = 0; left < right; left++) {
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001143 float w = widths[left + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001144
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001145 if (w + lsum > lavail) {
1146 break;
1147 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001148
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001149 lsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001150 }
1151
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001152 ellipsisStart = left;
1153 ellipsisCount = right - left;
1154 } else {
1155 if (Log.isLoggable(TAG, Log.WARN)) {
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001156 Log.w(TAG, "Middle Ellipsis only supported with one line");
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001157 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001158 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001159 }
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001160 mEllipsized = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001161 mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
1162 mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
1163 }
1164
Selim Cinek365ec092017-03-09 00:10:52 -08001165 private float getTotalInsets(int line) {
1166 int totalIndent = 0;
1167 if (mLeftIndents != null) {
1168 totalIndent = mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1169 }
1170 if (mRightIndents != null) {
1171 totalIndent += mRightIndents[Math.min(line, mRightIndents.length - 1)];
1172 }
1173 return totalIndent;
1174 }
1175
Doug Felte8e45f22010-03-29 14:58:40 -07001176 // Override the base class so we can directly access our members,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001177 // rather than relying on member functions.
1178 // The logic mirrors that of Layout.getLineForVertical
1179 // FIXME: It may be faster to do a linear search for layouts without many lines.
Gilles Debunne66111472010-11-19 11:04:37 -08001180 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001181 public int getLineForVertical(int vertical) {
1182 int high = mLineCount;
1183 int low = -1;
1184 int guess;
1185 int[] lines = mLines;
1186 while (high - low > 1) {
1187 guess = (high + low) >> 1;
1188 if (lines[mColumns * guess + TOP] > vertical){
1189 high = guess;
1190 } else {
1191 low = guess;
1192 }
1193 }
1194 if (low < 0) {
1195 return 0;
1196 } else {
1197 return low;
1198 }
1199 }
1200
Gilles Debunne66111472010-11-19 11:04:37 -08001201 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001202 public int getLineCount() {
1203 return mLineCount;
1204 }
1205
Gilles Debunne66111472010-11-19 11:04:37 -08001206 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001207 public int getLineTop(int line) {
Raph Levien07e6c232016-04-04 12:34:06 -07001208 return mLines[mColumns * line + TOP];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001209 }
1210
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001211 /**
1212 * @hide
1213 */
1214 @Override
1215 public int getLineExtra(int line) {
1216 return mLines[mColumns * line + EXTRA];
1217 }
1218
Gilles Debunne66111472010-11-19 11:04:37 -08001219 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001220 public int getLineDescent(int line) {
Raph Levien07e6c232016-04-04 12:34:06 -07001221 return mLines[mColumns * line + DESCENT];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001222 }
1223
Gilles Debunne66111472010-11-19 11:04:37 -08001224 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001225 public int getLineStart(int line) {
1226 return mLines[mColumns * line + START] & START_MASK;
1227 }
1228
Gilles Debunne66111472010-11-19 11:04:37 -08001229 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001230 public int getParagraphDirection(int line) {
1231 return mLines[mColumns * line + DIR] >> DIR_SHIFT;
1232 }
1233
Gilles Debunne66111472010-11-19 11:04:37 -08001234 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001235 public boolean getLineContainsTab(int line) {
1236 return (mLines[mColumns * line + TAB] & TAB_MASK) != 0;
1237 }
1238
Gilles Debunne66111472010-11-19 11:04:37 -08001239 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001240 public final Directions getLineDirections(int line) {
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001241 if (line > getLineCount()) {
1242 throw new ArrayIndexOutOfBoundsException();
1243 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001244 return mLineDirections[line];
1245 }
1246
Gilles Debunne66111472010-11-19 11:04:37 -08001247 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001248 public int getTopPadding() {
1249 return mTopPadding;
1250 }
1251
Gilles Debunne66111472010-11-19 11:04:37 -08001252 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001253 public int getBottomPadding() {
1254 return mBottomPadding;
1255 }
1256
Raph Levien26d443a2015-03-30 14:18:32 -07001257 /**
1258 * @hide
1259 */
1260 @Override
1261 public int getHyphen(int line) {
Keisuke Kuroyanagif5af4a32016-08-31 21:40:53 +09001262 return mLines[mColumns * line + HYPHEN] & HYPHEN_MASK;
Raph Levien26d443a2015-03-30 14:18:32 -07001263 }
1264
Raph Levien2ea52902015-07-01 14:39:31 -07001265 /**
1266 * @hide
1267 */
1268 @Override
1269 public int getIndentAdjust(int line, Alignment align) {
1270 if (align == Alignment.ALIGN_LEFT) {
1271 if (mLeftIndents == null) {
1272 return 0;
1273 } else {
1274 return mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1275 }
1276 } else if (align == Alignment.ALIGN_RIGHT) {
1277 if (mRightIndents == null) {
1278 return 0;
1279 } else {
1280 return -mRightIndents[Math.min(line, mRightIndents.length - 1)];
1281 }
1282 } else if (align == Alignment.ALIGN_CENTER) {
1283 int left = 0;
1284 if (mLeftIndents != null) {
1285 left = mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1286 }
1287 int right = 0;
1288 if (mRightIndents != null) {
1289 right = mRightIndents[Math.min(line, mRightIndents.length - 1)];
1290 }
1291 return (left - right) >> 1;
1292 } else {
1293 throw new AssertionError("unhandled alignment " + align);
1294 }
1295 }
1296
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001297 @Override
1298 public int getEllipsisCount(int line) {
1299 if (mColumns < COLUMNS_ELLIPSIZE) {
1300 return 0;
1301 }
1302
1303 return mLines[mColumns * line + ELLIPSIS_COUNT];
1304 }
1305
1306 @Override
1307 public int getEllipsisStart(int line) {
1308 if (mColumns < COLUMNS_ELLIPSIZE) {
1309 return 0;
1310 }
1311
1312 return mLines[mColumns * line + ELLIPSIS_START];
1313 }
1314
1315 @Override
1316 public int getEllipsizedWidth() {
1317 return mEllipsizedWidth;
1318 }
1319
Siyamed Sinir0745c722016-05-31 20:39:33 -07001320 /**
1321 * Return the total height of this layout.
1322 *
1323 * @param cap if true and max lines is set, returns the height of the layout at the max lines.
1324 *
1325 * @hide
1326 */
1327 public int getHeight(boolean cap) {
1328 if (cap && mLineCount >= mMaximumVisibleLineCount && mMaxLineHeight == -1 &&
1329 Log.isLoggable(TAG, Log.WARN)) {
1330 Log.w(TAG, "maxLineHeight should not be -1. "
1331 + " maxLines:" + mMaximumVisibleLineCount
1332 + " lineCount:" + mLineCount);
1333 }
1334
1335 return cap && mLineCount >= mMaximumVisibleLineCount && mMaxLineHeight != -1 ?
1336 mMaxLineHeight : super.getHeight();
1337 }
1338
Seigo Nonakab90e08f2017-10-20 15:23:51 -07001339 @FastNative
1340 private static native long nInit(
1341 @BreakStrategy int breakStrategy,
1342 @HyphenationFrequency int hyphenationFrequency,
1343 boolean isJustified,
1344 @Nullable int[] indents,
1345 @Nullable int[] leftPaddings,
1346 @Nullable int[] rightPaddings);
1347
1348 @CriticalNative
1349 private static native void nFinish(long nativePtr);
1350
Anish Athalyec8f9e622014-07-21 15:26:34 -07001351 // populates LineBreaks and returns the number of breaks found
1352 //
1353 // the arrays inside the LineBreaks objects are passed in as well
1354 // to reduce the number of JNI calls in the common case where the
1355 // arrays do not have to be resized
Seigo Nonaka2dd1a552017-10-06 11:41:00 -07001356 // The individual character widths will be returned in charWidths. The length of charWidths must
1357 // be at least the length of the text.
Seigo Nonakab90e08f2017-10-20 15:23:51 -07001358 private static native int nComputeLineBreaks(
1359 /* non zero */ long nativePtr,
1360
1361 // Inputs
1362 @NonNull char[] text,
Seigo Nonaka6f1868b2017-12-01 16:24:19 -08001363 /* Non Zero */ long measuredTextPtr,
Seigo Nonakab90e08f2017-10-20 15:23:51 -07001364 @IntRange(from = 0) int length,
1365 @FloatRange(from = 0.0f) float firstWidth,
1366 @IntRange(from = 0) int firstWidthLineCount,
1367 @FloatRange(from = 0.0f) float restWidth,
1368 @Nullable int[] variableTabStops,
1369 int defaultTabStop,
1370 @IntRange(from = 0) int indentsOffset,
1371
1372 // Outputs
1373 @NonNull LineBreaks recycle,
1374 @IntRange(from = 0) int recycleLength,
1375 @NonNull int[] recycleBreaks,
1376 @NonNull float[] recycleWidths,
1377 @NonNull float[] recycleAscents,
1378 @NonNull float[] recycleDescents,
1379 @NonNull int[] recycleFlags,
1380 @NonNull float[] charWidths);
Anish Athalye88b5b0b2014-06-24 14:39:43 -07001381
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001382 private int mLineCount;
1383 private int mTopPadding, mBottomPadding;
1384 private int mColumns;
1385 private int mEllipsizedWidth;
1386
Siyamed Sinir0745c722016-05-31 20:39:33 -07001387 /**
1388 * Keeps track if ellipsize is applied to the text.
1389 */
1390 private boolean mEllipsized;
1391
1392 /**
1393 * If maxLines is set, ellipsize is not set, and the actual line count of text is greater than
1394 * or equal to maxLine, this variable holds the ideal visual height of the maxLine'th line
1395 * starting from the top of the layout. If maxLines is not set its value will be -1.
1396 *
1397 * The value is the same as getLineTop(maxLines) for ellipsized version where structurally no
1398 * more than maxLines is contained.
1399 */
Siyamed Sinira19cd512017-08-03 22:01:56 -07001400 private int mMaxLineHeight = DEFAULT_MAX_LINE_HEIGHT;
Siyamed Sinir0745c722016-05-31 20:39:33 -07001401
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001402 private static final int COLUMNS_NORMAL = 5;
1403 private static final int COLUMNS_ELLIPSIZE = 7;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001404 private static final int START = 0;
1405 private static final int DIR = START;
1406 private static final int TAB = START;
1407 private static final int TOP = 1;
1408 private static final int DESCENT = 2;
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001409 private static final int EXTRA = 3;
1410 private static final int HYPHEN = 4;
1411 private static final int ELLIPSIS_START = 5;
1412 private static final int ELLIPSIS_COUNT = 6;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001413
1414 private int[] mLines;
1415 private Directions[] mLineDirections;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001416 private int mMaximumVisibleLineCount = Integer.MAX_VALUE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001417
1418 private static final int START_MASK = 0x1FFFFFFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001419 private static final int DIR_SHIFT = 30;
1420 private static final int TAB_MASK = 0x20000000;
Keisuke Kuroyanagif5af4a32016-08-31 21:40:53 +09001421 private static final int HYPHEN_MASK = 0xFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001422
Doug Feltc982f602010-05-25 11:51:40 -07001423 private static final int TAB_INCREMENT = 20; // same as Layout, but that's private
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001424
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001425 private static final char CHAR_NEW_LINE = '\n';
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001426
1427 private static final double EXTRA_ROUNDING = 0.5;
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001428
Siyamed Sinira19cd512017-08-03 22:01:56 -07001429 private static final int DEFAULT_MAX_LINE_HEIGHT = -1;
1430
Anish Athalyec8f9e622014-07-21 15:26:34 -07001431 // This is used to return three arrays from a single JNI call when
1432 // performing line breaking
Deepanshu Gupta70539192014-10-15 15:57:40 -07001433 /*package*/ static class LineBreaks {
Anish Athalyec8f9e622014-07-21 15:26:34 -07001434 private static final int INITIAL_SIZE = 16;
1435 public int[] breaks = new int[INITIAL_SIZE];
1436 public float[] widths = new float[INITIAL_SIZE];
Roozbeh Pournader737dfea2017-08-10 11:32:24 -07001437 public float[] ascents = new float[INITIAL_SIZE];
1438 public float[] descents = new float[INITIAL_SIZE];
Roozbeh Pournader112d9c72015-08-07 12:44:41 -07001439 public int[] flags = new int[INITIAL_SIZE]; // hasTab
Anish Athalyec8f9e622014-07-21 15:26:34 -07001440 // breaks, widths, and flags should all have the same length
1441 }
1442
Seigo Nonakabafe1972017-08-24 15:30:29 -07001443 @Nullable private int[] mLeftIndents;
1444 @Nullable private int[] mRightIndents;
Roozbeh Pournader9a9179d2017-08-29 14:14:28 -07001445 @Nullable private int[] mLeftPaddings;
1446 @Nullable private int[] mRightPaddings;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001447}