blob: 7628e755eceb3d434fd013c64bae9bdf12805e8e [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;
Roozbeh Pournaderef7cfa12017-06-15 12:39:04 -070024import android.os.LocaleList;
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;
28import android.text.style.MetricAffectingSpan;
Doug Feltc982f602010-05-25 11:51:40 -070029import android.text.style.TabStopSpan;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -070030import android.util.Log;
Seigo Nonaka1bee3332017-09-27 18:37:37 -070031import android.util.Pair;
Raph Levien39b4db72015-03-25 13:18:20 -070032import android.util.Pools.SynchronizedPool;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080033
Doug Feltcb3791202011-07-07 11:57:48 -070034import com.android.internal.util.ArrayUtils;
Adam Lesinski776abc22014-03-07 11:30:59 -050035import com.android.internal.util.GrowingArrayUtils;
Doug Feltcb3791202011-07-07 11:57:48 -070036
Anish Athalyec8f9e622014-07-21 15:26:34 -070037import java.util.Arrays;
Raph Levien4c1f12e2015-03-02 16:29:23 -080038import java.util.Locale;
Anish Athalyec8f9e622014-07-21 15:26:34 -070039
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 {
51
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -070052 static final String TAG = "StaticLayout";
53
Raph Leviend3ab6922015-03-02 14:30:53 -080054 /**
Roozbeh Pournader22a167c2017-08-21 12:53:44 -070055 * Builder for static layouts. The builder is the preferred pattern for constructing
56 * StaticLayout objects and should be preferred over the constructors, particularly to access
57 * newer features. To build a static layout, first call {@link #obtain} with the required
58 * arguments (text, paint, and width), then call setters for optional parameters, and finally
59 * {@link #build} to build the StaticLayout object. Parameters not explicitly set will get
Raph Levien531c30c2015-04-30 16:29:59 -070060 * default values.
Raph Leviend3ab6922015-03-02 14:30:53 -080061 */
62 public final static class Builder {
Raph Levien4c1f12e2015-03-02 16:29:23 -080063 private Builder() {
64 mNativePtr = nNewBuilder();
65 }
66
Raph Levien531c30c2015-04-30 16:29:59 -070067 /**
Roozbeh Pournader22a167c2017-08-21 12:53:44 -070068 * Obtain a builder for constructing StaticLayout objects.
Raph Levien531c30c2015-04-30 16:29:59 -070069 *
70 * @param source The text to be laid out, optionally with spans
71 * @param start The index of the start of the text
72 * @param end The index + 1 of the end of the text
73 * @param paint The base paint used for layout
74 * @param width The width in pixels
75 * @return a builder object used for constructing the StaticLayout
76 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -070077 @NonNull
78 public static Builder obtain(@NonNull CharSequence source, @IntRange(from = 0) int start,
79 @IntRange(from = 0) int end, @NonNull TextPaint paint,
80 @IntRange(from = 0) int width) {
Raph Levien39b4db72015-03-25 13:18:20 -070081 Builder b = sPool.acquire();
Raph Leviend3ab6922015-03-02 14:30:53 -080082 if (b == null) {
83 b = new Builder();
84 }
85
86 // set default initial values
Raph Levien39b4db72015-03-25 13:18:20 -070087 b.mText = source;
88 b.mStart = start;
89 b.mEnd = end;
Raph Levienebd66ca2015-04-30 15:27:57 -070090 b.mPaint = paint;
Raph Levien39b4db72015-03-25 13:18:20 -070091 b.mWidth = width;
92 b.mAlignment = Alignment.ALIGN_NORMAL;
Raph Leviend3ab6922015-03-02 14:30:53 -080093 b.mTextDir = TextDirectionHeuristics.FIRSTSTRONG_LTR;
Roozbeh Pournader22a167c2017-08-21 12:53:44 -070094 b.mSpacingMult = DEFAULT_LINESPACING_MULTIPLIER;
95 b.mSpacingAdd = DEFAULT_LINESPACING_ADDITION;
Raph Leviend3ab6922015-03-02 14:30:53 -080096 b.mIncludePad = true;
Roozbeh Pournader737dfea2017-08-10 11:32:24 -070097 b.mFallbackLineSpacing = false;
Raph Levien39b4db72015-03-25 13:18:20 -070098 b.mEllipsizedWidth = width;
Raph Leviend3ab6922015-03-02 14:30:53 -080099 b.mEllipsize = null;
100 b.mMaxLines = Integer.MAX_VALUE;
Raph Levien3bd60c72015-05-06 14:26:35 -0700101 b.mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700102 b.mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700103 b.mJustificationMode = Layout.JUSTIFICATION_MODE_NONE;
Seigo Nonaka5d518dc2017-08-31 14:01:54 -0700104 b.mLocales = null;
Raph Leviend3ab6922015-03-02 14:30:53 -0800105
106 b.mMeasuredText = MeasuredText.obtain();
107 return b;
108 }
109
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700110 /**
111 * This method should be called after the layout is finished getting constructed and the
112 * builder needs to be cleaned up and returned to the pool.
113 */
114 private static void recycle(@NonNull Builder b) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800115 b.mPaint = null;
116 b.mText = null;
117 MeasuredText.recycle(b.mMeasuredText);
Raph Levien3bd60c72015-05-06 14:26:35 -0700118 b.mMeasuredText = null;
Raph Levien2ea52902015-07-01 14:39:31 -0700119 b.mLeftIndents = null;
120 b.mRightIndents = null;
Seigo Nonaka5d518dc2017-08-31 14:01:54 -0700121 b.mLocales = null;
Roozbeh Pournader9a9179d2017-08-29 14:14:28 -0700122 b.mLeftPaddings = null;
123 b.mRightPaddings = null;
Raph Levien3bd60c72015-05-06 14:26:35 -0700124 nFinishBuilder(b.mNativePtr);
Raph Levien39b4db72015-03-25 13:18:20 -0700125 sPool.release(b);
Raph Leviend3ab6922015-03-02 14:30:53 -0800126 }
127
128 // release any expensive state
129 /* package */ void finish() {
Raph Levien4c1f12e2015-03-02 16:29:23 -0800130 nFinishBuilder(mNativePtr);
Raph Levien22ba7862015-07-29 12:34:13 -0700131 mText = null;
132 mPaint = null;
133 mLeftIndents = null;
134 mRightIndents = null;
Roozbeh Pournader9a9179d2017-08-29 14:14:28 -0700135 mLeftPaddings = null;
136 mRightPaddings = null;
Raph Leviend3ab6922015-03-02 14:30:53 -0800137 mMeasuredText.finish();
138 }
139
140 public Builder setText(CharSequence source) {
141 return setText(source, 0, source.length());
142 }
143
Raph Levien531c30c2015-04-30 16:29:59 -0700144 /**
145 * Set the text. Only useful when re-using the builder, which is done for
146 * the internal implementation of {@link DynamicLayout} but not as part
147 * of normal {@link StaticLayout} usage.
148 *
149 * @param source The text to be laid out, optionally with spans
150 * @param start The index of the start of the text
151 * @param end The index + 1 of the end of the text
152 * @return this builder, useful for chaining
153 *
154 * @hide
155 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700156 @NonNull
157 public Builder setText(@NonNull CharSequence source, int start, int end) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800158 mText = source;
159 mStart = start;
160 mEnd = end;
161 return this;
162 }
163
Raph Levien531c30c2015-04-30 16:29:59 -0700164 /**
165 * Set the paint. Internal for reuse cases only.
166 *
167 * @param paint The base paint used for layout
168 * @return this builder, useful for chaining
169 *
170 * @hide
171 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700172 @NonNull
173 public Builder setPaint(@NonNull TextPaint paint) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800174 mPaint = paint;
175 return this;
176 }
177
Raph Levien531c30c2015-04-30 16:29:59 -0700178 /**
179 * Set the width. Internal for reuse cases only.
180 *
181 * @param width The width in pixels
182 * @return this builder, useful for chaining
183 *
184 * @hide
185 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700186 @NonNull
187 public Builder setWidth(@IntRange(from = 0) int width) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800188 mWidth = width;
189 if (mEllipsize == null) {
190 mEllipsizedWidth = width;
191 }
192 return this;
193 }
194
Raph Levien531c30c2015-04-30 16:29:59 -0700195 /**
196 * Set the alignment. The default is {@link Layout.Alignment#ALIGN_NORMAL}.
197 *
198 * @param alignment Alignment for the resulting {@link StaticLayout}
199 * @return this builder, useful for chaining
200 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700201 @NonNull
202 public Builder setAlignment(@NonNull Alignment alignment) {
Raph Levien39b4db72015-03-25 13:18:20 -0700203 mAlignment = alignment;
204 return this;
205 }
206
Raph Levien531c30c2015-04-30 16:29:59 -0700207 /**
208 * Set the text direction heuristic. The text direction heuristic is used to
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700209 * resolve text direction per-paragraph based on the input text. The default is
Raph Levien531c30c2015-04-30 16:29:59 -0700210 * {@link TextDirectionHeuristics#FIRSTSTRONG_LTR}.
211 *
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700212 * @param textDir text direction heuristic for resolving bidi behavior.
Raph Levien531c30c2015-04-30 16:29:59 -0700213 * @return this builder, useful for chaining
214 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700215 @NonNull
216 public Builder setTextDirection(@NonNull TextDirectionHeuristic textDir) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800217 mTextDir = textDir;
218 return this;
219 }
220
Raph Levien531c30c2015-04-30 16:29:59 -0700221 /**
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700222 * Set line spacing parameters. Each line will have its line spacing multiplied by
223 * {@code spacingMult} and then increased by {@code spacingAdd}. The default is 0.0 for
224 * {@code spacingAdd} and 1.0 for {@code spacingMult}.
Raph Levien531c30c2015-04-30 16:29:59 -0700225 *
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700226 * @param spacingAdd the amount of line spacing addition
227 * @param spacingMult the line spacing multiplier
Raph Levien531c30c2015-04-30 16:29:59 -0700228 * @return this builder, useful for chaining
229 * @see android.widget.TextView#setLineSpacing
230 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700231 @NonNull
232 public Builder setLineSpacing(float spacingAdd, @FloatRange(from = 0.0) float spacingMult) {
Raph Levien531c30c2015-04-30 16:29:59 -0700233 mSpacingAdd = spacingAdd;
Raph Leviend3ab6922015-03-02 14:30:53 -0800234 mSpacingMult = spacingMult;
235 return this;
236 }
237
Raph Levien531c30c2015-04-30 16:29:59 -0700238 /**
239 * Set whether to include extra space beyond font ascent and descent (which is
240 * needed to avoid clipping in some languages, such as Arabic and Kannada). The
241 * default is {@code true}.
242 *
243 * @param includePad whether to include padding
244 * @return this builder, useful for chaining
245 * @see android.widget.TextView#setIncludeFontPadding
246 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700247 @NonNull
Raph Leviend3ab6922015-03-02 14:30:53 -0800248 public Builder setIncludePad(boolean includePad) {
249 mIncludePad = includePad;
250 return this;
251 }
252
Raph Levien531c30c2015-04-30 16:29:59 -0700253 /**
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700254 * Set whether to respect the ascent and descent of the fallback fonts that are used in
255 * displaying the text (which is needed to avoid text from consecutive lines running into
256 * each other). If set, fallback fonts that end up getting used can increase the ascent
257 * and descent of the lines that they are used on.
258 *
259 * <p>For backward compatibility reasons, the default is {@code false}, but setting this to
260 * true is strongly recommended. It is required to be true if text could be in languages
261 * like Burmese or Tibetan where text is typically much taller or deeper than Latin text.
262 *
263 * @param useLineSpacingFromFallbacks whether to expand linespacing based on fallback fonts
264 * @return this builder, useful for chaining
265 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700266 @NonNull
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700267 public Builder setUseLineSpacingFromFallbacks(boolean useLineSpacingFromFallbacks) {
268 mFallbackLineSpacing = useLineSpacingFromFallbacks;
269 return this;
270 }
271
272 /**
Raph Levien531c30c2015-04-30 16:29:59 -0700273 * Set the width as used for ellipsizing purposes, if it differs from the
274 * normal layout width. The default is the {@code width}
275 * passed to {@link #obtain}.
276 *
277 * @param ellipsizedWidth width used for ellipsizing, in pixels
278 * @return this builder, useful for chaining
279 * @see android.widget.TextView#setEllipsize
280 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700281 @NonNull
282 public Builder setEllipsizedWidth(@IntRange(from = 0) int ellipsizedWidth) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800283 mEllipsizedWidth = ellipsizedWidth;
284 return this;
285 }
286
Raph Levien531c30c2015-04-30 16:29:59 -0700287 /**
288 * Set ellipsizing on the layout. Causes words that are longer than the view
289 * is wide, or exceeding the number of lines (see #setMaxLines) in the case
290 * of {@link android.text.TextUtils.TruncateAt#END} or
291 * {@link android.text.TextUtils.TruncateAt#MARQUEE}, to be ellipsized instead
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700292 * of broken. The default is {@code null}, indicating no ellipsis is to be applied.
Raph Levien531c30c2015-04-30 16:29:59 -0700293 *
294 * @param ellipsize type of ellipsis behavior
295 * @return this builder, useful for chaining
296 * @see android.widget.TextView#setEllipsize
297 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700298 @NonNull
Raph Levien531c30c2015-04-30 16:29:59 -0700299 public Builder setEllipsize(@Nullable TextUtils.TruncateAt ellipsize) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800300 mEllipsize = ellipsize;
301 return this;
302 }
303
Raph Levien531c30c2015-04-30 16:29:59 -0700304 /**
305 * Set maximum number of lines. This is particularly useful in the case of
306 * ellipsizing, where it changes the layout of the last line. The default is
307 * unlimited.
308 *
309 * @param maxLines maximum number of lines in the layout
310 * @return this builder, useful for chaining
311 * @see android.widget.TextView#setMaxLines
312 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700313 @NonNull
314 public Builder setMaxLines(@IntRange(from = 0) int maxLines) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800315 mMaxLines = maxLines;
316 return this;
317 }
318
Raph Levien531c30c2015-04-30 16:29:59 -0700319 /**
320 * Set break strategy, useful for selecting high quality or balanced paragraph
321 * layout options. The default is {@link Layout#BREAK_STRATEGY_SIMPLE}.
322 *
323 * @param breakStrategy break strategy for paragraph layout
324 * @return this builder, useful for chaining
325 * @see android.widget.TextView#setBreakStrategy
326 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700327 @NonNull
Raph Levien39b4db72015-03-25 13:18:20 -0700328 public Builder setBreakStrategy(@BreakStrategy int breakStrategy) {
329 mBreakStrategy = breakStrategy;
330 return this;
331 }
332
Raph Levien531c30c2015-04-30 16:29:59 -0700333 /**
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700334 * Set hyphenation frequency, to control the amount of automatic hyphenation used. The
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700335 * possible values are defined in {@link Layout}, by constants named with the pattern
336 * {@code HYPHENATION_FREQUENCY_*}. The default is
337 * {@link Layout#HYPHENATION_FREQUENCY_NONE}.
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700338 *
339 * @param hyphenationFrequency hyphenation frequency for the paragraph
340 * @return this builder, useful for chaining
341 * @see android.widget.TextView#setHyphenationFrequency
342 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700343 @NonNull
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700344 public Builder setHyphenationFrequency(@HyphenationFrequency int hyphenationFrequency) {
345 mHyphenationFrequency = hyphenationFrequency;
346 return this;
347 }
348
349 /**
Raph Levien531c30c2015-04-30 16:29:59 -0700350 * Set indents. Arguments are arrays holding an indent amount, one per line, measured in
351 * pixels. For lines past the last element in the array, the last element repeats.
352 *
353 * @param leftIndents array of indent values for left margin, in pixels
354 * @param rightIndents array of indent values for right margin, in pixels
355 * @return this builder, useful for chaining
Raph Levien531c30c2015-04-30 16:29:59 -0700356 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700357 @NonNull
358 public Builder setIndents(@Nullable int[] leftIndents, @Nullable int[] rightIndents) {
Raph Levien2ea52902015-07-01 14:39:31 -0700359 mLeftIndents = leftIndents;
360 mRightIndents = rightIndents;
Raph Leviene319d5a2015-04-14 23:51:07 -0700361 return this;
362 }
363
Raph Levien70616ec2015-03-04 10:41:30 -0800364 /**
Roozbeh Pournader9a9179d2017-08-29 14:14:28 -0700365 * Set available paddings to draw overhanging text on. Arguments are arrays holding the
366 * amount of padding available, one per line, measured in pixels. For lines past the last
367 * element in the array, the last element repeats.
368 *
369 * The individual padding amounts should be non-negative. The result of passing negative
370 * paddings is undefined.
371 *
372 * @param leftPaddings array of amounts of available padding for left margin, in pixels
373 * @param rightPaddings array of amounts of available padding for right margin, in pixels
374 * @return this builder, useful for chaining
375 *
376 * @hide
377 */
378 @NonNull
379 public Builder setAvailablePaddings(@Nullable int[] leftPaddings,
380 @Nullable int[] rightPaddings) {
381 mLeftPaddings = leftPaddings;
382 mRightPaddings = rightPaddings;
383 return this;
384 }
385
386 /**
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700387 * Set paragraph justification mode. The default value is
388 * {@link Layout#JUSTIFICATION_MODE_NONE}. If the last line is too short for justification,
389 * the last line will be displayed with the alignment set by {@link #setAlignment}.
Seigo Nonaka09da71a2016-11-28 16:24:14 +0900390 *
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700391 * @param justificationMode justification mode for the paragraph.
Seigo Nonaka09da71a2016-11-28 16:24:14 +0900392 * @return this builder, useful for chaining.
393 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700394 @NonNull
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700395 public Builder setJustificationMode(@JustificationMode int justificationMode) {
396 mJustificationMode = justificationMode;
Seigo Nonaka09da71a2016-11-28 16:24:14 +0900397 return this;
398 }
399
Siyamed Sinir442c1512017-07-24 12:18:27 -0700400 /**
401 * Sets whether the line spacing should be applied for the last line. Default value is
402 * {@code false}.
403 *
404 * @hide
405 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700406 @NonNull
Siyamed Sinir442c1512017-07-24 12:18:27 -0700407 /* package */ Builder setAddLastLineLineSpacing(boolean value) {
408 mAddLastLineLineSpacing = value;
409 return this;
410 }
411
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700412 @NonNull
413 private long[] getHyphenators(@NonNull LocaleList locales) {
Roozbeh Pournaderef7cfa12017-06-15 12:39:04 -0700414 final int length = locales.size();
415 final long[] result = new long[length];
416 for (int i = 0; i < length; i++) {
417 final Locale locale = locales.get(i);
418 result[i] = Hyphenator.get(locale).getNativePtr();
419 }
420 return result;
421 }
422
Seigo Nonaka09da71a2016-11-28 16:24:14 +0900423 /**
Raph Levien70616ec2015-03-04 10:41:30 -0800424 * Measurement and break iteration is done in native code. The protocol for using
425 * the native code is as follows.
426 *
Raph Levien26d443a2015-03-30 14:18:32 -0700427 * For each paragraph, do a nSetupParagraph, which sets paragraph text, line width, tab
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700428 * stops, break strategy, and hyphenation frequency (and possibly other parameters in the
429 * future).
Raph Levienc94f7422015-03-06 19:19:48 -0800430 *
431 * Then, for each run within the paragraph:
Raph Levien70616ec2015-03-04 10:41:30 -0800432 * - one of the following, depending on the type of run:
433 * + addStyleRun (a text run, to be measured in native code)
Raph Levien70616ec2015-03-04 10:41:30 -0800434 * + addReplacementRun (a replacement run, width is given)
435 *
436 * After measurement, nGetWidths() is valid if the widths are needed (eg for ellipsis).
437 * Run nComputeLineBreaks() to obtain line breaks for the paragraph.
438 *
439 * After all paragraphs, call finish() to release expensive buffers.
440 */
441
Seigo Nonaka1bee3332017-09-27 18:37:37 -0700442 private Pair<String, long[]> getLocaleAndHyphenatorIfChanged(TextPaint paint) {
Seigo Nonaka5d518dc2017-08-31 14:01:54 -0700443 final LocaleList locales = paint.getTextLocales();
Seigo Nonaka5d518dc2017-08-31 14:01:54 -0700444 if (!locales.equals(mLocales)) {
Seigo Nonaka1bee3332017-09-27 18:37:37 -0700445 mLocales = locales;
446 return new Pair(locales.toLanguageTags(), getHyphenators(locales));
Seigo Nonaka5d518dc2017-08-31 14:01:54 -0700447 } else {
448 // passing null means keep current locale.
449 // TODO: move locale change detection to native.
Seigo Nonaka1bee3332017-09-27 18:37:37 -0700450 return new Pair(null, null);
Seigo Nonaka5d518dc2017-08-31 14:01:54 -0700451 }
Seigo Nonaka1bee3332017-09-27 18:37:37 -0700452 }
453
Seigo Nonaka5cc93482017-10-02 16:01:14 -0700454 /* package */ void addStyleRun(TextPaint paint, int start, int end, boolean isRtl) {
Seigo Nonaka1bee3332017-09-27 18:37:37 -0700455 Pair<String, long[]> locHyph = getLocaleAndHyphenatorIfChanged(paint);
Seigo Nonaka5cc93482017-10-02 16:01:14 -0700456 nAddStyleRun(mNativePtr, paint.getNativeInstance(), start, end, isRtl, locHyph.first,
457 locHyph.second);
Raph Levien70616ec2015-03-04 10:41:30 -0800458 }
459
Seigo Nonaka1bee3332017-09-27 18:37:37 -0700460 /* package */ void addReplacementRun(TextPaint paint, int start, int end, float width) {
461 Pair<String, long[]> locHyph = getLocaleAndHyphenatorIfChanged(paint);
462 nAddReplacementRun(mNativePtr, start, end, width, locHyph.first, locHyph.second);
Raph Levien70616ec2015-03-04 10:41:30 -0800463 }
464
Raph Levien531c30c2015-04-30 16:29:59 -0700465 /**
466 * Build the {@link StaticLayout} after options have been set.
467 *
468 * <p>Note: the builder object must not be reused in any way after calling this
469 * method. Setting parameters after calling this method, or calling it a second
470 * time on the same builder object, will likely lead to unexpected results.
471 *
472 * @return the newly constructed {@link StaticLayout} object
473 */
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700474 @NonNull
Raph Leviend3ab6922015-03-02 14:30:53 -0800475 public StaticLayout build() {
Raph Levien39b4db72015-03-25 13:18:20 -0700476 StaticLayout result = new StaticLayout(this);
477 Builder.recycle(this);
Raph Leviend3ab6922015-03-02 14:30:53 -0800478 return result;
479 }
480
Raph Levien4c1f12e2015-03-02 16:29:23 -0800481 @Override
482 protected void finalize() throws Throwable {
483 try {
484 nFreeBuilder(mNativePtr);
485 } finally {
486 super.finalize();
487 }
488 }
489
490 /* package */ long mNativePtr;
491
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700492 private CharSequence mText;
493 private int mStart;
494 private int mEnd;
495 private TextPaint mPaint;
496 private int mWidth;
497 private Alignment mAlignment;
498 private TextDirectionHeuristic mTextDir;
499 private float mSpacingMult;
500 private float mSpacingAdd;
501 private boolean mIncludePad;
502 private boolean mFallbackLineSpacing;
503 private int mEllipsizedWidth;
504 private TextUtils.TruncateAt mEllipsize;
505 private int mMaxLines;
506 private int mBreakStrategy;
507 private int mHyphenationFrequency;
Seigo Nonakabafe1972017-08-24 15:30:29 -0700508 @Nullable private int[] mLeftIndents;
509 @Nullable private int[] mRightIndents;
Roozbeh Pournader9a9179d2017-08-29 14:14:28 -0700510 @Nullable private int[] mLeftPaddings;
511 @Nullable private int[] mRightPaddings;
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700512 private int mJustificationMode;
513 private boolean mAddLastLineLineSpacing;
Raph Leviend3ab6922015-03-02 14:30:53 -0800514
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700515 private final Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
Raph Leviend3ab6922015-03-02 14:30:53 -0800516
517 // This will go away and be subsumed by native builder code
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700518 private MeasuredText mMeasuredText;
Raph Leviend3ab6922015-03-02 14:30:53 -0800519
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700520 private LocaleList mLocales;
Raph Levien4c1f12e2015-03-02 16:29:23 -0800521
Siyamed Sinira273a702017-10-05 11:22:12 -0700522 private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<>(3);
Raph Leviend3ab6922015-03-02 14:30:53 -0800523 }
524
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800525 public StaticLayout(CharSequence source, TextPaint paint,
526 int width,
527 Alignment align, float spacingmult, float spacingadd,
528 boolean includepad) {
529 this(source, 0, source.length(), paint, width, align,
530 spacingmult, spacingadd, includepad);
531 }
532
Doug Feltcb3791202011-07-07 11:57:48 -0700533 /**
534 * @hide
535 */
536 public StaticLayout(CharSequence source, TextPaint paint,
537 int width, Alignment align, TextDirectionHeuristic textDir,
538 float spacingmult, float spacingadd,
539 boolean includepad) {
540 this(source, 0, source.length(), paint, width, align, textDir,
541 spacingmult, spacingadd, includepad);
542 }
543
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800544 public StaticLayout(CharSequence source, int bufstart, int bufend,
545 TextPaint paint, int outerwidth,
546 Alignment align,
547 float spacingmult, float spacingadd,
548 boolean includepad) {
549 this(source, bufstart, bufend, paint, outerwidth, align,
550 spacingmult, spacingadd, includepad, null, 0);
551 }
552
Doug Feltcb3791202011-07-07 11:57:48 -0700553 /**
554 * @hide
555 */
556 public StaticLayout(CharSequence source, int bufstart, int bufend,
557 TextPaint paint, int outerwidth,
558 Alignment align, TextDirectionHeuristic textDir,
559 float spacingmult, float spacingadd,
560 boolean includepad) {
561 this(source, bufstart, bufend, paint, outerwidth, align, textDir,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700562 spacingmult, spacingadd, includepad, null, 0, Integer.MAX_VALUE);
Doug Feltcb3791202011-07-07 11:57:48 -0700563}
564
565 public StaticLayout(CharSequence source, int bufstart, int bufend,
566 TextPaint paint, int outerwidth,
567 Alignment align,
568 float spacingmult, float spacingadd,
569 boolean includepad,
570 TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
571 this(source, bufstart, bufend, paint, outerwidth, align,
572 TextDirectionHeuristics.FIRSTSTRONG_LTR,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700573 spacingmult, spacingadd, includepad, ellipsize, ellipsizedWidth, Integer.MAX_VALUE);
Doug Feltcb3791202011-07-07 11:57:48 -0700574 }
575
576 /**
577 * @hide
578 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800579 public StaticLayout(CharSequence source, int bufstart, int bufend,
580 TextPaint paint, int outerwidth,
Doug Feltcb3791202011-07-07 11:57:48 -0700581 Alignment align, TextDirectionHeuristic textDir,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800582 float spacingmult, float spacingadd,
583 boolean includepad,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700584 TextUtils.TruncateAt ellipsize, int ellipsizedWidth, int maxLines) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800585 super((ellipsize == null)
Doug Felt4e0c5e52010-03-15 16:56:02 -0700586 ? source
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800587 : (source instanceof Spanned)
588 ? new SpannedEllipsizer(source)
589 : new Ellipsizer(source),
Doug Feltcb3791202011-07-07 11:57:48 -0700590 paint, outerwidth, align, textDir, spacingmult, spacingadd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800591
Raph Levienebd66ca2015-04-30 15:27:57 -0700592 Builder b = Builder.obtain(source, bufstart, bufend, paint, outerwidth)
Raph Levien39b4db72015-03-25 13:18:20 -0700593 .setAlignment(align)
Raph Leviena6a08282015-06-03 13:20:45 -0700594 .setTextDirection(textDir)
Raph Levien531c30c2015-04-30 16:29:59 -0700595 .setLineSpacing(spacingadd, spacingmult)
Raph Leviend3ab6922015-03-02 14:30:53 -0800596 .setIncludePad(includepad)
597 .setEllipsizedWidth(ellipsizedWidth)
598 .setEllipsize(ellipsize)
599 .setMaxLines(maxLines);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800600 /*
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700601 * This is annoying, but we can't refer to the layout until superclass construction is
602 * finished, and the superclass constructor wants the reference to the display text.
Doug Felt4e0c5e52010-03-15 16:56:02 -0700603 *
Roozbeh Pournader22a167c2017-08-21 12:53:44 -0700604 * In other words, the two Ellipsizer classes in Layout.java need a (Dynamic|Static)Layout
605 * as a parameter to do their calculations, but the Ellipsizers also need to be the input
606 * to the superclass's constructor (Layout). In order to go around the circular
607 * dependency, we construct the Ellipsizer with only one of the parameters, the text. And
608 * we fill in the rest of the needed information (layout, width, and method) later, here.
609 *
610 * This will break if the superclass constructor ever actually cares about the content
611 * instead of just holding the reference.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800612 */
613 if (ellipsize != null) {
614 Ellipsizer e = (Ellipsizer) getText();
615
616 e.mLayout = this;
617 e.mWidth = ellipsizedWidth;
618 e.mMethod = ellipsize;
619 mEllipsizedWidth = ellipsizedWidth;
620
621 mColumns = COLUMNS_ELLIPSIZE;
622 } else {
623 mColumns = COLUMNS_NORMAL;
624 mEllipsizedWidth = outerwidth;
625 }
626
Siyamed Siniraf398512017-07-25 19:08:42 -0700627 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2);
628 mLines = ArrayUtils.newUnpaddedIntArray(2 * mColumns);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700629 mMaximumVisibleLineCount = maxLines;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800630
Raph Levien70616ec2015-03-04 10:41:30 -0800631 generate(b, b.mIncludePad, b.mIncludePad);
Doug Felte8e45f22010-03-29 14:58:40 -0700632
Raph Leviend3ab6922015-03-02 14:30:53 -0800633 Builder.recycle(b);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800634 }
635
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700636 /* package */ StaticLayout(@Nullable CharSequence text) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700637 super(text, null, 0, null, 0, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800638
639 mColumns = COLUMNS_ELLIPSIZE;
Siyamed Siniraf398512017-07-25 19:08:42 -0700640 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2);
641 mLines = ArrayUtils.newUnpaddedIntArray(2 * mColumns);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800642 }
643
Raph Levien39b4db72015-03-25 13:18:20 -0700644 private StaticLayout(Builder b) {
645 super((b.mEllipsize == null)
646 ? b.mText
647 : (b.mText instanceof Spanned)
648 ? new SpannedEllipsizer(b.mText)
649 : new Ellipsizer(b.mText),
Roozbeh Pournader158dfaf2017-09-21 13:23:50 -0700650 b.mPaint, b.mWidth, b.mAlignment, b.mTextDir, b.mSpacingMult, b.mSpacingAdd);
Raph Levien39b4db72015-03-25 13:18:20 -0700651
652 if (b.mEllipsize != null) {
653 Ellipsizer e = (Ellipsizer) getText();
654
655 e.mLayout = this;
656 e.mWidth = b.mEllipsizedWidth;
657 e.mMethod = b.mEllipsize;
658 mEllipsizedWidth = b.mEllipsizedWidth;
659
660 mColumns = COLUMNS_ELLIPSIZE;
661 } else {
662 mColumns = COLUMNS_NORMAL;
663 mEllipsizedWidth = b.mWidth;
664 }
665
Siyamed Siniraf398512017-07-25 19:08:42 -0700666 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2);
667 mLines = ArrayUtils.newUnpaddedIntArray(2 * mColumns);
Raph Levien39b4db72015-03-25 13:18:20 -0700668 mMaximumVisibleLineCount = b.mMaxLines;
669
Raph Levien2ea52902015-07-01 14:39:31 -0700670 mLeftIndents = b.mLeftIndents;
671 mRightIndents = b.mRightIndents;
Roozbeh Pournader9a9179d2017-08-29 14:14:28 -0700672 mLeftPaddings = b.mLeftPaddings;
673 mRightPaddings = b.mRightPaddings;
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700674 setJustificationMode(b.mJustificationMode);
Raph Levien2ea52902015-07-01 14:39:31 -0700675
Raph Levien39b4db72015-03-25 13:18:20 -0700676 generate(b, b.mIncludePad, b.mIncludePad);
677 }
678
Raph Leviend3ab6922015-03-02 14:30:53 -0800679 /* package */ void generate(Builder b, boolean includepad, boolean trackpad) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700680 final CharSequence source = b.mText;
Raph Leviend3ab6922015-03-02 14:30:53 -0800681 int bufStart = b.mStart;
682 int bufEnd = b.mEnd;
683 TextPaint paint = b.mPaint;
684 int outerWidth = b.mWidth;
685 TextDirectionHeuristic textDir = b.mTextDir;
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700686 final boolean fallbackLineSpacing = b.mFallbackLineSpacing;
Raph Leviend3ab6922015-03-02 14:30:53 -0800687 float spacingmult = b.mSpacingMult;
688 float spacingadd = b.mSpacingAdd;
689 float ellipsizedWidth = b.mEllipsizedWidth;
690 TextUtils.TruncateAt ellipsize = b.mEllipsize;
Siyamed Sinir442c1512017-07-24 12:18:27 -0700691 final boolean addLastLineSpacing = b.mAddLastLineLineSpacing;
Raph Levien4c1f12e2015-03-02 16:29:23 -0800692 LineBreaks lineBreaks = new LineBreaks(); // TODO: move to builder to avoid allocation costs
Anish Athalyec8f9e622014-07-21 15:26:34 -0700693 // store span end locations
694 int[] spanEndCache = new int[4];
695 // store fontMetrics per span range
696 // must be a multiple of 4 (and > 0) (store top, bottom, ascent, and descent per range)
697 int[] fmCache = new int[4 * 4];
Anish Athalye88b5b0b2014-06-24 14:39:43 -0700698
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800699 mLineCount = 0;
Siyamed Sinira19cd512017-08-03 22:01:56 -0700700 mEllipsized = false;
Siyamed Sinira8d982d2017-08-21 13:27:47 -0700701 mMaxLineHeight = mMaximumVisibleLineCount < 1 ? 0 : DEFAULT_MAX_LINE_HEIGHT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800702
703 int v = 0;
704 boolean needMultiply = (spacingmult != 1 || spacingadd != 0);
705
Raph Leviend3ab6922015-03-02 14:30:53 -0800706 Paint.FontMetricsInt fm = b.mFontMetricsInt;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800707 int[] chooseHtv = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800708
Raph Leviend3ab6922015-03-02 14:30:53 -0800709 MeasuredText measured = b.mMeasuredText;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800710
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800711 Spanned spanned = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800712 if (source instanceof Spanned)
713 spanned = (Spanned) source;
714
Seigo Nonakabafe1972017-08-24 15:30:29 -0700715 final int[] indents;
716 if (mLeftIndents != null || mRightIndents != null) {
717 final int leftLen = mLeftIndents == null ? 0 : mLeftIndents.length;
718 final int rightLen = mRightIndents == null ? 0 : mRightIndents.length;
719 final int indentsLen = Math.max(leftLen, rightLen);
720 indents = new int[indentsLen];
721 for (int i = 0; i < leftLen; i++) {
722 indents[i] = mLeftIndents[i];
723 }
724 for (int i = 0; i < rightLen; i++) {
725 indents[i] += mRightIndents[i];
726 }
727 } else {
728 indents = null;
729 }
730
Doug Felte8e45f22010-03-29 14:58:40 -0700731 int paraEnd;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800732 for (int paraStart = bufStart; paraStart <= bufEnd; paraStart = paraEnd) {
733 paraEnd = TextUtils.indexOf(source, CHAR_NEW_LINE, paraStart, bufEnd);
Doug Felte8e45f22010-03-29 14:58:40 -0700734 if (paraEnd < 0)
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800735 paraEnd = bufEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800736 else
Doug Felte8e45f22010-03-29 14:58:40 -0700737 paraEnd++;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800738
Anish Athalyec8f9e622014-07-21 15:26:34 -0700739 int firstWidthLineCount = 1;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800740 int firstWidth = outerWidth;
741 int restWidth = outerWidth;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800742
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800743 LineHeightSpan[] chooseHt = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800744
745 if (spanned != null) {
Eric Fischer74d31ef2010-08-05 15:29:36 -0700746 LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
Doug Felte8e45f22010-03-29 14:58:40 -0700747 LeadingMarginSpan.class);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800748 for (int i = 0; i < sp.length; i++) {
Mark Wagner7b5676e2009-10-16 11:44:23 -0700749 LeadingMarginSpan lms = sp[i];
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800750 firstWidth -= sp[i].getLeadingMargin(true);
751 restWidth -= sp[i].getLeadingMargin(false);
Doug Feltcb3791202011-07-07 11:57:48 -0700752
Doug Feltc982f602010-05-25 11:51:40 -0700753 // LeadingMarginSpan2 is odd. The count affects all
Anish Athalyeab08c6d2014-08-08 12:09:58 -0700754 // leading margin spans, not just this particular one
Doug Feltc982f602010-05-25 11:51:40 -0700755 if (lms instanceof LeadingMarginSpan2) {
756 LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700757 firstWidthLineCount = Math.max(firstWidthLineCount,
758 lms2.getLeadingMarginLineCount());
Mark Wagner7b5676e2009-10-16 11:44:23 -0700759 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800760 }
761
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800762 chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800763
Roozbeh Pournader431e5062015-10-16 02:27:03 -0700764 if (chooseHt.length == 0) {
765 chooseHt = null; // So that out() would not assume it has any contents
766 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800767 if (chooseHtv == null ||
768 chooseHtv.length < chooseHt.length) {
Adam Lesinski776abc22014-03-07 11:30:59 -0500769 chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800770 }
771
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800772 for (int i = 0; i < chooseHt.length; i++) {
773 int o = spanned.getSpanStart(chooseHt[i]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800774
Doug Felte8e45f22010-03-29 14:58:40 -0700775 if (o < paraStart) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800776 // starts in this layout, before the
777 // current paragraph
778
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800779 chooseHtv[i] = getLineTop(getLineForOffset(o));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800780 } else {
781 // starts in this paragraph
782
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800783 chooseHtv[i] = v;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800784 }
785 }
786 }
787 }
788
Raph Levien70616ec2015-03-04 10:41:30 -0800789 measured.setPara(source, paraStart, paraEnd, textDir, b);
Doug Felte8e45f22010-03-29 14:58:40 -0700790 char[] chs = measured.mChars;
791 float[] widths = measured.mWidths;
792 byte[] chdirs = measured.mLevels;
793 int dir = measured.mDir;
794 boolean easy = measured.mEasy;
Raph Levienc94f7422015-03-06 19:19:48 -0800795
796 // tab stop locations
797 int[] variableTabStops = null;
798 if (spanned != null) {
799 TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
800 paraEnd, TabStopSpan.class);
801 if (spans.length > 0) {
802 int[] stops = new int[spans.length];
803 for (int i = 0; i < spans.length; i++) {
804 stops[i] = spans[i].getTabStop();
805 }
806 Arrays.sort(stops, 0, stops.length);
807 variableTabStops = stops;
808 }
809 }
810
Seigo Nonaka1bee3332017-09-27 18:37:37 -0700811 // TODO: Move locale tracking code to native.
812 b.mLocales = null; // Reset the locale tracking.
813
Raph Levienc94f7422015-03-06 19:19:48 -0800814 nSetupParagraph(b.mNativePtr, chs, paraEnd - paraStart,
815 firstWidth, firstWidthLineCount, restWidth,
Seigo Nonaka09da71a2016-11-28 16:24:14 +0900816 variableTabStops, TAB_INCREMENT, b.mBreakStrategy, b.mHyphenationFrequency,
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700817 // TODO: Support more justification mode, e.g. letter spacing, stretching.
Roozbeh Pournader9a9179d2017-08-29 14:14:28 -0700818 b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE,
819 // TODO: indents and paddings don't need to get passed to native code for every
820 // paragraph. Pass them to native code just once.
821 indents, mLeftPaddings, mRightPaddings, mLineCount);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800822
Anish Athalyec8f9e622014-07-21 15:26:34 -0700823 // measurement has to be done before performing line breaking
824 // but we don't want to recompute fontmetrics or span ranges the
825 // second time, so we cache those and then use those stored values
826 int fmCacheCount = 0;
827 int spanEndCacheCount = 0;
Gilles Debunnecd943a72012-06-07 17:54:47 -0700828 for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
Anish Athalyec8f9e622014-07-21 15:26:34 -0700829 if (fmCacheCount * 4 >= fmCache.length) {
830 int[] grow = new int[fmCacheCount * 4 * 2];
831 System.arraycopy(fmCache, 0, grow, 0, fmCacheCount * 4);
832 fmCache = grow;
833 }
834
835 if (spanEndCacheCount >= spanEndCache.length) {
836 int[] grow = new int[spanEndCacheCount * 2];
837 System.arraycopy(spanEndCache, 0, grow, 0, spanEndCacheCount);
838 spanEndCache = grow;
839 }
Doug Felte8e45f22010-03-29 14:58:40 -0700840
Gilles Debunnecd943a72012-06-07 17:54:47 -0700841 if (spanned == null) {
842 spanEnd = paraEnd;
Doug Felt23241882010-06-02 14:41:06 -0700843 int spanLen = spanEnd - spanStart;
Gilles Debunnecd943a72012-06-07 17:54:47 -0700844 measured.addStyleRun(paint, spanLen, fm);
845 } else {
846 spanEnd = spanned.nextSpanTransition(spanStart, paraEnd,
847 MetricAffectingSpan.class);
848 int spanLen = spanEnd - spanStart;
849 MetricAffectingSpan[] spans =
Doug Felt23241882010-06-02 14:41:06 -0700850 spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class);
Gilles Debunnecd943a72012-06-07 17:54:47 -0700851 spans = TextUtils.removeEmptySpans(spans, spanned, MetricAffectingSpan.class);
852 measured.addStyleRun(paint, spans, spanLen, fm);
Doug Felt23241882010-06-02 14:41:06 -0700853 }
854
Anish Athalyec8f9e622014-07-21 15:26:34 -0700855 // the order of storage here (top, bottom, ascent, descent) has to match the code below
856 // where these values are retrieved
857 fmCache[fmCacheCount * 4 + 0] = fm.top;
858 fmCache[fmCacheCount * 4 + 1] = fm.bottom;
859 fmCache[fmCacheCount * 4 + 2] = fm.ascent;
860 fmCache[fmCacheCount * 4 + 3] = fm.descent;
861 fmCacheCount++;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800862
Anish Athalyec8f9e622014-07-21 15:26:34 -0700863 spanEndCache[spanEndCacheCount] = spanEnd;
864 spanEndCacheCount++;
865 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800866
Raph Levien70616ec2015-03-04 10:41:30 -0800867 nGetWidths(b.mNativePtr, widths);
Raph Levienc94f7422015-03-06 19:19:48 -0800868 int breakCount = nComputeLineBreaks(b.mNativePtr, lineBreaks, lineBreaks.breaks,
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700869 lineBreaks.widths, lineBreaks.ascents, lineBreaks.descents, lineBreaks.flags,
870 lineBreaks.breaks.length);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800871
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700872 final int[] breaks = lineBreaks.breaks;
873 final float[] lineWidths = lineBreaks.widths;
874 final float[] ascents = lineBreaks.ascents;
875 final float[] descents = lineBreaks.descents;
876 final int[] flags = lineBreaks.flags;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700877
Keisuke Kuroyanagif4a3f3a2015-06-10 09:37:32 +0900878 final int remainingLineCount = mMaximumVisibleLineCount - mLineCount;
879 final boolean ellipsisMayBeApplied = ellipsize != null
880 && (ellipsize == TextUtils.TruncateAt.END
881 || (mMaximumVisibleLineCount == 1
882 && ellipsize != TextUtils.TruncateAt.MARQUEE));
Roozbeh Pournader9ea756f2017-07-25 11:20:29 -0700883 if (0 < remainingLineCount && remainingLineCount < breakCount
884 && ellipsisMayBeApplied) {
Keisuke Kuroyanagif4a3f3a2015-06-10 09:37:32 +0900885 // Calculate width and flag.
886 float width = 0;
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700887 int flag = 0; // XXX May need to also have starting hyphen edit
Keisuke Kuroyanagif4a3f3a2015-06-10 09:37:32 +0900888 for (int i = remainingLineCount - 1; i < breakCount; i++) {
Keisuke Kuroyanagi78f0d832016-05-10 12:21:33 -0700889 if (i == breakCount - 1) {
890 width += lineWidths[i];
891 } else {
892 for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) {
893 width += widths[j];
894 }
895 }
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700896 flag |= flags[i] & TAB_MASK;
Keisuke Kuroyanagif4a3f3a2015-06-10 09:37:32 +0900897 }
Keisuke Kuroyanagi78f0d832016-05-10 12:21:33 -0700898 // Treat the last line and overflowed lines as a single line.
899 breaks[remainingLineCount - 1] = breaks[breakCount - 1];
Keisuke Kuroyanagif4a3f3a2015-06-10 09:37:32 +0900900 lineWidths[remainingLineCount - 1] = width;
901 flags[remainingLineCount - 1] = flag;
902
903 breakCount = remainingLineCount;
904 }
905
Anish Athalyec8f9e622014-07-21 15:26:34 -0700906 // here is the offset of the starting character of the line we are currently measuring
907 int here = paraStart;
908
909 int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
910 int fmCacheIndex = 0;
911 int spanEndCacheIndex = 0;
912 int breakIndex = 0;
913 for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
914 // retrieve end of span
915 spanEnd = spanEndCache[spanEndCacheIndex++];
916
917 // retrieve cached metrics, order matches above
918 fm.top = fmCache[fmCacheIndex * 4 + 0];
919 fm.bottom = fmCache[fmCacheIndex * 4 + 1];
920 fm.ascent = fmCache[fmCacheIndex * 4 + 2];
921 fm.descent = fmCache[fmCacheIndex * 4 + 3];
922 fmCacheIndex++;
923
924 if (fm.top < fmTop) {
925 fmTop = fm.top;
926 }
927 if (fm.ascent < fmAscent) {
928 fmAscent = fm.ascent;
929 }
930 if (fm.descent > fmDescent) {
931 fmDescent = fm.descent;
932 }
933 if (fm.bottom > fmBottom) {
934 fmBottom = fm.bottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800935 }
936
Anish Athalyec8f9e622014-07-21 15:26:34 -0700937 // skip breaks ending before current span range
938 while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
939 breakIndex++;
940 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800941
Anish Athalyec8f9e622014-07-21 15:26:34 -0700942 while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
943 int endPos = paraStart + breaks[breakIndex];
944
Raph Levience4155a2015-03-11 11:02:33 -0700945 boolean moreChars = (endPos < bufEnd);
Raph Levien4c02e832014-12-12 11:17:01 -0800946
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700947 final int ascent = fallbackLineSpacing
Siyamed Sinira273a702017-10-05 11:22:12 -0700948 ? Math.min(fmAscent, Math.round(ascents[breakIndex]))
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700949 : fmAscent;
950 final int descent = fallbackLineSpacing
Siyamed Sinira273a702017-10-05 11:22:12 -0700951 ? Math.max(fmDescent, Math.round(descents[breakIndex]))
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700952 : fmDescent;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700953 v = out(source, here, endPos,
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700954 ascent, descent, fmTop, fmBottom,
Roozbeh Pournader431e5062015-10-16 02:27:03 -0700955 v, spacingmult, spacingadd, chooseHt, chooseHtv, fm, flags[breakIndex],
Anish Athalyec8f9e622014-07-21 15:26:34 -0700956 needMultiply, chdirs, dir, easy, bufEnd, includepad, trackpad,
Siyamed Sinir442c1512017-07-24 12:18:27 -0700957 addLastLineSpacing, chs, widths, paraStart, ellipsize,
958 ellipsizedWidth, lineWidths[breakIndex], paint, moreChars);
Anish Athalyec8f9e622014-07-21 15:26:34 -0700959
960 if (endPos < spanEnd) {
961 // preserve metrics for current span
962 fmTop = fm.top;
963 fmBottom = fm.bottom;
964 fmAscent = fm.ascent;
965 fmDescent = fm.descent;
966 } else {
967 fmTop = fmBottom = fmAscent = fmDescent = 0;
968 }
969
970 here = endPos;
971 breakIndex++;
972
Siyamed Sinir0745c722016-05-31 20:39:33 -0700973 if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) {
Anish Athalyec8f9e622014-07-21 15:26:34 -0700974 return;
975 }
976 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800977 }
978
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800979 if (paraEnd == bufEnd)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800980 break;
981 }
982
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700983 if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE) &&
Fabrice Di Meglioad0b0512011-10-04 17:21:26 -0700984 mLineCount < mMaximumVisibleLineCount) {
Raph Levien70616ec2015-03-04 10:41:30 -0800985 measured.setPara(source, bufEnd, bufEnd, textDir, b);
Fabrice Di Meglioe6318892013-06-18 20:03:41 -0700986
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800987 paint.getFontMetricsInt(fm);
988
989 v = out(source,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800990 bufEnd, bufEnd, fm.ascent, fm.descent,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800991 fm.top, fm.bottom,
992 v,
993 spacingmult, spacingadd, null,
Raph Levien26d443a2015-03-30 14:18:32 -0700994 null, fm, 0,
Fabrice Di Meglioe6318892013-06-18 20:03:41 -0700995 needMultiply, measured.mLevels, measured.mDir, measured.mEasy, bufEnd,
Siyamed Sinir442c1512017-07-24 12:18:27 -0700996 includepad, trackpad, addLastLineSpacing, null,
Gilles Debunned300e752011-10-17 13:37:36 -0700997 null, bufStart, ellipsize,
998 ellipsizedWidth, 0, paint, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800999 }
1000 }
1001
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001002 // The parameters that are not changed in the method are marked as final to make the code
1003 // easier to understand.
1004 private int out(final CharSequence text, final int start, final int end, int above, int below,
1005 int top, int bottom, int v, final float spacingmult, final float spacingadd,
1006 final LineHeightSpan[] chooseHt, final int[] chooseHtv, final Paint.FontMetricsInt fm,
1007 final int flags, final boolean needMultiply, final byte[] chdirs, final int dir,
1008 final boolean easy, final int bufEnd, final boolean includePad, final boolean trackPad,
1009 final boolean addLastLineLineSpacing, final char[] chs, final float[] widths,
1010 final int widthStart, final TextUtils.TruncateAt ellipsize, final float ellipsisWidth,
1011 final float textWidth, final TextPaint paint, final boolean moreChars) {
Siyamed Siniraf398512017-07-25 19:08:42 -07001012 final int j = mLineCount;
1013 final int off = j * mColumns;
1014 final int want = off + mColumns + TOP;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001015 int[] lines = mLines;
1016
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001017 if (want >= lines.length) {
Siyamed Siniraf398512017-07-25 19:08:42 -07001018 final int[] grow = ArrayUtils.newUnpaddedIntArray(GrowingArrayUtils.growSize(want));
Adam Lesinski776abc22014-03-07 11:30:59 -05001019 System.arraycopy(lines, 0, grow, 0, lines.length);
1020 mLines = grow;
1021 lines = grow;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001022 }
1023
Siyamed Siniraf398512017-07-25 19:08:42 -07001024 if (j >= mLineDirections.length) {
1025 final Directions[] grow = ArrayUtils.newUnpaddedArray(Directions.class,
1026 GrowingArrayUtils.growSize(j));
1027 System.arraycopy(mLineDirections, 0, grow, 0, mLineDirections.length);
1028 mLineDirections = grow;
1029 }
1030
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001031 lines[off + START] = start;
1032 lines[off + TOP] = v;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001033
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001034 // Information about hyphenation, tabs, and directions are needed for determining
1035 // ellipsization, so the values should be assigned before ellipsization.
Eric Fischera9f1dd02009-08-12 15:00:10 -07001036
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001037 // TODO: could move TAB to share same column as HYPHEN, simplifying this code and gaining
1038 // one bit for start field
1039 lines[off + TAB] |= flags & TAB_MASK;
1040 lines[off + HYPHEN] = flags;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001041
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001042 lines[off + DIR] |= dir << DIR_SHIFT;
1043 // easy means all chars < the first RTL, so no emoji, no nothing
1044 // XXX a run with no text or all spaces is easy but might be an empty
1045 // RTL paragraph. Make sure easy is false if this is the case.
1046 if (easy) {
1047 mLineDirections[j] = DIRS_ALL_LEFT_TO_RIGHT;
1048 } else {
1049 mLineDirections[j] = AndroidBidi.directions(dir, chdirs, start - widthStart, chs,
1050 start - widthStart, end - start);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001051 }
1052
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001053 final boolean firstLine = (j == 0);
1054 final boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);
Siyamed Sinir0745c722016-05-31 20:39:33 -07001055
1056 if (ellipsize != null) {
1057 // If there is only one line, then do any type of ellipsis except when it is MARQUEE
1058 // if there are multiple lines, just allow END ellipsis on the last line
1059 boolean forceEllipsis = moreChars && (mLineCount + 1 == mMaximumVisibleLineCount);
1060
1061 boolean doEllipsis =
1062 (((mMaximumVisibleLineCount == 1 && moreChars) || (firstLine && !moreChars)) &&
1063 ellipsize != TextUtils.TruncateAt.MARQUEE) ||
1064 (!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
1065 ellipsize == TextUtils.TruncateAt.END);
1066 if (doEllipsis) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001067 calculateEllipsis(text, start, end, widths, widthStart,
1068 ellipsisWidth - getTotalInsets(j), ellipsize, j,
1069 textWidth, paint, forceEllipsis, dir);
Siyamed Sinir0745c722016-05-31 20:39:33 -07001070 }
1071 }
1072
Siyamed Sinirfb0b2dc2017-07-26 22:08:24 -07001073 final boolean lastLine;
1074 if (mEllipsized) {
1075 lastLine = true;
1076 } else {
1077 final boolean lastCharIsNewLine = widthStart != bufEnd && bufEnd > 0
1078 && text.charAt(bufEnd - 1) == CHAR_NEW_LINE;
1079 if (end == bufEnd && !lastCharIsNewLine) {
1080 lastLine = true;
1081 } else if (start == bufEnd && lastCharIsNewLine) {
1082 lastLine = true;
1083 } else {
1084 lastLine = false;
1085 }
1086 }
Raph Leviend97b0972014-04-24 12:51:35 -07001087
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001088 if (chooseHt != null) {
1089 fm.ascent = above;
1090 fm.descent = below;
1091 fm.top = top;
1092 fm.bottom = bottom;
1093
1094 for (int i = 0; i < chooseHt.length; i++) {
1095 if (chooseHt[i] instanceof LineHeightSpan.WithDensity) {
1096 ((LineHeightSpan.WithDensity) chooseHt[i])
1097 .chooseHeight(text, start, end, chooseHtv[i], v, fm, paint);
1098
1099 } else {
1100 chooseHt[i].chooseHeight(text, start, end, chooseHtv[i], v, fm);
1101 }
1102 }
1103
1104 above = fm.ascent;
1105 below = fm.descent;
1106 top = fm.top;
1107 bottom = fm.bottom;
1108 }
1109
Raph Leviend97b0972014-04-24 12:51:35 -07001110 if (firstLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001111 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001112 mTopPadding = top - above;
1113 }
1114
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001115 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001116 above = top;
1117 }
1118 }
Raph Leviend97b0972014-04-24 12:51:35 -07001119
Raph Leviend97b0972014-04-24 12:51:35 -07001120 if (lastLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001121 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001122 mBottomPadding = bottom - below;
1123 }
1124
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001125 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001126 below = bottom;
1127 }
1128 }
1129
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001130 final int extra;
Siyamed Sinir442c1512017-07-24 12:18:27 -07001131 if (needMultiply && (addLastLineLineSpacing || !lastLine)) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001132 final double ex = (below - above) * (spacingmult - 1) + spacingadd;
Doug Felt10657582010-02-22 11:19:01 -08001133 if (ex >= 0) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001134 extra = (int)(ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -08001135 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001136 extra = -(int)(-ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -08001137 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001138 } else {
1139 extra = 0;
1140 }
1141
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001142 lines[off + DESCENT] = below + extra;
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001143 lines[off + EXTRA] = extra;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001144
Siyamed Sinir0745c722016-05-31 20:39:33 -07001145 // special case for non-ellipsized last visible line when maxLines is set
1146 // store the height as if it was ellipsized
1147 if (!mEllipsized && currentLineIsTheLastVisibleOne) {
1148 // below calculation as if it was the last line
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001149 final int maxLineBelow = includePad ? bottom : below;
Siyamed Sinir0745c722016-05-31 20:39:33 -07001150 // similar to the calculation of v below, without the extra.
1151 mMaxLineHeight = v + (maxLineBelow - above);
1152 }
1153
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001154 v += (below - above) + extra;
1155 lines[off + mColumns + START] = end;
1156 lines[off + mColumns + TOP] = v;
1157
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001158 mLineCount++;
1159 return v;
1160 }
1161
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001162 private void calculateEllipsis(CharSequence text, int lineStart, int lineEnd, float[] widths,
1163 int widthStart, float avail, TextUtils.TruncateAt where, int line, float textWidth,
1164 TextPaint paint, boolean forceEllipsis, int dir) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001165 if (textWidth <= avail && !forceEllipsis) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001166 // Everything fits!
1167 mLines[mColumns * line + ELLIPSIS_START] = 0;
1168 mLines[mColumns * line + ELLIPSIS_COUNT] = 0;
1169 return;
1170 }
1171
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001172 float tempAvail = avail;
1173 int numberOfTries = 0;
1174 boolean lineFits = false;
1175 mWorkPaint.set(paint);
1176 do {
1177 final float ellipsizedWidth = guessEllipsis(text, lineStart, lineEnd, widths,
Siyamed Sinira273a702017-10-05 11:22:12 -07001178 widthStart, tempAvail, where, line, mWorkPaint, forceEllipsis, dir);
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001179 if (ellipsizedWidth <= avail) {
1180 lineFits = true;
1181 } else {
1182 numberOfTries++;
1183 if (numberOfTries > 10) {
1184 // If the text still doesn't fit after ten tries, assume it will never fit and
1185 // ellipsize it all.
1186 mLines[mColumns * line + ELLIPSIS_START] = 0;
1187 mLines[mColumns * line + ELLIPSIS_COUNT] = lineEnd - lineStart;
1188 lineFits = true;
1189 } else {
1190 // Some side effect of ellipsization has caused the text to go over the
1191 // available width. Let's make the available width shorter by exactly that
1192 // amount and retry.
1193 tempAvail -= ellipsizedWidth - avail;
1194 }
1195 }
1196 } while (!lineFits);
1197 mEllipsized = true;
1198 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001199
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001200 // Returns the width of the ellipsized line which in some rare cases can actually be larger
1201 // than 'avail' (due to kerning or other context-based effect of replacement of text by
1202 // ellipsis). If all the line needs to ellipsized away, or it's an invalud hyphenation mode,
1203 // returns 0 so the caller can stop iterating.
1204 //
1205 // This method temporarily modifies the TextPaint passed to it, so the TextPaint passed to it
1206 // should not be accessed while the method is running.
1207 private float guessEllipsis(CharSequence text, int lineStart, int lineEnd, float[] widths,
Siyamed Sinira273a702017-10-05 11:22:12 -07001208 int widthStart, float avail, TextUtils.TruncateAt where, int line,
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001209 TextPaint paint, boolean forceEllipsis, int dir) {
1210 final int savedHyphenEdit = paint.getHyphenEdit();
1211 paint.setHyphenEdit(0);
1212 final float ellipsisWidth = paint.measureText(TextUtils.getEllipsisString(where));
1213 final int ellipsisStart;
1214 final int ellipsisCount;
1215 final int len = lineEnd - lineStart;
1216 final int offset = lineStart - widthStart;
1217
1218 int hyphen = getHyphen(line);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001219 // We only support start ellipsis on a single line
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001220 if (where == TextUtils.TruncateAt.START) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001221 if (mMaximumVisibleLineCount == 1) {
1222 float sum = 0;
1223 int i;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001224
Keisuke Kuroyanagied2eea12015-04-14 18:18:35 +09001225 for (i = len; i > 0; i--) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001226 final float w = widths[i - 1 + offset];
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001227 if (w + sum + ellipsisWidth > avail) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001228 while (i < len && widths[i + offset] == 0.0f) {
Keisuke Kuroyanagi82d1c442016-09-14 14:30:14 +09001229 i++;
1230 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001231 break;
1232 }
1233
1234 sum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001235 }
1236
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001237 ellipsisStart = 0;
1238 ellipsisCount = i;
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001239 // Strip the potential hyphenation at beginning of line.
1240 hyphen &= ~Paint.HYPHENEDIT_MASK_START_OF_LINE;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001241 } else {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001242 ellipsisStart = 0;
1243 ellipsisCount = 0;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001244 if (Log.isLoggable(TAG, Log.WARN)) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001245 Log.w(TAG, "Start ellipsis only supported with one line");
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001246 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001247 }
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001248 } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE ||
1249 where == TextUtils.TruncateAt.END_SMALL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001250 float sum = 0;
1251 int i;
1252
1253 for (i = 0; i < len; i++) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001254 final float w = widths[i + offset];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001255
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001256 if (w + sum + ellipsisWidth > avail) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001257 break;
1258 }
1259
1260 sum += w;
1261 }
1262
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001263 if (forceEllipsis && i == len && len > 0) {
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -07001264 ellipsisStart = len - 1;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001265 ellipsisCount = 1;
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001266 } else {
1267 ellipsisStart = i;
1268 ellipsisCount = len - i;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001269 }
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001270 // Strip the potential hyphenation at end of line.
1271 hyphen &= ~Paint.HYPHENEDIT_MASK_END_OF_LINE;
1272 } else { // where = TextUtils.TruncateAt.MIDDLE
1273 // We only support middle ellipsis on a single line.
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001274 if (mMaximumVisibleLineCount == 1) {
1275 float lsum = 0, rsum = 0;
1276 int left = 0, right = len;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001277
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001278 final float ravail = (avail - ellipsisWidth) / 2;
Raph Levien0e3c5e82014-12-04 13:26:07 -08001279 for (right = len; right > 0; right--) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001280 final float w = widths[right - 1 + offset];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001281
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001282 if (w + rsum > ravail) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001283 while (right < len && widths[right + offset] == 0.0f) {
Keisuke Kuroyanagi82d1c442016-09-14 14:30:14 +09001284 right++;
1285 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001286 break;
1287 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001288 rsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001289 }
1290
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001291 final float lavail = avail - ellipsisWidth - rsum;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001292 for (left = 0; left < right; left++) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001293 final float w = widths[left + offset];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001294
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001295 if (w + lsum > lavail) {
1296 break;
1297 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001298
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001299 lsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001300 }
1301
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001302 ellipsisStart = left;
1303 ellipsisCount = right - left;
1304 } else {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001305 ellipsisStart = 0;
1306 ellipsisCount = 0;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001307 if (Log.isLoggable(TAG, Log.WARN)) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001308 Log.w(TAG, "Middle ellipsis only supported with one line");
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001309 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001310 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001311 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001312 mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
1313 mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001314
1315 if (ellipsisStart == 0 && (ellipsisCount == 0 || ellipsisCount == len)) {
1316 // Unsupported ellipsization mode or all text is ellipsized away. Return 0.
1317 return 0.0f;
1318 }
1319
1320 final boolean isSpanned = text instanceof Spanned;
1321 final Ellipsizer ellipsizedText = isSpanned
1322 ? new SpannedEllipsizer(text)
1323 : new Ellipsizer(text);
1324 ellipsizedText.mLayout = this;
1325 ellipsizedText.mMethod = where;
1326
1327 final boolean hasTabs = getLineContainsTab(line);
1328 final TabStops tabStops;
1329 if (hasTabs && isSpanned) {
1330 final TabStopSpan[] tabs = getParagraphSpans((Spanned) ellipsizedText, lineStart,
1331 lineEnd, TabStopSpan.class);
1332 if (tabs.length == 0) {
1333 tabStops = null;
1334 } else {
1335 tabStops = new TabStops(TAB_INCREMENT, tabs);
1336 }
1337 } else {
1338 tabStops = null;
1339 }
1340 paint.setHyphenEdit(hyphen);
1341 final TextLine textline = TextLine.obtain();
1342 textline.set(paint, ellipsizedText, lineStart, lineEnd, dir, getLineDirections(line),
1343 hasTabs, tabStops);
1344 // Since TextLine.metric() returns negative values for RTL text, multiplication by dir
1345 // converts it to an actual width. Note that we don't want to use the absolute value,
1346 // since we may actually have glyphs with negative advances, which by definition always
1347 // fit.
1348 final float ellipsizedWidth = textline.metrics(null) * dir;
1349 TextLine.recycle(textline);
1350 paint.setHyphenEdit(savedHyphenEdit);
1351 return ellipsizedWidth;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001352 }
1353
Selim Cinek365ec092017-03-09 00:10:52 -08001354 private float getTotalInsets(int line) {
1355 int totalIndent = 0;
1356 if (mLeftIndents != null) {
1357 totalIndent = mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1358 }
1359 if (mRightIndents != null) {
1360 totalIndent += mRightIndents[Math.min(line, mRightIndents.length - 1)];
1361 }
1362 return totalIndent;
1363 }
1364
Doug Felte8e45f22010-03-29 14:58:40 -07001365 // Override the base class so we can directly access our members,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001366 // rather than relying on member functions.
1367 // The logic mirrors that of Layout.getLineForVertical
1368 // FIXME: It may be faster to do a linear search for layouts without many lines.
Gilles Debunne66111472010-11-19 11:04:37 -08001369 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001370 public int getLineForVertical(int vertical) {
1371 int high = mLineCount;
1372 int low = -1;
1373 int guess;
1374 int[] lines = mLines;
1375 while (high - low > 1) {
1376 guess = (high + low) >> 1;
1377 if (lines[mColumns * guess + TOP] > vertical){
1378 high = guess;
1379 } else {
1380 low = guess;
1381 }
1382 }
1383 if (low < 0) {
1384 return 0;
1385 } else {
1386 return low;
1387 }
1388 }
1389
Gilles Debunne66111472010-11-19 11:04:37 -08001390 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001391 public int getLineCount() {
1392 return mLineCount;
1393 }
1394
Gilles Debunne66111472010-11-19 11:04:37 -08001395 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001396 public int getLineTop(int line) {
Raph Levien07e6c232016-04-04 12:34:06 -07001397 return mLines[mColumns * line + TOP];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001398 }
1399
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001400 /**
1401 * @hide
1402 */
1403 @Override
1404 public int getLineExtra(int line) {
1405 return mLines[mColumns * line + EXTRA];
1406 }
1407
Gilles Debunne66111472010-11-19 11:04:37 -08001408 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001409 public int getLineDescent(int line) {
Raph Levien07e6c232016-04-04 12:34:06 -07001410 return mLines[mColumns * line + DESCENT];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001411 }
1412
Gilles Debunne66111472010-11-19 11:04:37 -08001413 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001414 public int getLineStart(int line) {
1415 return mLines[mColumns * line + START] & START_MASK;
1416 }
1417
Gilles Debunne66111472010-11-19 11:04:37 -08001418 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001419 public int getParagraphDirection(int line) {
1420 return mLines[mColumns * line + DIR] >> DIR_SHIFT;
1421 }
1422
Gilles Debunne66111472010-11-19 11:04:37 -08001423 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001424 public boolean getLineContainsTab(int line) {
1425 return (mLines[mColumns * line + TAB] & TAB_MASK) != 0;
1426 }
1427
Gilles Debunne66111472010-11-19 11:04:37 -08001428 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001429 public final Directions getLineDirections(int line) {
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001430 if (line > getLineCount()) {
1431 throw new ArrayIndexOutOfBoundsException();
1432 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001433 return mLineDirections[line];
1434 }
1435
Gilles Debunne66111472010-11-19 11:04:37 -08001436 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001437 public int getTopPadding() {
1438 return mTopPadding;
1439 }
1440
Gilles Debunne66111472010-11-19 11:04:37 -08001441 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001442 public int getBottomPadding() {
1443 return mBottomPadding;
1444 }
1445
Raph Levien26d443a2015-03-30 14:18:32 -07001446 /**
1447 * @hide
1448 */
1449 @Override
1450 public int getHyphen(int line) {
Keisuke Kuroyanagif5af4a32016-08-31 21:40:53 +09001451 return mLines[mColumns * line + HYPHEN] & HYPHEN_MASK;
Raph Levien26d443a2015-03-30 14:18:32 -07001452 }
1453
Raph Levien2ea52902015-07-01 14:39:31 -07001454 /**
1455 * @hide
1456 */
1457 @Override
1458 public int getIndentAdjust(int line, Alignment align) {
1459 if (align == Alignment.ALIGN_LEFT) {
1460 if (mLeftIndents == null) {
1461 return 0;
1462 } else {
1463 return mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1464 }
1465 } else if (align == Alignment.ALIGN_RIGHT) {
1466 if (mRightIndents == null) {
1467 return 0;
1468 } else {
1469 return -mRightIndents[Math.min(line, mRightIndents.length - 1)];
1470 }
1471 } else if (align == Alignment.ALIGN_CENTER) {
1472 int left = 0;
1473 if (mLeftIndents != null) {
1474 left = mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1475 }
1476 int right = 0;
1477 if (mRightIndents != null) {
1478 right = mRightIndents[Math.min(line, mRightIndents.length - 1)];
1479 }
1480 return (left - right) >> 1;
1481 } else {
1482 throw new AssertionError("unhandled alignment " + align);
1483 }
1484 }
1485
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001486 @Override
1487 public int getEllipsisCount(int line) {
1488 if (mColumns < COLUMNS_ELLIPSIZE) {
1489 return 0;
1490 }
1491
1492 return mLines[mColumns * line + ELLIPSIS_COUNT];
1493 }
1494
1495 @Override
1496 public int getEllipsisStart(int line) {
1497 if (mColumns < COLUMNS_ELLIPSIZE) {
1498 return 0;
1499 }
1500
1501 return mLines[mColumns * line + ELLIPSIS_START];
1502 }
1503
1504 @Override
1505 public int getEllipsizedWidth() {
1506 return mEllipsizedWidth;
1507 }
1508
Siyamed Sinir0745c722016-05-31 20:39:33 -07001509 /**
1510 * Return the total height of this layout.
1511 *
1512 * @param cap if true and max lines is set, returns the height of the layout at the max lines.
1513 *
1514 * @hide
1515 */
1516 public int getHeight(boolean cap) {
1517 if (cap && mLineCount >= mMaximumVisibleLineCount && mMaxLineHeight == -1 &&
1518 Log.isLoggable(TAG, Log.WARN)) {
1519 Log.w(TAG, "maxLineHeight should not be -1. "
1520 + " maxLines:" + mMaximumVisibleLineCount
1521 + " lineCount:" + mLineCount);
1522 }
1523
1524 return cap && mLineCount >= mMaximumVisibleLineCount && mMaxLineHeight != -1 ?
1525 mMaxLineHeight : super.getHeight();
1526 }
1527
Raph Levien70616ec2015-03-04 10:41:30 -08001528 private static native long nNewBuilder();
1529 private static native void nFreeBuilder(long nativePtr);
1530 private static native void nFinishBuilder(long nativePtr);
Raph Levien26d443a2015-03-30 14:18:32 -07001531
Raph Levienc94f7422015-03-06 19:19:48 -08001532 // Set up paragraph text and settings; done as one big method to minimize jni crossings
Seigo Nonakabafe1972017-08-24 15:30:29 -07001533 private static native void nSetupParagraph(
Seigo Nonaka5d518dc2017-08-31 14:01:54 -07001534 /* non zero */ long nativePtr, @NonNull char[] text, @IntRange(from = 0) int length,
Seigo Nonakabafe1972017-08-24 15:30:29 -07001535 @FloatRange(from = 0.0f) float firstWidth, @IntRange(from = 0) int firstWidthLineCount,
1536 @FloatRange(from = 0.0f) float restWidth, @Nullable int[] variableTabStops,
1537 int defaultTabStop, @BreakStrategy int breakStrategy,
1538 @HyphenationFrequency int hyphenationFrequency, boolean isJustified,
Roozbeh Pournader9a9179d2017-08-29 14:14:28 -07001539 @Nullable int[] indents, @Nullable int[] leftPaddings, @Nullable int[] rightPaddings,
1540 @IntRange(from = 0) int indentsOffset);
Raph Levien70616ec2015-03-04 10:41:30 -08001541
Seigo Nonaka5cc93482017-10-02 16:01:14 -07001542 private static native void nAddStyleRun(
Seigo Nonaka1bee3332017-09-27 18:37:37 -07001543 /* non-zero */ long nativePtr, /* non-zero */ long nativePaint,
Seigo Nonaka5d518dc2017-08-31 14:01:54 -07001544 @IntRange(from = 0) int start, @IntRange(from = 0) int end, boolean isRtl,
1545 @Nullable String languageTags, @Nullable long[] hyphenators);
Raph Levien70616ec2015-03-04 10:41:30 -08001546
Seigo Nonaka1bee3332017-09-27 18:37:37 -07001547 private static native void nAddReplacementRun(/* non-zero */ long nativePtr,
1548 @IntRange(from = 0) int start, @IntRange(from = 0) int end,
1549 @FloatRange(from = 0.0f) float width, @Nullable String languageTags,
1550 @Nullable long[] hyphenators);
Raph Levien70616ec2015-03-04 10:41:30 -08001551
1552 private static native void nGetWidths(long nativePtr, float[] widths);
1553
Anish Athalyec8f9e622014-07-21 15:26:34 -07001554 // populates LineBreaks and returns the number of breaks found
1555 //
1556 // the arrays inside the LineBreaks objects are passed in as well
1557 // to reduce the number of JNI calls in the common case where the
1558 // arrays do not have to be resized
Raph Levienc94f7422015-03-06 19:19:48 -08001559 private static native int nComputeLineBreaks(long nativePtr, LineBreaks recycle,
Roozbeh Pournader737dfea2017-08-10 11:32:24 -07001560 int[] recycleBreaks, float[] recycleWidths, float[] recycleAscents,
1561 float[] recycleDescents, int[] recycleFlags, int recycleLength);
Anish Athalye88b5b0b2014-06-24 14:39:43 -07001562
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001563 private int mLineCount;
1564 private int mTopPadding, mBottomPadding;
1565 private int mColumns;
1566 private int mEllipsizedWidth;
1567
Siyamed Sinir0745c722016-05-31 20:39:33 -07001568 /**
1569 * Keeps track if ellipsize is applied to the text.
1570 */
1571 private boolean mEllipsized;
1572
1573 /**
1574 * If maxLines is set, ellipsize is not set, and the actual line count of text is greater than
1575 * or equal to maxLine, this variable holds the ideal visual height of the maxLine'th line
1576 * starting from the top of the layout. If maxLines is not set its value will be -1.
1577 *
1578 * The value is the same as getLineTop(maxLines) for ellipsized version where structurally no
1579 * more than maxLines is contained.
1580 */
Siyamed Sinira19cd512017-08-03 22:01:56 -07001581 private int mMaxLineHeight = DEFAULT_MAX_LINE_HEIGHT;
Siyamed Sinir0745c722016-05-31 20:39:33 -07001582
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001583 private TextPaint mWorkPaint = new TextPaint();
1584
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001585 private static final int COLUMNS_NORMAL = 5;
1586 private static final int COLUMNS_ELLIPSIZE = 7;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001587 private static final int START = 0;
1588 private static final int DIR = START;
1589 private static final int TAB = START;
1590 private static final int TOP = 1;
1591 private static final int DESCENT = 2;
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001592 private static final int EXTRA = 3;
1593 private static final int HYPHEN = 4;
1594 private static final int ELLIPSIS_START = 5;
1595 private static final int ELLIPSIS_COUNT = 6;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001596
1597 private int[] mLines;
1598 private Directions[] mLineDirections;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001599 private int mMaximumVisibleLineCount = Integer.MAX_VALUE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001600
1601 private static final int START_MASK = 0x1FFFFFFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001602 private static final int DIR_SHIFT = 30;
1603 private static final int TAB_MASK = 0x20000000;
Keisuke Kuroyanagif5af4a32016-08-31 21:40:53 +09001604 private static final int HYPHEN_MASK = 0xFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001605
Doug Feltc982f602010-05-25 11:51:40 -07001606 private static final int TAB_INCREMENT = 20; // same as Layout, but that's private
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001607
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001608 private static final char CHAR_NEW_LINE = '\n';
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001609
1610 private static final double EXTRA_ROUNDING = 0.5;
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001611
Siyamed Sinira19cd512017-08-03 22:01:56 -07001612 private static final int DEFAULT_MAX_LINE_HEIGHT = -1;
1613
Anish Athalyec8f9e622014-07-21 15:26:34 -07001614 // This is used to return three arrays from a single JNI call when
1615 // performing line breaking
Deepanshu Gupta70539192014-10-15 15:57:40 -07001616 /*package*/ static class LineBreaks {
Anish Athalyec8f9e622014-07-21 15:26:34 -07001617 private static final int INITIAL_SIZE = 16;
1618 public int[] breaks = new int[INITIAL_SIZE];
1619 public float[] widths = new float[INITIAL_SIZE];
Roozbeh Pournader737dfea2017-08-10 11:32:24 -07001620 public float[] ascents = new float[INITIAL_SIZE];
1621 public float[] descents = new float[INITIAL_SIZE];
Roozbeh Pournader112d9c72015-08-07 12:44:41 -07001622 public int[] flags = new int[INITIAL_SIZE]; // hasTab
Anish Athalyec8f9e622014-07-21 15:26:34 -07001623 // breaks, widths, and flags should all have the same length
1624 }
1625
Seigo Nonakabafe1972017-08-24 15:30:29 -07001626 @Nullable private int[] mLeftIndents;
1627 @Nullable private int[] mRightIndents;
Roozbeh Pournader9a9179d2017-08-29 14:14:28 -07001628 @Nullable private int[] mLeftPaddings;
1629 @Nullable private int[] mRightPaddings;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001630}