blob: 9ebf04dd6b33d80e67c105272709d8e94c40b15a [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
Raph Levien531c30c2015-04-30 16:29:59 -070019import android.annotation.Nullable;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080020import android.graphics.Paint;
Roozbeh Pournaderef7cfa12017-06-15 12:39:04 -070021import android.os.LocaleList;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080022import android.text.style.LeadingMarginSpan;
Gilles Debunne66111472010-11-19 11:04:37 -080023import android.text.style.LeadingMarginSpan.LeadingMarginSpan2;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080024import android.text.style.LineHeightSpan;
25import android.text.style.MetricAffectingSpan;
Doug Feltc982f602010-05-25 11:51:40 -070026import android.text.style.TabStopSpan;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -070027import android.util.Log;
Raph Levien39b4db72015-03-25 13:18:20 -070028import android.util.Pools.SynchronizedPool;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080029
Doug Feltcb3791202011-07-07 11:57:48 -070030import com.android.internal.util.ArrayUtils;
Adam Lesinski776abc22014-03-07 11:30:59 -050031import com.android.internal.util.GrowingArrayUtils;
Doug Feltcb3791202011-07-07 11:57:48 -070032
Raph Levien091dba22015-08-31 16:21:20 -070033import java.nio.ByteBuffer;
Anish Athalyec8f9e622014-07-21 15:26:34 -070034import java.util.Arrays;
Raph Levien4c1f12e2015-03-02 16:29:23 -080035import java.util.Locale;
Anish Athalyec8f9e622014-07-21 15:26:34 -070036
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080037/**
38 * StaticLayout is a Layout for text that will not be edited after it
39 * is laid out. Use {@link DynamicLayout} for text that may change.
40 * <p>This is used by widgets to control text layout. You should not need
41 * to use this class directly unless you are implementing your own widget
42 * or custom display object, or would be tempted to call
Doug Felt4e0c5e52010-03-15 16:56:02 -070043 * {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int,
44 * float, float, android.graphics.Paint)
45 * Canvas.drawText()} directly.</p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080046 */
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -080047public class StaticLayout extends Layout {
48
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -070049 static final String TAG = "StaticLayout";
50
Raph Leviend3ab6922015-03-02 14:30:53 -080051 /**
Raph Levien531c30c2015-04-30 16:29:59 -070052 * Builder for static layouts. The builder is a newer pattern for constructing
53 * StaticLayout objects and should be preferred over the constructors,
54 * particularly to access newer features. To build a static layout, first
55 * call {@link #obtain} with the required arguments (text, paint, and width),
56 * then call setters for optional parameters, and finally {@link #build}
57 * to build the StaticLayout object. Parameters not explicitly set will get
58 * default values.
Raph Leviend3ab6922015-03-02 14:30:53 -080059 */
60 public final static class Builder {
Raph Levien4c1f12e2015-03-02 16:29:23 -080061 private Builder() {
62 mNativePtr = nNewBuilder();
63 }
64
Raph Levien531c30c2015-04-30 16:29:59 -070065 /**
66 * Obtain a builder for constructing StaticLayout objects
67 *
68 * @param source The text to be laid out, optionally with spans
69 * @param start The index of the start of the text
70 * @param end The index + 1 of the end of the text
71 * @param paint The base paint used for layout
72 * @param width The width in pixels
73 * @return a builder object used for constructing the StaticLayout
74 */
Raph Levienebd66ca2015-04-30 15:27:57 -070075 public static Builder obtain(CharSequence source, int start, int end, TextPaint paint,
76 int width) {
Raph Levien39b4db72015-03-25 13:18:20 -070077 Builder b = sPool.acquire();
Raph Leviend3ab6922015-03-02 14:30:53 -080078 if (b == null) {
79 b = new Builder();
80 }
81
82 // set default initial values
Raph Levien39b4db72015-03-25 13:18:20 -070083 b.mText = source;
84 b.mStart = start;
85 b.mEnd = end;
Raph Levienebd66ca2015-04-30 15:27:57 -070086 b.mPaint = paint;
Raph Levien39b4db72015-03-25 13:18:20 -070087 b.mWidth = width;
88 b.mAlignment = Alignment.ALIGN_NORMAL;
Raph Leviend3ab6922015-03-02 14:30:53 -080089 b.mTextDir = TextDirectionHeuristics.FIRSTSTRONG_LTR;
90 b.mSpacingMult = 1.0f;
91 b.mSpacingAdd = 0.0f;
92 b.mIncludePad = true;
Raph Levien39b4db72015-03-25 13:18:20 -070093 b.mEllipsizedWidth = width;
Raph Leviend3ab6922015-03-02 14:30:53 -080094 b.mEllipsize = null;
95 b.mMaxLines = Integer.MAX_VALUE;
Raph Levien3bd60c72015-05-06 14:26:35 -070096 b.mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
Roozbeh Pournader95c7a132015-05-12 12:01:06 -070097 b.mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
Seigo Nonaka4b4730d2017-03-31 09:42:16 -070098 b.mJustificationMode = Layout.JUSTIFICATION_MODE_NONE;
Raph Leviend3ab6922015-03-02 14:30:53 -080099
100 b.mMeasuredText = MeasuredText.obtain();
101 return b;
102 }
103
Raph Levien39b4db72015-03-25 13:18:20 -0700104 private static void recycle(Builder b) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800105 b.mPaint = null;
106 b.mText = null;
107 MeasuredText.recycle(b.mMeasuredText);
Raph Levien3bd60c72015-05-06 14:26:35 -0700108 b.mMeasuredText = null;
Raph Levien2ea52902015-07-01 14:39:31 -0700109 b.mLeftIndents = null;
110 b.mRightIndents = null;
Raph Levien3bd60c72015-05-06 14:26:35 -0700111 nFinishBuilder(b.mNativePtr);
Raph Levien39b4db72015-03-25 13:18:20 -0700112 sPool.release(b);
Raph Leviend3ab6922015-03-02 14:30:53 -0800113 }
114
115 // release any expensive state
116 /* package */ void finish() {
Raph Levien4c1f12e2015-03-02 16:29:23 -0800117 nFinishBuilder(mNativePtr);
Raph Levien22ba7862015-07-29 12:34:13 -0700118 mText = null;
119 mPaint = null;
120 mLeftIndents = null;
121 mRightIndents = null;
Raph Leviend3ab6922015-03-02 14:30:53 -0800122 mMeasuredText.finish();
123 }
124
125 public Builder setText(CharSequence source) {
126 return setText(source, 0, source.length());
127 }
128
Raph Levien531c30c2015-04-30 16:29:59 -0700129 /**
130 * Set the text. Only useful when re-using the builder, which is done for
131 * the internal implementation of {@link DynamicLayout} but not as part
132 * of normal {@link StaticLayout} usage.
133 *
134 * @param source The text to be laid out, optionally with spans
135 * @param start The index of the start of the text
136 * @param end The index + 1 of the end of the text
137 * @return this builder, useful for chaining
138 *
139 * @hide
140 */
Raph Leviend3ab6922015-03-02 14:30:53 -0800141 public Builder setText(CharSequence source, int start, int end) {
142 mText = source;
143 mStart = start;
144 mEnd = end;
145 return this;
146 }
147
Raph Levien531c30c2015-04-30 16:29:59 -0700148 /**
149 * Set the paint. Internal for reuse cases only.
150 *
151 * @param paint The base paint used for layout
152 * @return this builder, useful for chaining
153 *
154 * @hide
155 */
Raph Leviend3ab6922015-03-02 14:30:53 -0800156 public Builder setPaint(TextPaint paint) {
157 mPaint = paint;
158 return this;
159 }
160
Raph Levien531c30c2015-04-30 16:29:59 -0700161 /**
162 * Set the width. Internal for reuse cases only.
163 *
164 * @param width The width in pixels
165 * @return this builder, useful for chaining
166 *
167 * @hide
168 */
Raph Leviend3ab6922015-03-02 14:30:53 -0800169 public Builder setWidth(int width) {
170 mWidth = width;
171 if (mEllipsize == null) {
172 mEllipsizedWidth = width;
173 }
174 return this;
175 }
176
Raph Levien531c30c2015-04-30 16:29:59 -0700177 /**
178 * Set the alignment. The default is {@link Layout.Alignment#ALIGN_NORMAL}.
179 *
180 * @param alignment Alignment for the resulting {@link StaticLayout}
181 * @return this builder, useful for chaining
182 */
Raph Levien39b4db72015-03-25 13:18:20 -0700183 public Builder setAlignment(Alignment alignment) {
184 mAlignment = alignment;
185 return this;
186 }
187
Raph Levien531c30c2015-04-30 16:29:59 -0700188 /**
189 * Set the text direction heuristic. The text direction heuristic is used to
190 * resolve text direction based per-paragraph based on the input text. The default is
191 * {@link TextDirectionHeuristics#FIRSTSTRONG_LTR}.
192 *
193 * @param textDir text direction heuristic for resolving BiDi behavior.
194 * @return this builder, useful for chaining
195 */
Raph Leviena6a08282015-06-03 13:20:45 -0700196 public Builder setTextDirection(TextDirectionHeuristic textDir) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800197 mTextDir = textDir;
198 return this;
199 }
200
Raph Levien531c30c2015-04-30 16:29:59 -0700201 /**
202 * Set line spacing parameters. The default is 0.0 for {@code spacingAdd}
203 * and 1.0 for {@code spacingMult}.
204 *
205 * @param spacingAdd line spacing add
206 * @param spacingMult line spacing multiplier
207 * @return this builder, useful for chaining
208 * @see android.widget.TextView#setLineSpacing
209 */
210 public Builder setLineSpacing(float spacingAdd, float spacingMult) {
211 mSpacingAdd = spacingAdd;
Raph Leviend3ab6922015-03-02 14:30:53 -0800212 mSpacingMult = spacingMult;
213 return this;
214 }
215
Raph Levien531c30c2015-04-30 16:29:59 -0700216 /**
217 * Set whether to include extra space beyond font ascent and descent (which is
218 * needed to avoid clipping in some languages, such as Arabic and Kannada). The
219 * default is {@code true}.
220 *
221 * @param includePad whether to include padding
222 * @return this builder, useful for chaining
223 * @see android.widget.TextView#setIncludeFontPadding
224 */
Raph Leviend3ab6922015-03-02 14:30:53 -0800225 public Builder setIncludePad(boolean includePad) {
226 mIncludePad = includePad;
227 return this;
228 }
229
Raph Levien531c30c2015-04-30 16:29:59 -0700230 /**
231 * Set the width as used for ellipsizing purposes, if it differs from the
232 * normal layout width. The default is the {@code width}
233 * passed to {@link #obtain}.
234 *
235 * @param ellipsizedWidth width used for ellipsizing, in pixels
236 * @return this builder, useful for chaining
237 * @see android.widget.TextView#setEllipsize
238 */
Raph Leviend3ab6922015-03-02 14:30:53 -0800239 public Builder setEllipsizedWidth(int ellipsizedWidth) {
240 mEllipsizedWidth = ellipsizedWidth;
241 return this;
242 }
243
Raph Levien531c30c2015-04-30 16:29:59 -0700244 /**
245 * Set ellipsizing on the layout. Causes words that are longer than the view
246 * is wide, or exceeding the number of lines (see #setMaxLines) in the case
247 * of {@link android.text.TextUtils.TruncateAt#END} or
248 * {@link android.text.TextUtils.TruncateAt#MARQUEE}, to be ellipsized instead
249 * of broken. The default is
250 * {@code null}, indicating no ellipsis is to be applied.
251 *
252 * @param ellipsize type of ellipsis behavior
253 * @return this builder, useful for chaining
254 * @see android.widget.TextView#setEllipsize
255 */
256 public Builder setEllipsize(@Nullable TextUtils.TruncateAt ellipsize) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800257 mEllipsize = ellipsize;
258 return this;
259 }
260
Raph Levien531c30c2015-04-30 16:29:59 -0700261 /**
262 * Set maximum number of lines. This is particularly useful in the case of
263 * ellipsizing, where it changes the layout of the last line. The default is
264 * unlimited.
265 *
266 * @param maxLines maximum number of lines in the layout
267 * @return this builder, useful for chaining
268 * @see android.widget.TextView#setMaxLines
269 */
Raph Leviend3ab6922015-03-02 14:30:53 -0800270 public Builder setMaxLines(int maxLines) {
271 mMaxLines = maxLines;
272 return this;
273 }
274
Raph Levien531c30c2015-04-30 16:29:59 -0700275 /**
276 * Set break strategy, useful for selecting high quality or balanced paragraph
277 * layout options. The default is {@link Layout#BREAK_STRATEGY_SIMPLE}.
278 *
279 * @param breakStrategy break strategy for paragraph layout
280 * @return this builder, useful for chaining
281 * @see android.widget.TextView#setBreakStrategy
282 */
Raph Levien39b4db72015-03-25 13:18:20 -0700283 public Builder setBreakStrategy(@BreakStrategy int breakStrategy) {
284 mBreakStrategy = breakStrategy;
285 return this;
286 }
287
Raph Levien531c30c2015-04-30 16:29:59 -0700288 /**
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700289 * Set hyphenation frequency, to control the amount of automatic hyphenation used. The
290 * default is {@link Layout#HYPHENATION_FREQUENCY_NONE}.
291 *
292 * @param hyphenationFrequency hyphenation frequency for the paragraph
293 * @return this builder, useful for chaining
294 * @see android.widget.TextView#setHyphenationFrequency
295 */
296 public Builder setHyphenationFrequency(@HyphenationFrequency int hyphenationFrequency) {
297 mHyphenationFrequency = hyphenationFrequency;
298 return this;
299 }
300
301 /**
Raph Levien531c30c2015-04-30 16:29:59 -0700302 * Set indents. Arguments are arrays holding an indent amount, one per line, measured in
303 * pixels. For lines past the last element in the array, the last element repeats.
304 *
305 * @param leftIndents array of indent values for left margin, in pixels
306 * @param rightIndents array of indent values for right margin, in pixels
307 * @return this builder, useful for chaining
Raph Levien531c30c2015-04-30 16:29:59 -0700308 */
Raph Leviene319d5a2015-04-14 23:51:07 -0700309 public Builder setIndents(int[] leftIndents, int[] rightIndents) {
Raph Levien2ea52902015-07-01 14:39:31 -0700310 mLeftIndents = leftIndents;
311 mRightIndents = rightIndents;
Raph Leviene319d5a2015-04-14 23:51:07 -0700312 int leftLen = leftIndents == null ? 0 : leftIndents.length;
313 int rightLen = rightIndents == null ? 0 : rightIndents.length;
314 int[] indents = new int[Math.max(leftLen, rightLen)];
315 for (int i = 0; i < indents.length; i++) {
316 int leftMargin = i < leftLen ? leftIndents[i] : 0;
317 int rightMargin = i < rightLen ? rightIndents[i] : 0;
318 indents[i] = leftMargin + rightMargin;
319 }
320 nSetIndents(mNativePtr, indents);
321 return this;
322 }
323
Raph Levien70616ec2015-03-04 10:41:30 -0800324 /**
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700325 * Set paragraph justification mode. The default value is
326 * {@link Layout#JUSTIFICATION_MODE_NONE}. If the last line is too short for justification,
327 * the last line will be displayed with the alignment set by {@link #setAlignment}.
Seigo Nonaka09da71a2016-11-28 16:24:14 +0900328 *
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700329 * @param justificationMode justification mode for the paragraph.
Seigo Nonaka09da71a2016-11-28 16:24:14 +0900330 * @return this builder, useful for chaining.
331 */
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700332 public Builder setJustificationMode(@JustificationMode int justificationMode) {
333 mJustificationMode = justificationMode;
Seigo Nonaka09da71a2016-11-28 16:24:14 +0900334 return this;
335 }
336
Siyamed Sinir442c1512017-07-24 12:18:27 -0700337 /**
338 * Sets whether the line spacing should be applied for the last line. Default value is
339 * {@code false}.
340 *
341 * @hide
342 */
343 /* package */ Builder setAddLastLineLineSpacing(boolean value) {
344 mAddLastLineLineSpacing = value;
345 return this;
346 }
347
Roozbeh Pournaderef7cfa12017-06-15 12:39:04 -0700348 private long[] getHyphenators(LocaleList locales) {
349 final int length = locales.size();
350 final long[] result = new long[length];
351 for (int i = 0; i < length; i++) {
352 final Locale locale = locales.get(i);
353 result[i] = Hyphenator.get(locale).getNativePtr();
354 }
355 return result;
356 }
357
Seigo Nonaka09da71a2016-11-28 16:24:14 +0900358 /**
Raph Levien70616ec2015-03-04 10:41:30 -0800359 * Measurement and break iteration is done in native code. The protocol for using
360 * the native code is as follows.
361 *
Raph Levien26d443a2015-03-30 14:18:32 -0700362 * For each paragraph, do a nSetupParagraph, which sets paragraph text, line width, tab
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700363 * stops, break strategy, and hyphenation frequency (and possibly other parameters in the
364 * future).
Raph Levienc94f7422015-03-06 19:19:48 -0800365 *
366 * Then, for each run within the paragraph:
Roozbeh Pournaderef7cfa12017-06-15 12:39:04 -0700367 * - setLocales (this must be done at least for the first run, optional afterwards)
Raph Levien70616ec2015-03-04 10:41:30 -0800368 * - one of the following, depending on the type of run:
369 * + addStyleRun (a text run, to be measured in native code)
370 * + addMeasuredRun (a run already measured in Java, passed into native code)
371 * + addReplacementRun (a replacement run, width is given)
372 *
373 * After measurement, nGetWidths() is valid if the widths are needed (eg for ellipsis).
374 * Run nComputeLineBreaks() to obtain line breaks for the paragraph.
375 *
376 * After all paragraphs, call finish() to release expensive buffers.
377 */
378
Roozbeh Pournaderef7cfa12017-06-15 12:39:04 -0700379 private void setLocales(LocaleList locales) {
380 if (!locales.equals(mLocales)) {
381 nSetLocales(mNativePtr, locales.toLanguageTags(), getHyphenators(locales));
382 mLocales = locales;
Raph Levien4c1f12e2015-03-02 16:29:23 -0800383 }
384 }
385
Raph Levien70616ec2015-03-04 10:41:30 -0800386 /* package */ float addStyleRun(TextPaint paint, int start, int end, boolean isRtl) {
Roozbeh Pournaderef7cfa12017-06-15 12:39:04 -0700387 setLocales(paint.getTextLocales());
Raph Levien70616ec2015-03-04 10:41:30 -0800388 return nAddStyleRun(mNativePtr, paint.getNativeInstance(), paint.mNativeTypeface,
389 start, end, isRtl);
390 }
391
392 /* package */ void addMeasuredRun(int start, int end, float[] widths) {
393 nAddMeasuredRun(mNativePtr, start, end, widths);
394 }
395
396 /* package */ void addReplacementRun(int start, int end, float width) {
397 nAddReplacementRun(mNativePtr, start, end, width);
398 }
399
Raph Levien531c30c2015-04-30 16:29:59 -0700400 /**
401 * Build the {@link StaticLayout} after options have been set.
402 *
403 * <p>Note: the builder object must not be reused in any way after calling this
404 * method. Setting parameters after calling this method, or calling it a second
405 * time on the same builder object, will likely lead to unexpected results.
406 *
407 * @return the newly constructed {@link StaticLayout} object
408 */
Raph Leviend3ab6922015-03-02 14:30:53 -0800409 public StaticLayout build() {
Raph Levien39b4db72015-03-25 13:18:20 -0700410 StaticLayout result = new StaticLayout(this);
411 Builder.recycle(this);
Raph Leviend3ab6922015-03-02 14:30:53 -0800412 return result;
413 }
414
Raph Levien4c1f12e2015-03-02 16:29:23 -0800415 @Override
416 protected void finalize() throws Throwable {
417 try {
418 nFreeBuilder(mNativePtr);
419 } finally {
420 super.finalize();
421 }
422 }
423
424 /* package */ long mNativePtr;
425
Raph Leviend3ab6922015-03-02 14:30:53 -0800426 CharSequence mText;
427 int mStart;
428 int mEnd;
429 TextPaint mPaint;
430 int mWidth;
Raph Levien39b4db72015-03-25 13:18:20 -0700431 Alignment mAlignment;
Raph Leviend3ab6922015-03-02 14:30:53 -0800432 TextDirectionHeuristic mTextDir;
433 float mSpacingMult;
434 float mSpacingAdd;
435 boolean mIncludePad;
436 int mEllipsizedWidth;
437 TextUtils.TruncateAt mEllipsize;
438 int mMaxLines;
Raph Levien39b4db72015-03-25 13:18:20 -0700439 int mBreakStrategy;
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700440 int mHyphenationFrequency;
Raph Levien2ea52902015-07-01 14:39:31 -0700441 int[] mLeftIndents;
442 int[] mRightIndents;
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700443 int mJustificationMode;
Siyamed Sinir442c1512017-07-24 12:18:27 -0700444 boolean mAddLastLineLineSpacing;
Raph Leviend3ab6922015-03-02 14:30:53 -0800445
446 Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
447
448 // This will go away and be subsumed by native builder code
449 MeasuredText mMeasuredText;
450
Roozbeh Pournaderef7cfa12017-06-15 12:39:04 -0700451 LocaleList mLocales;
Raph Levien4c1f12e2015-03-02 16:29:23 -0800452
Raph Levien39b4db72015-03-25 13:18:20 -0700453 private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<Builder>(3);
Raph Leviend3ab6922015-03-02 14:30:53 -0800454 }
455
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800456 public StaticLayout(CharSequence source, TextPaint paint,
457 int width,
458 Alignment align, float spacingmult, float spacingadd,
459 boolean includepad) {
460 this(source, 0, source.length(), paint, width, align,
461 spacingmult, spacingadd, includepad);
462 }
463
Doug Feltcb3791202011-07-07 11:57:48 -0700464 /**
465 * @hide
466 */
467 public StaticLayout(CharSequence source, TextPaint paint,
468 int width, Alignment align, TextDirectionHeuristic textDir,
469 float spacingmult, float spacingadd,
470 boolean includepad) {
471 this(source, 0, source.length(), paint, width, align, textDir,
472 spacingmult, spacingadd, includepad);
473 }
474
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800475 public StaticLayout(CharSequence source, int bufstart, int bufend,
476 TextPaint paint, int outerwidth,
477 Alignment align,
478 float spacingmult, float spacingadd,
479 boolean includepad) {
480 this(source, bufstart, bufend, paint, outerwidth, align,
481 spacingmult, spacingadd, includepad, null, 0);
482 }
483
Doug Feltcb3791202011-07-07 11:57:48 -0700484 /**
485 * @hide
486 */
487 public StaticLayout(CharSequence source, int bufstart, int bufend,
488 TextPaint paint, int outerwidth,
489 Alignment align, TextDirectionHeuristic textDir,
490 float spacingmult, float spacingadd,
491 boolean includepad) {
492 this(source, bufstart, bufend, paint, outerwidth, align, textDir,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700493 spacingmult, spacingadd, includepad, null, 0, Integer.MAX_VALUE);
Doug Feltcb3791202011-07-07 11:57:48 -0700494}
495
496 public StaticLayout(CharSequence source, int bufstart, int bufend,
497 TextPaint paint, int outerwidth,
498 Alignment align,
499 float spacingmult, float spacingadd,
500 boolean includepad,
501 TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
502 this(source, bufstart, bufend, paint, outerwidth, align,
503 TextDirectionHeuristics.FIRSTSTRONG_LTR,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700504 spacingmult, spacingadd, includepad, ellipsize, ellipsizedWidth, Integer.MAX_VALUE);
Doug Feltcb3791202011-07-07 11:57:48 -0700505 }
506
507 /**
508 * @hide
509 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800510 public StaticLayout(CharSequence source, int bufstart, int bufend,
511 TextPaint paint, int outerwidth,
Doug Feltcb3791202011-07-07 11:57:48 -0700512 Alignment align, TextDirectionHeuristic textDir,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800513 float spacingmult, float spacingadd,
514 boolean includepad,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700515 TextUtils.TruncateAt ellipsize, int ellipsizedWidth, int maxLines) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800516 super((ellipsize == null)
Doug Felt4e0c5e52010-03-15 16:56:02 -0700517 ? source
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800518 : (source instanceof Spanned)
519 ? new SpannedEllipsizer(source)
520 : new Ellipsizer(source),
Doug Feltcb3791202011-07-07 11:57:48 -0700521 paint, outerwidth, align, textDir, spacingmult, spacingadd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800522
Raph Levienebd66ca2015-04-30 15:27:57 -0700523 Builder b = Builder.obtain(source, bufstart, bufend, paint, outerwidth)
Raph Levien39b4db72015-03-25 13:18:20 -0700524 .setAlignment(align)
Raph Leviena6a08282015-06-03 13:20:45 -0700525 .setTextDirection(textDir)
Raph Levien531c30c2015-04-30 16:29:59 -0700526 .setLineSpacing(spacingadd, spacingmult)
Raph Leviend3ab6922015-03-02 14:30:53 -0800527 .setIncludePad(includepad)
528 .setEllipsizedWidth(ellipsizedWidth)
529 .setEllipsize(ellipsize)
530 .setMaxLines(maxLines);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800531 /*
532 * This is annoying, but we can't refer to the layout until
533 * superclass construction is finished, and the superclass
534 * constructor wants the reference to the display text.
Doug Felt4e0c5e52010-03-15 16:56:02 -0700535 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800536 * This will break if the superclass constructor ever actually
537 * cares about the content instead of just holding the reference.
538 */
539 if (ellipsize != null) {
540 Ellipsizer e = (Ellipsizer) getText();
541
542 e.mLayout = this;
543 e.mWidth = ellipsizedWidth;
544 e.mMethod = ellipsize;
545 mEllipsizedWidth = ellipsizedWidth;
546
547 mColumns = COLUMNS_ELLIPSIZE;
548 } else {
549 mColumns = COLUMNS_NORMAL;
550 mEllipsizedWidth = outerwidth;
551 }
552
Siyamed Siniraf398512017-07-25 19:08:42 -0700553 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2);
554 mLines = ArrayUtils.newUnpaddedIntArray(2 * mColumns);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700555 mMaximumVisibleLineCount = maxLines;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800556
Raph Levien70616ec2015-03-04 10:41:30 -0800557 generate(b, b.mIncludePad, b.mIncludePad);
Doug Felte8e45f22010-03-29 14:58:40 -0700558
Raph Leviend3ab6922015-03-02 14:30:53 -0800559 Builder.recycle(b);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800560 }
561
Roozbeh Pournader7f0ebc92017-08-02 22:47:14 +0000562 /* package */ StaticLayout(CharSequence text) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700563 super(text, null, 0, null, 0, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800564
565 mColumns = COLUMNS_ELLIPSIZE;
Siyamed Siniraf398512017-07-25 19:08:42 -0700566 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2);
567 mLines = ArrayUtils.newUnpaddedIntArray(2 * mColumns);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800568 }
569
Raph Levien39b4db72015-03-25 13:18:20 -0700570 private StaticLayout(Builder b) {
571 super((b.mEllipsize == null)
572 ? b.mText
573 : (b.mText instanceof Spanned)
574 ? new SpannedEllipsizer(b.mText)
575 : new Ellipsizer(b.mText),
576 b.mPaint, b.mWidth, b.mAlignment, b.mSpacingMult, b.mSpacingAdd);
577
578 if (b.mEllipsize != null) {
579 Ellipsizer e = (Ellipsizer) getText();
580
581 e.mLayout = this;
582 e.mWidth = b.mEllipsizedWidth;
583 e.mMethod = b.mEllipsize;
584 mEllipsizedWidth = b.mEllipsizedWidth;
585
586 mColumns = COLUMNS_ELLIPSIZE;
587 } else {
588 mColumns = COLUMNS_NORMAL;
589 mEllipsizedWidth = b.mWidth;
590 }
591
Siyamed Siniraf398512017-07-25 19:08:42 -0700592 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2);
593 mLines = ArrayUtils.newUnpaddedIntArray(2 * mColumns);
Raph Levien39b4db72015-03-25 13:18:20 -0700594 mMaximumVisibleLineCount = b.mMaxLines;
595
Raph Levien2ea52902015-07-01 14:39:31 -0700596 mLeftIndents = b.mLeftIndents;
597 mRightIndents = b.mRightIndents;
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700598 setJustificationMode(b.mJustificationMode);
Raph Levien2ea52902015-07-01 14:39:31 -0700599
Raph Levien39b4db72015-03-25 13:18:20 -0700600 generate(b, b.mIncludePad, b.mIncludePad);
601 }
602
Raph Leviend3ab6922015-03-02 14:30:53 -0800603 /* package */ void generate(Builder b, boolean includepad, boolean trackpad) {
Roozbeh Pournader7f0ebc92017-08-02 22:47:14 +0000604 CharSequence source = b.mText;
Raph Leviend3ab6922015-03-02 14:30:53 -0800605 int bufStart = b.mStart;
606 int bufEnd = b.mEnd;
607 TextPaint paint = b.mPaint;
608 int outerWidth = b.mWidth;
609 TextDirectionHeuristic textDir = b.mTextDir;
610 float spacingmult = b.mSpacingMult;
611 float spacingadd = b.mSpacingAdd;
612 float ellipsizedWidth = b.mEllipsizedWidth;
613 TextUtils.TruncateAt ellipsize = b.mEllipsize;
Siyamed Sinir442c1512017-07-24 12:18:27 -0700614 final boolean addLastLineSpacing = b.mAddLastLineLineSpacing;
Raph Levien4c1f12e2015-03-02 16:29:23 -0800615 LineBreaks lineBreaks = new LineBreaks(); // TODO: move to builder to avoid allocation costs
Anish Athalyec8f9e622014-07-21 15:26:34 -0700616 // store span end locations
617 int[] spanEndCache = new int[4];
618 // store fontMetrics per span range
619 // must be a multiple of 4 (and > 0) (store top, bottom, ascent, and descent per range)
620 int[] fmCache = new int[4 * 4];
Roozbeh Pournaderef7cfa12017-06-15 12:39:04 -0700621 b.setLocales(paint.getTextLocales());
Anish Athalye88b5b0b2014-06-24 14:39:43 -0700622
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800623 mLineCount = 0;
Siyamed Sinira19cd512017-08-03 22:01:56 -0700624 mEllipsized = false;
625 mMaxLineHeight = DEFAULT_MAX_LINE_HEIGHT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800626
627 int v = 0;
628 boolean needMultiply = (spacingmult != 1 || spacingadd != 0);
629
Raph Leviend3ab6922015-03-02 14:30:53 -0800630 Paint.FontMetricsInt fm = b.mFontMetricsInt;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800631 int[] chooseHtv = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800632
Raph Leviend3ab6922015-03-02 14:30:53 -0800633 MeasuredText measured = b.mMeasuredText;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800634
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800635 Spanned spanned = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800636 if (source instanceof Spanned)
637 spanned = (Spanned) source;
638
Doug Felte8e45f22010-03-29 14:58:40 -0700639 int paraEnd;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800640 for (int paraStart = bufStart; paraStart <= bufEnd; paraStart = paraEnd) {
641 paraEnd = TextUtils.indexOf(source, CHAR_NEW_LINE, paraStart, bufEnd);
Doug Felte8e45f22010-03-29 14:58:40 -0700642 if (paraEnd < 0)
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800643 paraEnd = bufEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800644 else
Doug Felte8e45f22010-03-29 14:58:40 -0700645 paraEnd++;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800646
Anish Athalyec8f9e622014-07-21 15:26:34 -0700647 int firstWidthLineCount = 1;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800648 int firstWidth = outerWidth;
649 int restWidth = outerWidth;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800650
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800651 LineHeightSpan[] chooseHt = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800652
653 if (spanned != null) {
Eric Fischer74d31ef2010-08-05 15:29:36 -0700654 LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
Doug Felte8e45f22010-03-29 14:58:40 -0700655 LeadingMarginSpan.class);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800656 for (int i = 0; i < sp.length; i++) {
Mark Wagner7b5676e2009-10-16 11:44:23 -0700657 LeadingMarginSpan lms = sp[i];
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800658 firstWidth -= sp[i].getLeadingMargin(true);
659 restWidth -= sp[i].getLeadingMargin(false);
Doug Feltcb3791202011-07-07 11:57:48 -0700660
Doug Feltc982f602010-05-25 11:51:40 -0700661 // LeadingMarginSpan2 is odd. The count affects all
Anish Athalyeab08c6d2014-08-08 12:09:58 -0700662 // leading margin spans, not just this particular one
Doug Feltc982f602010-05-25 11:51:40 -0700663 if (lms instanceof LeadingMarginSpan2) {
664 LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700665 firstWidthLineCount = Math.max(firstWidthLineCount,
666 lms2.getLeadingMarginLineCount());
Mark Wagner7b5676e2009-10-16 11:44:23 -0700667 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800668 }
669
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800670 chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800671
Roozbeh Pournader431e5062015-10-16 02:27:03 -0700672 if (chooseHt.length == 0) {
673 chooseHt = null; // So that out() would not assume it has any contents
674 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800675 if (chooseHtv == null ||
676 chooseHtv.length < chooseHt.length) {
Adam Lesinski776abc22014-03-07 11:30:59 -0500677 chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800678 }
679
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800680 for (int i = 0; i < chooseHt.length; i++) {
681 int o = spanned.getSpanStart(chooseHt[i]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800682
Doug Felte8e45f22010-03-29 14:58:40 -0700683 if (o < paraStart) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800684 // starts in this layout, before the
685 // current paragraph
686
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800687 chooseHtv[i] = getLineTop(getLineForOffset(o));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800688 } else {
689 // starts in this paragraph
690
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800691 chooseHtv[i] = v;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800692 }
693 }
694 }
695 }
696
Raph Levien70616ec2015-03-04 10:41:30 -0800697 measured.setPara(source, paraStart, paraEnd, textDir, b);
Doug Felte8e45f22010-03-29 14:58:40 -0700698 char[] chs = measured.mChars;
699 float[] widths = measured.mWidths;
700 byte[] chdirs = measured.mLevels;
701 int dir = measured.mDir;
702 boolean easy = measured.mEasy;
Raph Levienc94f7422015-03-06 19:19:48 -0800703
704 // tab stop locations
705 int[] variableTabStops = null;
706 if (spanned != null) {
707 TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
708 paraEnd, TabStopSpan.class);
709 if (spans.length > 0) {
710 int[] stops = new int[spans.length];
711 for (int i = 0; i < spans.length; i++) {
712 stops[i] = spans[i].getTabStop();
713 }
714 Arrays.sort(stops, 0, stops.length);
715 variableTabStops = stops;
716 }
717 }
718
Raph Levienc94f7422015-03-06 19:19:48 -0800719 nSetupParagraph(b.mNativePtr, chs, paraEnd - paraStart,
720 firstWidth, firstWidthLineCount, restWidth,
Seigo Nonaka09da71a2016-11-28 16:24:14 +0900721 variableTabStops, TAB_INCREMENT, b.mBreakStrategy, b.mHyphenationFrequency,
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700722 // TODO: Support more justification mode, e.g. letter spacing, stretching.
723 b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE);
Raph Levien2ea52902015-07-01 14:39:31 -0700724 if (mLeftIndents != null || mRightIndents != null) {
Roozbeh Pournader7f0ebc92017-08-02 22:47:14 +0000725 // TODO(raph) performance: it would be better to do this once per layout rather
Raph Levien2ea52902015-07-01 14:39:31 -0700726 // than once per paragraph, but that would require a change to the native
727 // interface.
728 int leftLen = mLeftIndents == null ? 0 : mLeftIndents.length;
729 int rightLen = mRightIndents == null ? 0 : mRightIndents.length;
Siyamed Sinirf9a08862016-04-12 19:30:44 -0700730 int indentsLen = Math.max(1, Math.max(leftLen, rightLen) - mLineCount);
Raph Levien2ea52902015-07-01 14:39:31 -0700731 int[] indents = new int[indentsLen];
732 for (int i = 0; i < indentsLen; i++) {
733 int leftMargin = mLeftIndents == null ? 0 :
734 mLeftIndents[Math.min(i + mLineCount, leftLen - 1)];
735 int rightMargin = mRightIndents == null ? 0 :
736 mRightIndents[Math.min(i + mLineCount, rightLen - 1)];
737 indents[i] = leftMargin + rightMargin;
738 }
739 nSetIndents(b.mNativePtr, indents);
740 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800741
Anish Athalyec8f9e622014-07-21 15:26:34 -0700742 // measurement has to be done before performing line breaking
743 // but we don't want to recompute fontmetrics or span ranges the
744 // second time, so we cache those and then use those stored values
745 int fmCacheCount = 0;
746 int spanEndCacheCount = 0;
Gilles Debunnecd943a72012-06-07 17:54:47 -0700747 for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
Anish Athalyec8f9e622014-07-21 15:26:34 -0700748 if (fmCacheCount * 4 >= fmCache.length) {
749 int[] grow = new int[fmCacheCount * 4 * 2];
750 System.arraycopy(fmCache, 0, grow, 0, fmCacheCount * 4);
751 fmCache = grow;
752 }
753
754 if (spanEndCacheCount >= spanEndCache.length) {
755 int[] grow = new int[spanEndCacheCount * 2];
756 System.arraycopy(spanEndCache, 0, grow, 0, spanEndCacheCount);
757 spanEndCache = grow;
758 }
Doug Felte8e45f22010-03-29 14:58:40 -0700759
Gilles Debunnecd943a72012-06-07 17:54:47 -0700760 if (spanned == null) {
761 spanEnd = paraEnd;
Doug Felt23241882010-06-02 14:41:06 -0700762 int spanLen = spanEnd - spanStart;
Gilles Debunnecd943a72012-06-07 17:54:47 -0700763 measured.addStyleRun(paint, spanLen, fm);
764 } else {
765 spanEnd = spanned.nextSpanTransition(spanStart, paraEnd,
766 MetricAffectingSpan.class);
767 int spanLen = spanEnd - spanStart;
768 MetricAffectingSpan[] spans =
Doug Felt23241882010-06-02 14:41:06 -0700769 spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class);
Gilles Debunnecd943a72012-06-07 17:54:47 -0700770 spans = TextUtils.removeEmptySpans(spans, spanned, MetricAffectingSpan.class);
771 measured.addStyleRun(paint, spans, spanLen, fm);
Doug Felt23241882010-06-02 14:41:06 -0700772 }
773
Anish Athalyec8f9e622014-07-21 15:26:34 -0700774 // the order of storage here (top, bottom, ascent, descent) has to match the code below
775 // where these values are retrieved
776 fmCache[fmCacheCount * 4 + 0] = fm.top;
777 fmCache[fmCacheCount * 4 + 1] = fm.bottom;
778 fmCache[fmCacheCount * 4 + 2] = fm.ascent;
779 fmCache[fmCacheCount * 4 + 3] = fm.descent;
780 fmCacheCount++;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800781
Anish Athalyec8f9e622014-07-21 15:26:34 -0700782 spanEndCache[spanEndCacheCount] = spanEnd;
783 spanEndCacheCount++;
784 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800785
Raph Levien70616ec2015-03-04 10:41:30 -0800786 nGetWidths(b.mNativePtr, widths);
Raph Levienc94f7422015-03-06 19:19:48 -0800787 int breakCount = nComputeLineBreaks(b.mNativePtr, lineBreaks, lineBreaks.breaks,
788 lineBreaks.widths, lineBreaks.flags, lineBreaks.breaks.length);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800789
Anish Athalyec8f9e622014-07-21 15:26:34 -0700790 int[] breaks = lineBreaks.breaks;
791 float[] lineWidths = lineBreaks.widths;
Raph Levien26d443a2015-03-30 14:18:32 -0700792 int[] flags = lineBreaks.flags;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700793
Keisuke Kuroyanagif4a3f3a2015-06-10 09:37:32 +0900794 final int remainingLineCount = mMaximumVisibleLineCount - mLineCount;
795 final boolean ellipsisMayBeApplied = ellipsize != null
796 && (ellipsize == TextUtils.TruncateAt.END
797 || (mMaximumVisibleLineCount == 1
798 && ellipsize != TextUtils.TruncateAt.MARQUEE));
Roozbeh Pournader9ea756f2017-07-25 11:20:29 -0700799 if (0 < remainingLineCount && remainingLineCount < breakCount
800 && ellipsisMayBeApplied) {
Keisuke Kuroyanagif4a3f3a2015-06-10 09:37:32 +0900801 // Calculate width and flag.
802 float width = 0;
803 int flag = 0;
804 for (int i = remainingLineCount - 1; i < breakCount; i++) {
Keisuke Kuroyanagi78f0d832016-05-10 12:21:33 -0700805 if (i == breakCount - 1) {
806 width += lineWidths[i];
807 } else {
808 for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) {
809 width += widths[j];
810 }
811 }
Roozbeh Pournader7f0ebc92017-08-02 22:47:14 +0000812 flag |= flags[i] & TAB_MASK;
Keisuke Kuroyanagif4a3f3a2015-06-10 09:37:32 +0900813 }
Keisuke Kuroyanagi78f0d832016-05-10 12:21:33 -0700814 // Treat the last line and overflowed lines as a single line.
815 breaks[remainingLineCount - 1] = breaks[breakCount - 1];
Keisuke Kuroyanagif4a3f3a2015-06-10 09:37:32 +0900816 lineWidths[remainingLineCount - 1] = width;
817 flags[remainingLineCount - 1] = flag;
818
819 breakCount = remainingLineCount;
820 }
821
Anish Athalyec8f9e622014-07-21 15:26:34 -0700822 // here is the offset of the starting character of the line we are currently measuring
823 int here = paraStart;
824
825 int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
826 int fmCacheIndex = 0;
827 int spanEndCacheIndex = 0;
828 int breakIndex = 0;
829 for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
830 // retrieve end of span
831 spanEnd = spanEndCache[spanEndCacheIndex++];
832
833 // retrieve cached metrics, order matches above
834 fm.top = fmCache[fmCacheIndex * 4 + 0];
835 fm.bottom = fmCache[fmCacheIndex * 4 + 1];
836 fm.ascent = fmCache[fmCacheIndex * 4 + 2];
837 fm.descent = fmCache[fmCacheIndex * 4 + 3];
838 fmCacheIndex++;
839
840 if (fm.top < fmTop) {
841 fmTop = fm.top;
842 }
843 if (fm.ascent < fmAscent) {
844 fmAscent = fm.ascent;
845 }
846 if (fm.descent > fmDescent) {
847 fmDescent = fm.descent;
848 }
849 if (fm.bottom > fmBottom) {
850 fmBottom = fm.bottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800851 }
852
Anish Athalyec8f9e622014-07-21 15:26:34 -0700853 // skip breaks ending before current span range
854 while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
855 breakIndex++;
856 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800857
Anish Athalyec8f9e622014-07-21 15:26:34 -0700858 while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
859 int endPos = paraStart + breaks[breakIndex];
860
Raph Levience4155a2015-03-11 11:02:33 -0700861 boolean moreChars = (endPos < bufEnd);
Raph Levien4c02e832014-12-12 11:17:01 -0800862
Anish Athalyec8f9e622014-07-21 15:26:34 -0700863 v = out(source, here, endPos,
864 fmAscent, fmDescent, fmTop, fmBottom,
Roozbeh Pournader431e5062015-10-16 02:27:03 -0700865 v, spacingmult, spacingadd, chooseHt, chooseHtv, fm, flags[breakIndex],
Anish Athalyec8f9e622014-07-21 15:26:34 -0700866 needMultiply, chdirs, dir, easy, bufEnd, includepad, trackpad,
Siyamed Sinir442c1512017-07-24 12:18:27 -0700867 addLastLineSpacing, chs, widths, paraStart, ellipsize,
868 ellipsizedWidth, lineWidths[breakIndex], paint, moreChars);
Anish Athalyec8f9e622014-07-21 15:26:34 -0700869
870 if (endPos < spanEnd) {
871 // preserve metrics for current span
872 fmTop = fm.top;
873 fmBottom = fm.bottom;
874 fmAscent = fm.ascent;
875 fmDescent = fm.descent;
876 } else {
877 fmTop = fmBottom = fmAscent = fmDescent = 0;
878 }
879
880 here = endPos;
881 breakIndex++;
882
Siyamed Sinir0745c722016-05-31 20:39:33 -0700883 if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) {
Anish Athalyec8f9e622014-07-21 15:26:34 -0700884 return;
885 }
886 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800887 }
888
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800889 if (paraEnd == bufEnd)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800890 break;
891 }
892
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700893 if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE) &&
Fabrice Di Meglioad0b0512011-10-04 17:21:26 -0700894 mLineCount < mMaximumVisibleLineCount) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800895 // Log.e("text", "output last " + bufEnd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800896
Raph Levien70616ec2015-03-04 10:41:30 -0800897 measured.setPara(source, bufEnd, bufEnd, textDir, b);
Fabrice Di Meglioe6318892013-06-18 20:03:41 -0700898
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800899 paint.getFontMetricsInt(fm);
900
901 v = out(source,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800902 bufEnd, bufEnd, fm.ascent, fm.descent,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800903 fm.top, fm.bottom,
904 v,
905 spacingmult, spacingadd, null,
Raph Levien26d443a2015-03-30 14:18:32 -0700906 null, fm, 0,
Fabrice Di Meglioe6318892013-06-18 20:03:41 -0700907 needMultiply, measured.mLevels, measured.mDir, measured.mEasy, bufEnd,
Siyamed Sinir442c1512017-07-24 12:18:27 -0700908 includepad, trackpad, addLastLineSpacing, null,
Gilles Debunned300e752011-10-17 13:37:36 -0700909 null, bufStart, ellipsize,
910 ellipsizedWidth, 0, paint, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800911 }
912 }
913
Roozbeh Pournader7f0ebc92017-08-02 22:47:14 +0000914 private int out(CharSequence text, int start, int end,
915 int above, int below, int top, int bottom, int v,
916 float spacingmult, float spacingadd,
917 LineHeightSpan[] chooseHt, int[] chooseHtv,
918 Paint.FontMetricsInt fm, int flags,
919 boolean needMultiply, byte[] chdirs, int dir,
920 boolean easy, int bufEnd, boolean includePad,
921 boolean trackPad, boolean addLastLineLineSpacing, char[] chs,
922 float[] widths, int widthStart, TextUtils.TruncateAt ellipsize,
923 float ellipsisWidth, float textWidth,
924 TextPaint paint, boolean moreChars) {
Siyamed Siniraf398512017-07-25 19:08:42 -0700925 final int j = mLineCount;
926 final int off = j * mColumns;
927 final int want = off + mColumns + TOP;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800928 int[] lines = mLines;
929
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800930 if (want >= lines.length) {
Siyamed Siniraf398512017-07-25 19:08:42 -0700931 final int[] grow = ArrayUtils.newUnpaddedIntArray(GrowingArrayUtils.growSize(want));
Adam Lesinski776abc22014-03-07 11:30:59 -0500932 System.arraycopy(lines, 0, grow, 0, lines.length);
933 mLines = grow;
934 lines = grow;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800935 }
936
Siyamed Siniraf398512017-07-25 19:08:42 -0700937 if (j >= mLineDirections.length) {
938 final Directions[] grow = ArrayUtils.newUnpaddedArray(Directions.class,
939 GrowingArrayUtils.growSize(j));
940 System.arraycopy(mLineDirections, 0, grow, 0, mLineDirections.length);
941 mLineDirections = grow;
942 }
943
Roozbeh Pournader7f0ebc92017-08-02 22:47:14 +0000944 if (chooseHt != null) {
945 fm.ascent = above;
946 fm.descent = below;
947 fm.top = top;
948 fm.bottom = bottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800949
Roozbeh Pournader7f0ebc92017-08-02 22:47:14 +0000950 for (int i = 0; i < chooseHt.length; i++) {
951 if (chooseHt[i] instanceof LineHeightSpan.WithDensity) {
952 ((LineHeightSpan.WithDensity) chooseHt[i]).
953 chooseHeight(text, start, end, chooseHtv[i], v, fm, paint);
Eric Fischera9f1dd02009-08-12 15:00:10 -0700954
Roozbeh Pournader7f0ebc92017-08-02 22:47:14 +0000955 } else {
956 chooseHt[i].chooseHeight(text, start, end, chooseHtv[i], v, fm);
957 }
958 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800959
Roozbeh Pournader7f0ebc92017-08-02 22:47:14 +0000960 above = fm.ascent;
961 below = fm.descent;
962 top = fm.top;
963 bottom = fm.bottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800964 }
965
Roozbeh Pournader7f0ebc92017-08-02 22:47:14 +0000966 boolean firstLine = (j == 0);
967 boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);
Siyamed Sinir0745c722016-05-31 20:39:33 -0700968
969 if (ellipsize != null) {
970 // If there is only one line, then do any type of ellipsis except when it is MARQUEE
971 // if there are multiple lines, just allow END ellipsis on the last line
972 boolean forceEllipsis = moreChars && (mLineCount + 1 == mMaximumVisibleLineCount);
973
974 boolean doEllipsis =
975 (((mMaximumVisibleLineCount == 1 && moreChars) || (firstLine && !moreChars)) &&
976 ellipsize != TextUtils.TruncateAt.MARQUEE) ||
977 (!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
978 ellipsize == TextUtils.TruncateAt.END);
979 if (doEllipsis) {
Roozbeh Pournader7f0ebc92017-08-02 22:47:14 +0000980 calculateEllipsis(start, end, widths, widthStart,
981 ellipsisWidth, ellipsize, j,
982 textWidth, paint, forceEllipsis);
Siyamed Sinir0745c722016-05-31 20:39:33 -0700983 }
984 }
985
Siyamed Sinirfb0b2dc2017-07-26 22:08:24 -0700986 final boolean lastLine;
987 if (mEllipsized) {
988 lastLine = true;
989 } else {
990 final boolean lastCharIsNewLine = widthStart != bufEnd && bufEnd > 0
991 && text.charAt(bufEnd - 1) == CHAR_NEW_LINE;
992 if (end == bufEnd && !lastCharIsNewLine) {
993 lastLine = true;
994 } else if (start == bufEnd && lastCharIsNewLine) {
995 lastLine = true;
996 } else {
997 lastLine = false;
998 }
999 }
Raph Leviend97b0972014-04-24 12:51:35 -07001000
1001 if (firstLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001002 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001003 mTopPadding = top - above;
1004 }
1005
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001006 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001007 above = top;
1008 }
1009 }
Raph Leviend97b0972014-04-24 12:51:35 -07001010
Roozbeh Pournader7f0ebc92017-08-02 22:47:14 +00001011 int extra;
1012
Raph Leviend97b0972014-04-24 12:51:35 -07001013 if (lastLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001014 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001015 mBottomPadding = bottom - below;
1016 }
1017
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001018 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001019 below = bottom;
1020 }
1021 }
1022
Siyamed Sinir442c1512017-07-24 12:18:27 -07001023 if (needMultiply && (addLastLineLineSpacing || !lastLine)) {
Roozbeh Pournader7f0ebc92017-08-02 22:47:14 +00001024 double ex = (below - above) * (spacingmult - 1) + spacingadd;
Doug Felt10657582010-02-22 11:19:01 -08001025 if (ex >= 0) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001026 extra = (int)(ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -08001027 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001028 extra = -(int)(-ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -08001029 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001030 } else {
1031 extra = 0;
1032 }
1033
Roozbeh Pournader7f0ebc92017-08-02 22:47:14 +00001034 lines[off + START] = start;
1035 lines[off + TOP] = v;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001036 lines[off + DESCENT] = below + extra;
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001037 lines[off + EXTRA] = extra;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001038
Siyamed Sinir0745c722016-05-31 20:39:33 -07001039 // special case for non-ellipsized last visible line when maxLines is set
1040 // store the height as if it was ellipsized
1041 if (!mEllipsized && currentLineIsTheLastVisibleOne) {
1042 // below calculation as if it was the last line
Roozbeh Pournader7f0ebc92017-08-02 22:47:14 +00001043 int maxLineBelow = includePad ? bottom : below;
Siyamed Sinir0745c722016-05-31 20:39:33 -07001044 // similar to the calculation of v below, without the extra.
1045 mMaxLineHeight = v + (maxLineBelow - above);
1046 }
1047
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001048 v += (below - above) + extra;
1049 lines[off + mColumns + START] = end;
1050 lines[off + mColumns + TOP] = v;
1051
Roozbeh Pournader7f0ebc92017-08-02 22:47:14 +00001052 // TODO: could move TAB to share same column as HYPHEN, simplifying this code and gaining
1053 // one bit for start field
1054 lines[off + TAB] |= flags & TAB_MASK;
1055 lines[off + HYPHEN] = flags;
1056
1057 lines[off + DIR] |= dir << DIR_SHIFT;
1058 Directions linedirs = DIRS_ALL_LEFT_TO_RIGHT;
1059 // easy means all chars < the first RTL, so no emoji, no nothing
1060 // XXX a run with no text or all spaces is easy but might be an empty
1061 // RTL paragraph. Make sure easy is false if this is the case.
1062 if (easy) {
1063 mLineDirections[j] = linedirs;
1064 } else {
1065 mLineDirections[j] = AndroidBidi.directions(dir, chdirs, start - widthStart, chs,
1066 start - widthStart, end - start);
1067 }
1068
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001069 mLineCount++;
1070 return v;
1071 }
1072
Roozbeh Pournader7f0ebc92017-08-02 22:47:14 +00001073 private void calculateEllipsis(int lineStart, int lineEnd,
1074 float[] widths, int widthStart,
1075 float avail, TextUtils.TruncateAt where,
1076 int line, float textWidth, TextPaint paint,
1077 boolean forceEllipsis) {
1078 avail -= getTotalInsets(line);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001079 if (textWidth <= avail && !forceEllipsis) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001080 // Everything fits!
1081 mLines[mColumns * line + ELLIPSIS_START] = 0;
1082 mLines[mColumns * line + ELLIPSIS_COUNT] = 0;
1083 return;
1084 }
1085
Roozbeh Pournader7f0ebc92017-08-02 22:47:14 +00001086 float ellipsisWidth = paint.measureText(TextUtils.getEllipsisString(where));
1087 int ellipsisStart = 0;
1088 int ellipsisCount = 0;
1089 int len = lineEnd - lineStart;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001090
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001091 // We only support start ellipsis on a single line
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001092 if (where == TextUtils.TruncateAt.START) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001093 if (mMaximumVisibleLineCount == 1) {
1094 float sum = 0;
1095 int i;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001096
Keisuke Kuroyanagied2eea12015-04-14 18:18:35 +09001097 for (i = len; i > 0; i--) {
Roozbeh Pournader7f0ebc92017-08-02 22:47:14 +00001098 float w = widths[i - 1 + lineStart - widthStart];
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001099 if (w + sum + ellipsisWidth > avail) {
Roozbeh Pournader7f0ebc92017-08-02 22:47:14 +00001100 while (i < len && widths[i + lineStart - widthStart] == 0.0f) {
Keisuke Kuroyanagi82d1c442016-09-14 14:30:14 +09001101 i++;
1102 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001103 break;
1104 }
1105
1106 sum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001107 }
1108
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001109 ellipsisStart = 0;
1110 ellipsisCount = i;
1111 } else {
1112 if (Log.isLoggable(TAG, Log.WARN)) {
Roozbeh Pournader7f0ebc92017-08-02 22:47:14 +00001113 Log.w(TAG, "Start Ellipsis only supported with one line");
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001114 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001115 }
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001116 } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE ||
1117 where == TextUtils.TruncateAt.END_SMALL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001118 float sum = 0;
1119 int i;
1120
1121 for (i = 0; i < len; i++) {
Roozbeh Pournader7f0ebc92017-08-02 22:47:14 +00001122 float w = widths[i + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001123
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001124 if (w + sum + ellipsisWidth > avail) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001125 break;
1126 }
1127
1128 sum += w;
1129 }
1130
Roozbeh Pournader7f0ebc92017-08-02 22:47:14 +00001131 ellipsisStart = i;
1132 ellipsisCount = len - i;
1133 if (forceEllipsis && ellipsisCount == 0 && len > 0) {
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -07001134 ellipsisStart = len - 1;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001135 ellipsisCount = 1;
1136 }
Roozbeh Pournader7f0ebc92017-08-02 22:47:14 +00001137 } else {
1138 // where = TextUtils.TruncateAt.MIDDLE We only support middle ellipsis on a single line
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001139 if (mMaximumVisibleLineCount == 1) {
1140 float lsum = 0, rsum = 0;
1141 int left = 0, right = len;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001142
Roozbeh Pournader7f0ebc92017-08-02 22:47:14 +00001143 float ravail = (avail - ellipsisWidth) / 2;
Raph Levien0e3c5e82014-12-04 13:26:07 -08001144 for (right = len; right > 0; right--) {
Roozbeh Pournader7f0ebc92017-08-02 22:47:14 +00001145 float w = widths[right - 1 + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001146
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001147 if (w + rsum > ravail) {
Roozbeh Pournader7f0ebc92017-08-02 22:47:14 +00001148 while (right < len && widths[right + lineStart - widthStart] == 0.0f) {
Keisuke Kuroyanagi82d1c442016-09-14 14:30:14 +09001149 right++;
1150 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001151 break;
1152 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001153 rsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001154 }
1155
Roozbeh Pournader7f0ebc92017-08-02 22:47:14 +00001156 float lavail = avail - ellipsisWidth - rsum;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001157 for (left = 0; left < right; left++) {
Roozbeh Pournader7f0ebc92017-08-02 22:47:14 +00001158 float w = widths[left + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001159
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001160 if (w + lsum > lavail) {
1161 break;
1162 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001163
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001164 lsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001165 }
1166
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001167 ellipsisStart = left;
1168 ellipsisCount = right - left;
1169 } else {
1170 if (Log.isLoggable(TAG, Log.WARN)) {
Roozbeh Pournader7f0ebc92017-08-02 22:47:14 +00001171 Log.w(TAG, "Middle Ellipsis only supported with one line");
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001172 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001173 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001174 }
Roozbeh Pournader7f0ebc92017-08-02 22:47:14 +00001175 mEllipsized = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001176 mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
1177 mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
1178 }
1179
Selim Cinek365ec092017-03-09 00:10:52 -08001180 private float getTotalInsets(int line) {
1181 int totalIndent = 0;
1182 if (mLeftIndents != null) {
1183 totalIndent = mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1184 }
1185 if (mRightIndents != null) {
1186 totalIndent += mRightIndents[Math.min(line, mRightIndents.length - 1)];
1187 }
1188 return totalIndent;
1189 }
1190
Doug Felte8e45f22010-03-29 14:58:40 -07001191 // Override the base class so we can directly access our members,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001192 // rather than relying on member functions.
1193 // The logic mirrors that of Layout.getLineForVertical
1194 // FIXME: It may be faster to do a linear search for layouts without many lines.
Gilles Debunne66111472010-11-19 11:04:37 -08001195 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001196 public int getLineForVertical(int vertical) {
1197 int high = mLineCount;
1198 int low = -1;
1199 int guess;
1200 int[] lines = mLines;
1201 while (high - low > 1) {
1202 guess = (high + low) >> 1;
1203 if (lines[mColumns * guess + TOP] > vertical){
1204 high = guess;
1205 } else {
1206 low = guess;
1207 }
1208 }
1209 if (low < 0) {
1210 return 0;
1211 } else {
1212 return low;
1213 }
1214 }
1215
Gilles Debunne66111472010-11-19 11:04:37 -08001216 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001217 public int getLineCount() {
1218 return mLineCount;
1219 }
1220
Gilles Debunne66111472010-11-19 11:04:37 -08001221 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001222 public int getLineTop(int line) {
Raph Levien07e6c232016-04-04 12:34:06 -07001223 return mLines[mColumns * line + TOP];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001224 }
1225
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001226 /**
1227 * @hide
1228 */
1229 @Override
1230 public int getLineExtra(int line) {
1231 return mLines[mColumns * line + EXTRA];
1232 }
1233
Gilles Debunne66111472010-11-19 11:04:37 -08001234 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001235 public int getLineDescent(int line) {
Raph Levien07e6c232016-04-04 12:34:06 -07001236 return mLines[mColumns * line + DESCENT];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001237 }
1238
Gilles Debunne66111472010-11-19 11:04:37 -08001239 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001240 public int getLineStart(int line) {
1241 return mLines[mColumns * line + START] & START_MASK;
1242 }
1243
Gilles Debunne66111472010-11-19 11:04:37 -08001244 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001245 public int getParagraphDirection(int line) {
1246 return mLines[mColumns * line + DIR] >> DIR_SHIFT;
1247 }
1248
Gilles Debunne66111472010-11-19 11:04:37 -08001249 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001250 public boolean getLineContainsTab(int line) {
1251 return (mLines[mColumns * line + TAB] & TAB_MASK) != 0;
1252 }
1253
Gilles Debunne66111472010-11-19 11:04:37 -08001254 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001255 public final Directions getLineDirections(int line) {
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001256 if (line > getLineCount()) {
1257 throw new ArrayIndexOutOfBoundsException();
1258 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001259 return mLineDirections[line];
1260 }
1261
Gilles Debunne66111472010-11-19 11:04:37 -08001262 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001263 public int getTopPadding() {
1264 return mTopPadding;
1265 }
1266
Gilles Debunne66111472010-11-19 11:04:37 -08001267 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001268 public int getBottomPadding() {
1269 return mBottomPadding;
1270 }
1271
Raph Levien26d443a2015-03-30 14:18:32 -07001272 /**
1273 * @hide
1274 */
1275 @Override
1276 public int getHyphen(int line) {
Keisuke Kuroyanagif5af4a32016-08-31 21:40:53 +09001277 return mLines[mColumns * line + HYPHEN] & HYPHEN_MASK;
Raph Levien26d443a2015-03-30 14:18:32 -07001278 }
1279
Raph Levien2ea52902015-07-01 14:39:31 -07001280 /**
1281 * @hide
1282 */
1283 @Override
1284 public int getIndentAdjust(int line, Alignment align) {
1285 if (align == Alignment.ALIGN_LEFT) {
1286 if (mLeftIndents == null) {
1287 return 0;
1288 } else {
1289 return mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1290 }
1291 } else if (align == Alignment.ALIGN_RIGHT) {
1292 if (mRightIndents == null) {
1293 return 0;
1294 } else {
1295 return -mRightIndents[Math.min(line, mRightIndents.length - 1)];
1296 }
1297 } else if (align == Alignment.ALIGN_CENTER) {
1298 int left = 0;
1299 if (mLeftIndents != null) {
1300 left = mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1301 }
1302 int right = 0;
1303 if (mRightIndents != null) {
1304 right = mRightIndents[Math.min(line, mRightIndents.length - 1)];
1305 }
1306 return (left - right) >> 1;
1307 } else {
1308 throw new AssertionError("unhandled alignment " + align);
1309 }
1310 }
1311
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001312 @Override
1313 public int getEllipsisCount(int line) {
1314 if (mColumns < COLUMNS_ELLIPSIZE) {
1315 return 0;
1316 }
1317
1318 return mLines[mColumns * line + ELLIPSIS_COUNT];
1319 }
1320
1321 @Override
1322 public int getEllipsisStart(int line) {
1323 if (mColumns < COLUMNS_ELLIPSIZE) {
1324 return 0;
1325 }
1326
1327 return mLines[mColumns * line + ELLIPSIS_START];
1328 }
1329
1330 @Override
1331 public int getEllipsizedWidth() {
1332 return mEllipsizedWidth;
1333 }
1334
Siyamed Sinir0745c722016-05-31 20:39:33 -07001335 /**
1336 * Return the total height of this layout.
1337 *
1338 * @param cap if true and max lines is set, returns the height of the layout at the max lines.
1339 *
1340 * @hide
1341 */
1342 public int getHeight(boolean cap) {
1343 if (cap && mLineCount >= mMaximumVisibleLineCount && mMaxLineHeight == -1 &&
1344 Log.isLoggable(TAG, Log.WARN)) {
1345 Log.w(TAG, "maxLineHeight should not be -1. "
1346 + " maxLines:" + mMaximumVisibleLineCount
1347 + " lineCount:" + mLineCount);
1348 }
1349
1350 return cap && mLineCount >= mMaximumVisibleLineCount && mMaxLineHeight != -1 ?
1351 mMaxLineHeight : super.getHeight();
1352 }
1353
Raph Levien70616ec2015-03-04 10:41:30 -08001354 private static native long nNewBuilder();
1355 private static native void nFreeBuilder(long nativePtr);
1356 private static native void nFinishBuilder(long nativePtr);
Raph Levien26d443a2015-03-30 14:18:32 -07001357
Roozbeh Pournadera59c3fe2017-02-27 10:13:44 -08001358 /* package */ static native long nLoadHyphenator(ByteBuffer buf, int offset,
1359 int minPrefix, int minSuffix);
Raph Levien26d443a2015-03-30 14:18:32 -07001360
Roozbeh Pournaderef7cfa12017-06-15 12:39:04 -07001361 private static native void nSetLocales(long nativePtr, String locales,
1362 long[] nativeHyphenators);
Raph Levien70616ec2015-03-04 10:41:30 -08001363
Raph Leviene319d5a2015-04-14 23:51:07 -07001364 private static native void nSetIndents(long nativePtr, int[] indents);
1365
Raph Levienc94f7422015-03-06 19:19:48 -08001366 // Set up paragraph text and settings; done as one big method to minimize jni crossings
1367 private static native void nSetupParagraph(long nativePtr, char[] text, int length,
1368 float firstWidth, int firstWidthLineCount, float restWidth,
Seigo Nonaka09da71a2016-11-28 16:24:14 +09001369 int[] variableTabStops, int defaultTabStop, int breakStrategy, int hyphenationFrequency,
1370 boolean isJustified);
Raph Levien70616ec2015-03-04 10:41:30 -08001371
1372 private static native float nAddStyleRun(long nativePtr, long nativePaint,
1373 long nativeTypeface, int start, int end, boolean isRtl);
1374
1375 private static native void nAddMeasuredRun(long nativePtr,
1376 int start, int end, float[] widths);
1377
1378 private static native void nAddReplacementRun(long nativePtr, int start, int end, float width);
1379
1380 private static native void nGetWidths(long nativePtr, float[] widths);
1381
Anish Athalyec8f9e622014-07-21 15:26:34 -07001382 // populates LineBreaks and returns the number of breaks found
1383 //
1384 // the arrays inside the LineBreaks objects are passed in as well
1385 // to reduce the number of JNI calls in the common case where the
1386 // arrays do not have to be resized
Raph Levienc94f7422015-03-06 19:19:48 -08001387 private static native int nComputeLineBreaks(long nativePtr, LineBreaks recycle,
Raph Levien26d443a2015-03-30 14:18:32 -07001388 int[] recycleBreaks, float[] recycleWidths, int[] recycleFlags, int recycleLength);
Anish Athalye88b5b0b2014-06-24 14:39:43 -07001389
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001390 private int mLineCount;
1391 private int mTopPadding, mBottomPadding;
1392 private int mColumns;
1393 private int mEllipsizedWidth;
1394
Siyamed Sinir0745c722016-05-31 20:39:33 -07001395 /**
1396 * Keeps track if ellipsize is applied to the text.
1397 */
1398 private boolean mEllipsized;
1399
1400 /**
1401 * If maxLines is set, ellipsize is not set, and the actual line count of text is greater than
1402 * or equal to maxLine, this variable holds the ideal visual height of the maxLine'th line
1403 * starting from the top of the layout. If maxLines is not set its value will be -1.
1404 *
1405 * The value is the same as getLineTop(maxLines) for ellipsized version where structurally no
1406 * more than maxLines is contained.
1407 */
Siyamed Sinira19cd512017-08-03 22:01:56 -07001408 private int mMaxLineHeight = DEFAULT_MAX_LINE_HEIGHT;
Siyamed Sinir0745c722016-05-31 20:39:33 -07001409
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001410 private static final int COLUMNS_NORMAL = 5;
1411 private static final int COLUMNS_ELLIPSIZE = 7;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001412 private static final int START = 0;
1413 private static final int DIR = START;
1414 private static final int TAB = START;
1415 private static final int TOP = 1;
1416 private static final int DESCENT = 2;
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001417 private static final int EXTRA = 3;
1418 private static final int HYPHEN = 4;
1419 private static final int ELLIPSIS_START = 5;
1420 private static final int ELLIPSIS_COUNT = 6;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001421
1422 private int[] mLines;
1423 private Directions[] mLineDirections;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001424 private int mMaximumVisibleLineCount = Integer.MAX_VALUE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001425
1426 private static final int START_MASK = 0x1FFFFFFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001427 private static final int DIR_SHIFT = 30;
1428 private static final int TAB_MASK = 0x20000000;
Keisuke Kuroyanagif5af4a32016-08-31 21:40:53 +09001429 private static final int HYPHEN_MASK = 0xFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001430
Doug Feltc982f602010-05-25 11:51:40 -07001431 private static final int TAB_INCREMENT = 20; // same as Layout, but that's private
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001432
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001433 private static final char CHAR_NEW_LINE = '\n';
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001434
1435 private static final double EXTRA_ROUNDING = 0.5;
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001436
Siyamed Sinira19cd512017-08-03 22:01:56 -07001437 private static final int DEFAULT_MAX_LINE_HEIGHT = -1;
1438
Anish Athalyec8f9e622014-07-21 15:26:34 -07001439 // This is used to return three arrays from a single JNI call when
1440 // performing line breaking
Deepanshu Gupta70539192014-10-15 15:57:40 -07001441 /*package*/ static class LineBreaks {
Anish Athalyec8f9e622014-07-21 15:26:34 -07001442 private static final int INITIAL_SIZE = 16;
1443 public int[] breaks = new int[INITIAL_SIZE];
1444 public float[] widths = new float[INITIAL_SIZE];
Roozbeh Pournader112d9c72015-08-07 12:44:41 -07001445 public int[] flags = new int[INITIAL_SIZE]; // hasTab
Anish Athalyec8f9e622014-07-21 15:26:34 -07001446 // breaks, widths, and flags should all have the same length
1447 }
1448
Raph Levien2ea52902015-07-01 14:39:31 -07001449 private int[] mLeftIndents;
1450 private int[] mRightIndents;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001451}