blob: 299bde239fcfe4c42e7dbd67645c1b90d907eb87 [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 Nonakabeafa1f2018-02-01 21:39:24 -0800654 PrecomputedText measured = null;
Seigo Nonaka4e90fa22018-02-13 21:40:01 +0000655 final Spanned spanned;
Seigo Nonakabeafa1f2018-02-01 21:39:24 -0800656 if (source instanceof PrecomputedText) {
657 measured = (PrecomputedText) source;
658 if (!measured.canUseMeasuredResult(bufStart, bufEnd, textDir, paint, b.mBreakStrategy,
659 b.mHyphenationFrequency)) {
660 // Some parameters are different from the ones when measured text is created.
661 measured = null;
Seigo Nonakafbe63bd2017-12-02 19:28:05 -0800662 }
Seigo Nonaka87b15472018-01-12 14:06:29 -0800663 }
664
Seigo Nonakabeafa1f2018-02-01 21:39:24 -0800665 if (measured == null) {
666 final PrecomputedText.Params param = new PrecomputedText.Params(paint, textDir,
667 b.mBreakStrategy, b.mHyphenationFrequency);
668 measured = PrecomputedText.createWidthOnly(source, param, bufStart, bufEnd);
Seigo Nonaka4e90fa22018-02-13 21:40:01 +0000669 spanned = (source instanceof Spanned) ? (Spanned) source : null;
670 } else {
671 final CharSequence original = measured.getText();
672 spanned = (original instanceof Spanned) ? (Spanned) original : null;
Seigo Nonaka4e90fa22018-02-13 21:40:01 +0000673 }
Seigo Nonakafbe63bd2017-12-02 19:28:05 -0800674
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700675 try {
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800676 for (int paraIndex = 0; paraIndex < measured.getParagraphCount(); paraIndex++) {
677 final int paraStart = measured.getParagraphStart(paraIndex);
678 final int paraEnd = measured.getParagraphEnd(paraIndex);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800679
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700680 int firstWidthLineCount = 1;
681 int firstWidth = outerWidth;
682 int restWidth = outerWidth;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800683
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700684 LineHeightSpan[] chooseHt = null;
Doug Feltcb3791202011-07-07 11:57:48 -0700685
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700686 if (spanned != null) {
687 LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
688 LeadingMarginSpan.class);
689 for (int i = 0; i < sp.length; i++) {
690 LeadingMarginSpan lms = sp[i];
691 firstWidth -= sp[i].getLeadingMargin(true);
692 restWidth -= sp[i].getLeadingMargin(false);
693
694 // LeadingMarginSpan2 is odd. The count affects all
695 // leading margin spans, not just this particular one
696 if (lms instanceof LeadingMarginSpan2) {
697 LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
698 firstWidthLineCount = Math.max(firstWidthLineCount,
699 lms2.getLeadingMarginLineCount());
700 }
701 }
702
703 chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
704
705 if (chooseHt.length == 0) {
706 chooseHt = null; // So that out() would not assume it has any contents
707 } else {
708 if (chooseHtv == null || chooseHtv.length < chooseHt.length) {
709 chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
710 }
711
712 for (int i = 0; i < chooseHt.length; i++) {
713 int o = spanned.getSpanStart(chooseHt[i]);
714
715 if (o < paraStart) {
716 // starts in this layout, before the
717 // current paragraph
718
719 chooseHtv[i] = getLineTop(getLineForOffset(o));
720 } else {
721 // starts in this paragraph
722
723 chooseHtv[i] = v;
724 }
725 }
Mark Wagner7b5676e2009-10-16 11:44:23 -0700726 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800727 }
728
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700729 // tab stop locations
730 int[] variableTabStops = null;
731 if (spanned != null) {
732 TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
733 paraEnd, TabStopSpan.class);
734 if (spans.length > 0) {
735 int[] stops = new int[spans.length];
736 for (int i = 0; i < spans.length; i++) {
737 stops[i] = spans[i].getTabStop();
738 }
739 Arrays.sort(stops, 0, stops.length);
740 variableTabStops = stops;
741 }
742 }
743
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800744 final MeasuredParagraph measuredPara = measured.getMeasuredParagraph(paraIndex);
745 final char[] chs = measuredPara.getChars();
746 final int[] spanEndCache = measuredPara.getSpanEndCache().getRawArray();
747 final int[] fmCache = measuredPara.getFontMetrics().getRawArray();
Seigo Nonaka6f1868b2017-12-01 16:24:19 -0800748 // TODO: Stop keeping duplicated width copy in native and Java.
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800749 widths.resize(chs.length);
750
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700751 // measurement has to be done before performing line breaking
752 // but we don't want to recompute fontmetrics or span ranges the
753 // second time, so we cache those and then use those stored values
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700754
755 int breakCount = nComputeLineBreaks(
756 nativePtr,
757
758 // Inputs
759 chs,
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800760 measuredPara.getNativePtr(),
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700761 paraEnd - paraStart,
762 firstWidth,
763 firstWidthLineCount,
764 restWidth,
765 variableTabStops,
766 TAB_INCREMENT,
767 mLineCount,
768
769 // Outputs
770 lineBreaks,
771 lineBreaks.breaks.length,
772 lineBreaks.breaks,
773 lineBreaks.widths,
774 lineBreaks.ascents,
775 lineBreaks.descents,
776 lineBreaks.flags,
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800777 widths.getRawArray());
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700778
779 final int[] breaks = lineBreaks.breaks;
780 final float[] lineWidths = lineBreaks.widths;
781 final float[] ascents = lineBreaks.ascents;
782 final float[] descents = lineBreaks.descents;
783 final int[] flags = lineBreaks.flags;
784
785 final int remainingLineCount = mMaximumVisibleLineCount - mLineCount;
786 final boolean ellipsisMayBeApplied = ellipsize != null
787 && (ellipsize == TextUtils.TruncateAt.END
788 || (mMaximumVisibleLineCount == 1
789 && ellipsize != TextUtils.TruncateAt.MARQUEE));
790 if (0 < remainingLineCount && remainingLineCount < breakCount
791 && ellipsisMayBeApplied) {
792 // Calculate width and flag.
793 float width = 0;
794 int flag = 0; // XXX May need to also have starting hyphen edit
795 for (int i = remainingLineCount - 1; i < breakCount; i++) {
796 if (i == breakCount - 1) {
797 width += lineWidths[i];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800798 } else {
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700799 for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) {
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800800 width += widths.get(j);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700801 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800802 }
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700803 flag |= flags[i] & TAB_MASK;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800804 }
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700805 // Treat the last line and overflowed lines as a single line.
806 breaks[remainingLineCount - 1] = breaks[breakCount - 1];
807 lineWidths[remainingLineCount - 1] = width;
808 flags[remainingLineCount - 1] = flag;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800809
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700810 breakCount = remainingLineCount;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700811 }
812
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700813 // here is the offset of the starting character of the line we are currently
814 // measuring
815 int here = paraStart;
Doug Felte8e45f22010-03-29 14:58:40 -0700816
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700817 int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
818 int fmCacheIndex = 0;
819 int spanEndCacheIndex = 0;
820 int breakIndex = 0;
821 for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
822 // retrieve end of span
823 spanEnd = spanEndCache[spanEndCacheIndex++];
Doug Felt23241882010-06-02 14:41:06 -0700824
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700825 // retrieve cached metrics, order matches above
826 fm.top = fmCache[fmCacheIndex * 4 + 0];
827 fm.bottom = fmCache[fmCacheIndex * 4 + 1];
828 fm.ascent = fmCache[fmCacheIndex * 4 + 2];
829 fm.descent = fmCache[fmCacheIndex * 4 + 3];
830 fmCacheIndex++;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800831
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700832 if (fm.top < fmTop) {
Anish Athalyec8f9e622014-07-21 15:26:34 -0700833 fmTop = fm.top;
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700834 }
835 if (fm.ascent < fmAscent) {
Anish Athalyec8f9e622014-07-21 15:26:34 -0700836 fmAscent = fm.ascent;
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700837 }
838 if (fm.descent > fmDescent) {
Anish Athalyec8f9e622014-07-21 15:26:34 -0700839 fmDescent = fm.descent;
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700840 }
841 if (fm.bottom > fmBottom) {
842 fmBottom = fm.bottom;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700843 }
844
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700845 // skip breaks ending before current span range
846 while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
847 breakIndex++;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700848 }
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700849
850 while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
851 int endPos = paraStart + breaks[breakIndex];
852
853 boolean moreChars = (endPos < bufEnd);
854
855 final int ascent = fallbackLineSpacing
856 ? Math.min(fmAscent, Math.round(ascents[breakIndex]))
857 : fmAscent;
858 final int descent = fallbackLineSpacing
859 ? Math.max(fmDescent, Math.round(descents[breakIndex]))
860 : fmDescent;
861 v = out(source, here, endPos,
862 ascent, descent, fmTop, fmBottom,
863 v, spacingmult, spacingadd, chooseHt, chooseHtv, fm,
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800864 flags[breakIndex], needMultiply, measuredPara, bufEnd,
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800865 includepad, trackpad, addLastLineSpacing, chs, widths.getRawArray(),
866 paraStart, ellipsize, ellipsizedWidth, lineWidths[breakIndex],
867 paint, moreChars);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700868
869 if (endPos < spanEnd) {
870 // preserve metrics for current span
871 fmTop = fm.top;
872 fmBottom = fm.bottom;
873 fmAscent = fm.ascent;
874 fmDescent = fm.descent;
875 } else {
876 fmTop = fmBottom = fmAscent = fmDescent = 0;
877 }
878
879 here = endPos;
880 breakIndex++;
881
882 if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) {
883 return;
884 }
885 }
886 }
887
888 if (paraEnd == bufEnd) {
889 break;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700890 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800891 }
892
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700893 if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE)
894 && mLineCount < mMaximumVisibleLineCount) {
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800895 final MeasuredParagraph measuredPara =
896 MeasuredParagraph.buildForBidi(source, bufEnd, bufEnd, textDir, null);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700897 paint.getFontMetricsInt(fm);
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700898 v = out(source,
899 bufEnd, bufEnd, fm.ascent, fm.descent,
900 fm.top, fm.bottom,
901 v,
902 spacingmult, spacingadd, null,
903 null, fm, 0,
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800904 needMultiply, measuredPara, bufEnd,
Seigo Nonakab90e08f2017-10-20 15:23:51 -0700905 includepad, trackpad, addLastLineSpacing, null,
906 null, bufStart, ellipsize,
907 ellipsizedWidth, 0, paint, false);
908 }
909 } finally {
910 nFinish(nativePtr);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800911 }
912 }
913
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700914 private int out(final CharSequence text, final int start, final int end, int above, int below,
915 int top, int bottom, int v, final float spacingmult, final float spacingadd,
916 final LineHeightSpan[] chooseHt, final int[] chooseHtv, final Paint.FontMetricsInt fm,
Seigo Nonaka9d3bd082018-01-11 10:02:12 -0800917 final int flags, final boolean needMultiply, @NonNull final MeasuredParagraph measured,
Seigo Nonakaf1644f72017-11-27 22:09:49 -0800918 final int bufEnd, final boolean includePad, final boolean trackPad,
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700919 final boolean addLastLineLineSpacing, final char[] chs, final float[] widths,
920 final int widthStart, final TextUtils.TruncateAt ellipsize, final float ellipsisWidth,
921 final float textWidth, final TextPaint paint, final boolean moreChars) {
Siyamed Siniraf398512017-07-25 19:08:42 -0700922 final int j = mLineCount;
923 final int off = j * mColumns;
924 final int want = off + mColumns + TOP;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800925 int[] lines = mLines;
Seigo Nonaka4b6bcee2017-12-11 11:39:51 -0800926 final int dir = measured.getParagraphDir();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800927
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800928 if (want >= lines.length) {
Siyamed Siniraf398512017-07-25 19:08:42 -0700929 final int[] grow = ArrayUtils.newUnpaddedIntArray(GrowingArrayUtils.growSize(want));
Adam Lesinski776abc22014-03-07 11:30:59 -0500930 System.arraycopy(lines, 0, grow, 0, lines.length);
931 mLines = grow;
932 lines = grow;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800933 }
934
Siyamed Siniraf398512017-07-25 19:08:42 -0700935 if (j >= mLineDirections.length) {
936 final Directions[] grow = ArrayUtils.newUnpaddedArray(Directions.class,
937 GrowingArrayUtils.growSize(j));
938 System.arraycopy(mLineDirections, 0, grow, 0, mLineDirections.length);
939 mLineDirections = grow;
940 }
941
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800942 if (chooseHt != null) {
943 fm.ascent = above;
944 fm.descent = below;
945 fm.top = top;
946 fm.bottom = bottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800947
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800948 for (int i = 0; i < chooseHt.length; i++) {
949 if (chooseHt[i] instanceof LineHeightSpan.WithDensity) {
950 ((LineHeightSpan.WithDensity) chooseHt[i])
951 .chooseHeight(text, start, end, chooseHtv[i], v, fm, paint);
952 } else {
953 chooseHt[i].chooseHeight(text, start, end, chooseHtv[i], v, fm);
954 }
955 }
Eric Fischera9f1dd02009-08-12 15:00:10 -0700956
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800957 above = fm.ascent;
958 below = fm.descent;
959 top = fm.top;
960 bottom = fm.bottom;
961 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800962
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800963 boolean firstLine = (j == 0);
964 boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);
Siyamed Sinir0745c722016-05-31 20:39:33 -0700965
966 if (ellipsize != null) {
967 // If there is only one line, then do any type of ellipsis except when it is MARQUEE
968 // if there are multiple lines, just allow END ellipsis on the last line
969 boolean forceEllipsis = moreChars && (mLineCount + 1 == mMaximumVisibleLineCount);
970
971 boolean doEllipsis =
972 (((mMaximumVisibleLineCount == 1 && moreChars) || (firstLine && !moreChars)) &&
973 ellipsize != TextUtils.TruncateAt.MARQUEE) ||
974 (!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
975 ellipsize == TextUtils.TruncateAt.END);
976 if (doEllipsis) {
Siyamed Sinir1a5648a2018-01-23 16:40:06 -0800977 calculateEllipsis(start, end, widths, widthStart,
978 ellipsisWidth, ellipsize, j,
979 textWidth, paint, forceEllipsis);
Siyamed Sinir0745c722016-05-31 20:39:33 -0700980 }
981 }
982
Siyamed Sinirfb0b2dc2017-07-26 22:08:24 -0700983 final boolean lastLine;
984 if (mEllipsized) {
985 lastLine = true;
986 } else {
987 final boolean lastCharIsNewLine = widthStart != bufEnd && bufEnd > 0
988 && text.charAt(bufEnd - 1) == CHAR_NEW_LINE;
989 if (end == bufEnd && !lastCharIsNewLine) {
990 lastLine = true;
991 } else if (start == bufEnd && lastCharIsNewLine) {
992 lastLine = true;
993 } else {
994 lastLine = false;
995 }
996 }
Raph Leviend97b0972014-04-24 12:51:35 -0700997
998 if (firstLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800999 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001000 mTopPadding = top - above;
1001 }
1002
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001003 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001004 above = top;
1005 }
1006 }
Raph Leviend97b0972014-04-24 12:51:35 -07001007
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001008 int extra;
1009
Raph Leviend97b0972014-04-24 12:51:35 -07001010 if (lastLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001011 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001012 mBottomPadding = bottom - below;
1013 }
1014
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001015 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001016 below = bottom;
1017 }
1018 }
1019
Siyamed Sinir442c1512017-07-24 12:18:27 -07001020 if (needMultiply && (addLastLineLineSpacing || !lastLine)) {
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001021 double ex = (below - above) * (spacingmult - 1) + spacingadd;
Doug Felt10657582010-02-22 11:19:01 -08001022 if (ex >= 0) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001023 extra = (int)(ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -08001024 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001025 extra = -(int)(-ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -08001026 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001027 } else {
1028 extra = 0;
1029 }
1030
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001031 lines[off + START] = start;
1032 lines[off + TOP] = v;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001033 lines[off + DESCENT] = below + extra;
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001034 lines[off + EXTRA] = extra;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001035
Siyamed Sinir0745c722016-05-31 20:39:33 -07001036 // special case for non-ellipsized last visible line when maxLines is set
1037 // store the height as if it was ellipsized
1038 if (!mEllipsized && currentLineIsTheLastVisibleOne) {
1039 // below calculation as if it was the last line
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001040 int maxLineBelow = includePad ? bottom : below;
Siyamed Sinir0745c722016-05-31 20:39:33 -07001041 // similar to the calculation of v below, without the extra.
1042 mMaxLineHeight = v + (maxLineBelow - above);
1043 }
1044
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001045 v += (below - above) + extra;
1046 lines[off + mColumns + START] = end;
1047 lines[off + mColumns + TOP] = v;
1048
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001049 // TODO: could move TAB to share same column as HYPHEN, simplifying this code and gaining
1050 // one bit for start field
1051 lines[off + TAB] |= flags & TAB_MASK;
1052 lines[off + HYPHEN] = flags;
1053 lines[off + DIR] |= dir << DIR_SHIFT;
1054 mLineDirections[j] = measured.getDirections(start - widthStart, end - widthStart);
1055
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001056 mLineCount++;
1057 return v;
1058 }
1059
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001060 private void calculateEllipsis(int lineStart, int lineEnd,
1061 float[] widths, int widthStart,
1062 float avail, TextUtils.TruncateAt where,
1063 int line, float textWidth, TextPaint paint,
1064 boolean forceEllipsis) {
1065 avail -= getTotalInsets(line);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001066 if (textWidth <= avail && !forceEllipsis) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001067 // Everything fits!
1068 mLines[mColumns * line + ELLIPSIS_START] = 0;
1069 mLines[mColumns * line + ELLIPSIS_COUNT] = 0;
1070 return;
1071 }
1072
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001073 float ellipsisWidth = paint.measureText(TextUtils.getEllipsisString(where));
1074 int ellipsisStart = 0;
1075 int ellipsisCount = 0;
1076 int len = lineEnd - lineStart;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001077
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001078 // We only support start ellipsis on a single line
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001079 if (where == TextUtils.TruncateAt.START) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001080 if (mMaximumVisibleLineCount == 1) {
1081 float sum = 0;
1082 int i;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001083
Keisuke Kuroyanagied2eea12015-04-14 18:18:35 +09001084 for (i = len; i > 0; i--) {
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001085 float w = widths[i - 1 + lineStart - widthStart];
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001086 if (w + sum + ellipsisWidth > avail) {
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001087 while (i < len && widths[i + lineStart - widthStart] == 0.0f) {
Keisuke Kuroyanagi82d1c442016-09-14 14:30:14 +09001088 i++;
1089 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001090 break;
1091 }
1092
1093 sum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001094 }
1095
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001096 ellipsisStart = 0;
1097 ellipsisCount = i;
1098 } else {
1099 if (Log.isLoggable(TAG, Log.WARN)) {
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001100 Log.w(TAG, "Start Ellipsis only supported with one line");
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001101 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001102 }
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001103 } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE ||
1104 where == TextUtils.TruncateAt.END_SMALL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001105 float sum = 0;
1106 int i;
1107
1108 for (i = 0; i < len; i++) {
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001109 float w = widths[i + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001110
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001111 if (w + sum + ellipsisWidth > avail) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001112 break;
1113 }
1114
1115 sum += w;
1116 }
1117
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001118 ellipsisStart = i;
1119 ellipsisCount = len - i;
1120 if (forceEllipsis && ellipsisCount == 0 && len > 0) {
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -07001121 ellipsisStart = len - 1;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001122 ellipsisCount = 1;
1123 }
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001124 } else {
1125 // where = TextUtils.TruncateAt.MIDDLE We only support middle ellipsis on a single line
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001126 if (mMaximumVisibleLineCount == 1) {
1127 float lsum = 0, rsum = 0;
1128 int left = 0, right = len;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001129
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001130 float ravail = (avail - ellipsisWidth) / 2;
Raph Levien0e3c5e82014-12-04 13:26:07 -08001131 for (right = len; right > 0; right--) {
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001132 float w = widths[right - 1 + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001133
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001134 if (w + rsum > ravail) {
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001135 while (right < len && widths[right + lineStart - widthStart] == 0.0f) {
Keisuke Kuroyanagi82d1c442016-09-14 14:30:14 +09001136 right++;
1137 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001138 break;
1139 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001140 rsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001141 }
1142
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001143 float lavail = avail - ellipsisWidth - rsum;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001144 for (left = 0; left < right; left++) {
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001145 float w = widths[left + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001146
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001147 if (w + lsum > lavail) {
1148 break;
1149 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001150
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001151 lsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001152 }
1153
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001154 ellipsisStart = left;
1155 ellipsisCount = right - left;
1156 } else {
1157 if (Log.isLoggable(TAG, Log.WARN)) {
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001158 Log.w(TAG, "Middle Ellipsis only supported with one line");
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001159 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001160 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001161 }
Siyamed Sinir1a5648a2018-01-23 16:40:06 -08001162 mEllipsized = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001163 mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
1164 mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
1165 }
1166
Selim Cinek365ec092017-03-09 00:10:52 -08001167 private float getTotalInsets(int line) {
1168 int totalIndent = 0;
1169 if (mLeftIndents != null) {
1170 totalIndent = mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1171 }
1172 if (mRightIndents != null) {
1173 totalIndent += mRightIndents[Math.min(line, mRightIndents.length - 1)];
1174 }
1175 return totalIndent;
1176 }
1177
Doug Felte8e45f22010-03-29 14:58:40 -07001178 // Override the base class so we can directly access our members,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001179 // rather than relying on member functions.
1180 // The logic mirrors that of Layout.getLineForVertical
1181 // FIXME: It may be faster to do a linear search for layouts without many lines.
Gilles Debunne66111472010-11-19 11:04:37 -08001182 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001183 public int getLineForVertical(int vertical) {
1184 int high = mLineCount;
1185 int low = -1;
1186 int guess;
1187 int[] lines = mLines;
1188 while (high - low > 1) {
1189 guess = (high + low) >> 1;
1190 if (lines[mColumns * guess + TOP] > vertical){
1191 high = guess;
1192 } else {
1193 low = guess;
1194 }
1195 }
1196 if (low < 0) {
1197 return 0;
1198 } else {
1199 return low;
1200 }
1201 }
1202
Gilles Debunne66111472010-11-19 11:04:37 -08001203 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001204 public int getLineCount() {
1205 return mLineCount;
1206 }
1207
Gilles Debunne66111472010-11-19 11:04:37 -08001208 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001209 public int getLineTop(int line) {
Raph Levien07e6c232016-04-04 12:34:06 -07001210 return mLines[mColumns * line + TOP];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001211 }
1212
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001213 /**
1214 * @hide
1215 */
1216 @Override
1217 public int getLineExtra(int line) {
1218 return mLines[mColumns * line + EXTRA];
1219 }
1220
Gilles Debunne66111472010-11-19 11:04:37 -08001221 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001222 public int getLineDescent(int line) {
Raph Levien07e6c232016-04-04 12:34:06 -07001223 return mLines[mColumns * line + DESCENT];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001224 }
1225
Gilles Debunne66111472010-11-19 11:04:37 -08001226 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001227 public int getLineStart(int line) {
1228 return mLines[mColumns * line + START] & START_MASK;
1229 }
1230
Gilles Debunne66111472010-11-19 11:04:37 -08001231 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001232 public int getParagraphDirection(int line) {
1233 return mLines[mColumns * line + DIR] >> DIR_SHIFT;
1234 }
1235
Gilles Debunne66111472010-11-19 11:04:37 -08001236 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001237 public boolean getLineContainsTab(int line) {
1238 return (mLines[mColumns * line + TAB] & TAB_MASK) != 0;
1239 }
1240
Gilles Debunne66111472010-11-19 11:04:37 -08001241 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001242 public final Directions getLineDirections(int line) {
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001243 if (line > getLineCount()) {
1244 throw new ArrayIndexOutOfBoundsException();
1245 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001246 return mLineDirections[line];
1247 }
1248
Gilles Debunne66111472010-11-19 11:04:37 -08001249 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001250 public int getTopPadding() {
1251 return mTopPadding;
1252 }
1253
Gilles Debunne66111472010-11-19 11:04:37 -08001254 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001255 public int getBottomPadding() {
1256 return mBottomPadding;
1257 }
1258
Raph Levien26d443a2015-03-30 14:18:32 -07001259 /**
1260 * @hide
1261 */
1262 @Override
1263 public int getHyphen(int line) {
Keisuke Kuroyanagif5af4a32016-08-31 21:40:53 +09001264 return mLines[mColumns * line + HYPHEN] & HYPHEN_MASK;
Raph Levien26d443a2015-03-30 14:18:32 -07001265 }
1266
Raph Levien2ea52902015-07-01 14:39:31 -07001267 /**
1268 * @hide
1269 */
1270 @Override
1271 public int getIndentAdjust(int line, Alignment align) {
1272 if (align == Alignment.ALIGN_LEFT) {
1273 if (mLeftIndents == null) {
1274 return 0;
1275 } else {
1276 return mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1277 }
1278 } else if (align == Alignment.ALIGN_RIGHT) {
1279 if (mRightIndents == null) {
1280 return 0;
1281 } else {
1282 return -mRightIndents[Math.min(line, mRightIndents.length - 1)];
1283 }
1284 } else if (align == Alignment.ALIGN_CENTER) {
1285 int left = 0;
1286 if (mLeftIndents != null) {
1287 left = mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1288 }
1289 int right = 0;
1290 if (mRightIndents != null) {
1291 right = mRightIndents[Math.min(line, mRightIndents.length - 1)];
1292 }
1293 return (left - right) >> 1;
1294 } else {
1295 throw new AssertionError("unhandled alignment " + align);
1296 }
1297 }
1298
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001299 @Override
1300 public int getEllipsisCount(int line) {
1301 if (mColumns < COLUMNS_ELLIPSIZE) {
1302 return 0;
1303 }
1304
1305 return mLines[mColumns * line + ELLIPSIS_COUNT];
1306 }
1307
1308 @Override
1309 public int getEllipsisStart(int line) {
1310 if (mColumns < COLUMNS_ELLIPSIZE) {
1311 return 0;
1312 }
1313
1314 return mLines[mColumns * line + ELLIPSIS_START];
1315 }
1316
1317 @Override
1318 public int getEllipsizedWidth() {
1319 return mEllipsizedWidth;
1320 }
1321
Siyamed Sinir0745c722016-05-31 20:39:33 -07001322 /**
1323 * Return the total height of this layout.
1324 *
1325 * @param cap if true and max lines is set, returns the height of the layout at the max lines.
1326 *
1327 * @hide
1328 */
1329 public int getHeight(boolean cap) {
1330 if (cap && mLineCount >= mMaximumVisibleLineCount && mMaxLineHeight == -1 &&
1331 Log.isLoggable(TAG, Log.WARN)) {
1332 Log.w(TAG, "maxLineHeight should not be -1. "
1333 + " maxLines:" + mMaximumVisibleLineCount
1334 + " lineCount:" + mLineCount);
1335 }
1336
1337 return cap && mLineCount >= mMaximumVisibleLineCount && mMaxLineHeight != -1 ?
1338 mMaxLineHeight : super.getHeight();
1339 }
1340
Seigo Nonakab90e08f2017-10-20 15:23:51 -07001341 @FastNative
1342 private static native long nInit(
1343 @BreakStrategy int breakStrategy,
1344 @HyphenationFrequency int hyphenationFrequency,
1345 boolean isJustified,
1346 @Nullable int[] indents,
1347 @Nullable int[] leftPaddings,
1348 @Nullable int[] rightPaddings);
1349
1350 @CriticalNative
1351 private static native void nFinish(long nativePtr);
1352
Anish Athalyec8f9e622014-07-21 15:26:34 -07001353 // populates LineBreaks and returns the number of breaks found
1354 //
1355 // the arrays inside the LineBreaks objects are passed in as well
1356 // to reduce the number of JNI calls in the common case where the
1357 // arrays do not have to be resized
Seigo Nonaka2dd1a552017-10-06 11:41:00 -07001358 // The individual character widths will be returned in charWidths. The length of charWidths must
1359 // be at least the length of the text.
Seigo Nonakab90e08f2017-10-20 15:23:51 -07001360 private static native int nComputeLineBreaks(
1361 /* non zero */ long nativePtr,
1362
1363 // Inputs
1364 @NonNull char[] text,
Seigo Nonaka6f1868b2017-12-01 16:24:19 -08001365 /* Non Zero */ long measuredTextPtr,
Seigo Nonakab90e08f2017-10-20 15:23:51 -07001366 @IntRange(from = 0) int length,
1367 @FloatRange(from = 0.0f) float firstWidth,
1368 @IntRange(from = 0) int firstWidthLineCount,
1369 @FloatRange(from = 0.0f) float restWidth,
1370 @Nullable int[] variableTabStops,
1371 int defaultTabStop,
1372 @IntRange(from = 0) int indentsOffset,
1373
1374 // Outputs
1375 @NonNull LineBreaks recycle,
1376 @IntRange(from = 0) int recycleLength,
1377 @NonNull int[] recycleBreaks,
1378 @NonNull float[] recycleWidths,
1379 @NonNull float[] recycleAscents,
1380 @NonNull float[] recycleDescents,
1381 @NonNull int[] recycleFlags,
1382 @NonNull float[] charWidths);
Anish Athalye88b5b0b2014-06-24 14:39:43 -07001383
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001384 private int mLineCount;
1385 private int mTopPadding, mBottomPadding;
1386 private int mColumns;
1387 private int mEllipsizedWidth;
1388
Siyamed Sinir0745c722016-05-31 20:39:33 -07001389 /**
1390 * Keeps track if ellipsize is applied to the text.
1391 */
1392 private boolean mEllipsized;
1393
1394 /**
1395 * If maxLines is set, ellipsize is not set, and the actual line count of text is greater than
1396 * or equal to maxLine, this variable holds the ideal visual height of the maxLine'th line
1397 * starting from the top of the layout. If maxLines is not set its value will be -1.
1398 *
1399 * The value is the same as getLineTop(maxLines) for ellipsized version where structurally no
1400 * more than maxLines is contained.
1401 */
Siyamed Sinira19cd512017-08-03 22:01:56 -07001402 private int mMaxLineHeight = DEFAULT_MAX_LINE_HEIGHT;
Siyamed Sinir0745c722016-05-31 20:39:33 -07001403
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001404 private static final int COLUMNS_NORMAL = 5;
1405 private static final int COLUMNS_ELLIPSIZE = 7;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001406 private static final int START = 0;
1407 private static final int DIR = START;
1408 private static final int TAB = START;
1409 private static final int TOP = 1;
1410 private static final int DESCENT = 2;
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001411 private static final int EXTRA = 3;
1412 private static final int HYPHEN = 4;
1413 private static final int ELLIPSIS_START = 5;
1414 private static final int ELLIPSIS_COUNT = 6;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001415
1416 private int[] mLines;
1417 private Directions[] mLineDirections;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001418 private int mMaximumVisibleLineCount = Integer.MAX_VALUE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001419
1420 private static final int START_MASK = 0x1FFFFFFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001421 private static final int DIR_SHIFT = 30;
1422 private static final int TAB_MASK = 0x20000000;
Keisuke Kuroyanagif5af4a32016-08-31 21:40:53 +09001423 private static final int HYPHEN_MASK = 0xFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001424
Doug Feltc982f602010-05-25 11:51:40 -07001425 private static final int TAB_INCREMENT = 20; // same as Layout, but that's private
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001426
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001427 private static final char CHAR_NEW_LINE = '\n';
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001428
1429 private static final double EXTRA_ROUNDING = 0.5;
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001430
Siyamed Sinira19cd512017-08-03 22:01:56 -07001431 private static final int DEFAULT_MAX_LINE_HEIGHT = -1;
1432
Anish Athalyec8f9e622014-07-21 15:26:34 -07001433 // This is used to return three arrays from a single JNI call when
1434 // performing line breaking
Deepanshu Gupta70539192014-10-15 15:57:40 -07001435 /*package*/ static class LineBreaks {
Anish Athalyec8f9e622014-07-21 15:26:34 -07001436 private static final int INITIAL_SIZE = 16;
1437 public int[] breaks = new int[INITIAL_SIZE];
1438 public float[] widths = new float[INITIAL_SIZE];
Roozbeh Pournader737dfea2017-08-10 11:32:24 -07001439 public float[] ascents = new float[INITIAL_SIZE];
1440 public float[] descents = new float[INITIAL_SIZE];
Roozbeh Pournader112d9c72015-08-07 12:44:41 -07001441 public int[] flags = new int[INITIAL_SIZE]; // hasTab
Anish Athalyec8f9e622014-07-21 15:26:34 -07001442 // breaks, widths, and flags should all have the same length
1443 }
1444
Seigo Nonakabafe1972017-08-24 15:30:29 -07001445 @Nullable private int[] mLeftIndents;
1446 @Nullable private int[] mRightIndents;
Roozbeh Pournader9a9179d2017-08-29 14:14:28 -07001447 @Nullable private int[] mLeftPaddings;
1448 @Nullable private int[] mRightPaddings;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001449}