blob: 74ff6dc0ec7f47bb0f3668069a13c91afe559279 [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;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080021import android.text.style.LeadingMarginSpan;
Gilles Debunne66111472010-11-19 11:04:37 -080022import android.text.style.LeadingMarginSpan.LeadingMarginSpan2;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080023import android.text.style.LineHeightSpan;
24import android.text.style.MetricAffectingSpan;
Doug Feltc982f602010-05-25 11:51:40 -070025import android.text.style.TabStopSpan;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -070026import android.util.Log;
Raph Levien39b4db72015-03-25 13:18:20 -070027import android.util.Pools.SynchronizedPool;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080028
Doug Feltcb3791202011-07-07 11:57:48 -070029import com.android.internal.util.ArrayUtils;
Adam Lesinski776abc22014-03-07 11:30:59 -050030import com.android.internal.util.GrowingArrayUtils;
Doug Feltcb3791202011-07-07 11:57:48 -070031
Raph Levien091dba22015-08-31 16:21:20 -070032import java.nio.ByteBuffer;
Anish Athalyec8f9e622014-07-21 15:26:34 -070033import java.util.Arrays;
Raph Levien4c1f12e2015-03-02 16:29:23 -080034import java.util.Locale;
Anish Athalyec8f9e622014-07-21 15:26:34 -070035
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080036/**
37 * StaticLayout is a Layout for text that will not be edited after it
38 * is laid out. Use {@link DynamicLayout} for text that may change.
39 * <p>This is used by widgets to control text layout. You should not need
40 * to use this class directly unless you are implementing your own widget
41 * or custom display object, or would be tempted to call
Doug Felt4e0c5e52010-03-15 16:56:02 -070042 * {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int,
43 * float, float, android.graphics.Paint)
44 * Canvas.drawText()} directly.</p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080045 */
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -080046public class StaticLayout extends Layout {
47
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -070048 static final String TAG = "StaticLayout";
49
Raph Leviend3ab6922015-03-02 14:30:53 -080050 /**
Raph Levien531c30c2015-04-30 16:29:59 -070051 * Builder for static layouts. The builder is a newer pattern for constructing
52 * StaticLayout objects and should be preferred over the constructors,
53 * particularly to access newer features. To build a static layout, first
54 * call {@link #obtain} with the required arguments (text, paint, and width),
55 * then call setters for optional parameters, and finally {@link #build}
56 * to build the StaticLayout object. Parameters not explicitly set will get
57 * default values.
Raph Leviend3ab6922015-03-02 14:30:53 -080058 */
59 public final static class Builder {
Raph Levien4c1f12e2015-03-02 16:29:23 -080060 private Builder() {
61 mNativePtr = nNewBuilder();
62 }
63
Raph Levien531c30c2015-04-30 16:29:59 -070064 /**
65 * Obtain a builder for constructing StaticLayout objects
66 *
67 * @param source The text to be laid out, optionally with spans
68 * @param start The index of the start of the text
69 * @param end The index + 1 of the end of the text
70 * @param paint The base paint used for layout
71 * @param width The width in pixels
72 * @return a builder object used for constructing the StaticLayout
73 */
Raph Levienebd66ca2015-04-30 15:27:57 -070074 public static Builder obtain(CharSequence source, int start, int end, TextPaint paint,
75 int width) {
Raph Levien39b4db72015-03-25 13:18:20 -070076 Builder b = sPool.acquire();
Raph Leviend3ab6922015-03-02 14:30:53 -080077 if (b == null) {
78 b = new Builder();
79 }
80
81 // set default initial values
Raph Levien39b4db72015-03-25 13:18:20 -070082 b.mText = source;
83 b.mStart = start;
84 b.mEnd = end;
Raph Levienebd66ca2015-04-30 15:27:57 -070085 b.mPaint = paint;
Raph Levien39b4db72015-03-25 13:18:20 -070086 b.mWidth = width;
87 b.mAlignment = Alignment.ALIGN_NORMAL;
Raph Leviend3ab6922015-03-02 14:30:53 -080088 b.mTextDir = TextDirectionHeuristics.FIRSTSTRONG_LTR;
89 b.mSpacingMult = 1.0f;
90 b.mSpacingAdd = 0.0f;
91 b.mIncludePad = true;
Raph Levien39b4db72015-03-25 13:18:20 -070092 b.mEllipsizedWidth = width;
Raph Leviend3ab6922015-03-02 14:30:53 -080093 b.mEllipsize = null;
94 b.mMaxLines = Integer.MAX_VALUE;
Raph Levien3bd60c72015-05-06 14:26:35 -070095 b.mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
Roozbeh Pournader95c7a132015-05-12 12:01:06 -070096 b.mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
Seigo Nonaka4b4730d2017-03-31 09:42:16 -070097 b.mJustificationMode = Layout.JUSTIFICATION_MODE_NONE;
Raph Leviend3ab6922015-03-02 14:30:53 -080098
99 b.mMeasuredText = MeasuredText.obtain();
100 return b;
101 }
102
Raph Levien39b4db72015-03-25 13:18:20 -0700103 private static void recycle(Builder b) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800104 b.mPaint = null;
105 b.mText = null;
106 MeasuredText.recycle(b.mMeasuredText);
Raph Levien3bd60c72015-05-06 14:26:35 -0700107 b.mMeasuredText = null;
Raph Levien2ea52902015-07-01 14:39:31 -0700108 b.mLeftIndents = null;
109 b.mRightIndents = null;
Raph Levien3bd60c72015-05-06 14:26:35 -0700110 nFinishBuilder(b.mNativePtr);
Raph Levien39b4db72015-03-25 13:18:20 -0700111 sPool.release(b);
Raph Leviend3ab6922015-03-02 14:30:53 -0800112 }
113
114 // release any expensive state
115 /* package */ void finish() {
Raph Levien4c1f12e2015-03-02 16:29:23 -0800116 nFinishBuilder(mNativePtr);
Raph Levien22ba7862015-07-29 12:34:13 -0700117 mText = null;
118 mPaint = null;
119 mLeftIndents = null;
120 mRightIndents = null;
Raph Leviend3ab6922015-03-02 14:30:53 -0800121 mMeasuredText.finish();
122 }
123
124 public Builder setText(CharSequence source) {
125 return setText(source, 0, source.length());
126 }
127
Raph Levien531c30c2015-04-30 16:29:59 -0700128 /**
129 * Set the text. Only useful when re-using the builder, which is done for
130 * the internal implementation of {@link DynamicLayout} but not as part
131 * of normal {@link StaticLayout} usage.
132 *
133 * @param source The text to be laid out, optionally with spans
134 * @param start The index of the start of the text
135 * @param end The index + 1 of the end of the text
136 * @return this builder, useful for chaining
137 *
138 * @hide
139 */
Raph Leviend3ab6922015-03-02 14:30:53 -0800140 public Builder setText(CharSequence source, int start, int end) {
141 mText = source;
142 mStart = start;
143 mEnd = end;
144 return this;
145 }
146
Raph Levien531c30c2015-04-30 16:29:59 -0700147 /**
148 * Set the paint. Internal for reuse cases only.
149 *
150 * @param paint The base paint used for layout
151 * @return this builder, useful for chaining
152 *
153 * @hide
154 */
Raph Leviend3ab6922015-03-02 14:30:53 -0800155 public Builder setPaint(TextPaint paint) {
156 mPaint = paint;
157 return this;
158 }
159
Raph Levien531c30c2015-04-30 16:29:59 -0700160 /**
161 * Set the width. Internal for reuse cases only.
162 *
163 * @param width The width in pixels
164 * @return this builder, useful for chaining
165 *
166 * @hide
167 */
Raph Leviend3ab6922015-03-02 14:30:53 -0800168 public Builder setWidth(int width) {
169 mWidth = width;
170 if (mEllipsize == null) {
171 mEllipsizedWidth = width;
172 }
173 return this;
174 }
175
Raph Levien531c30c2015-04-30 16:29:59 -0700176 /**
177 * Set the alignment. The default is {@link Layout.Alignment#ALIGN_NORMAL}.
178 *
179 * @param alignment Alignment for the resulting {@link StaticLayout}
180 * @return this builder, useful for chaining
181 */
Raph Levien39b4db72015-03-25 13:18:20 -0700182 public Builder setAlignment(Alignment alignment) {
183 mAlignment = alignment;
184 return this;
185 }
186
Raph Levien531c30c2015-04-30 16:29:59 -0700187 /**
188 * Set the text direction heuristic. The text direction heuristic is used to
189 * resolve text direction based per-paragraph based on the input text. The default is
190 * {@link TextDirectionHeuristics#FIRSTSTRONG_LTR}.
191 *
192 * @param textDir text direction heuristic for resolving BiDi behavior.
193 * @return this builder, useful for chaining
194 */
Raph Leviena6a08282015-06-03 13:20:45 -0700195 public Builder setTextDirection(TextDirectionHeuristic textDir) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800196 mTextDir = textDir;
197 return this;
198 }
199
Raph Levien531c30c2015-04-30 16:29:59 -0700200 /**
201 * Set line spacing parameters. The default is 0.0 for {@code spacingAdd}
202 * and 1.0 for {@code spacingMult}.
203 *
204 * @param spacingAdd line spacing add
205 * @param spacingMult line spacing multiplier
206 * @return this builder, useful for chaining
207 * @see android.widget.TextView#setLineSpacing
208 */
209 public Builder setLineSpacing(float spacingAdd, float spacingMult) {
210 mSpacingAdd = spacingAdd;
Raph Leviend3ab6922015-03-02 14:30:53 -0800211 mSpacingMult = spacingMult;
212 return this;
213 }
214
Raph Levien531c30c2015-04-30 16:29:59 -0700215 /**
216 * Set whether to include extra space beyond font ascent and descent (which is
217 * needed to avoid clipping in some languages, such as Arabic and Kannada). The
218 * default is {@code true}.
219 *
220 * @param includePad whether to include padding
221 * @return this builder, useful for chaining
222 * @see android.widget.TextView#setIncludeFontPadding
223 */
Raph Leviend3ab6922015-03-02 14:30:53 -0800224 public Builder setIncludePad(boolean includePad) {
225 mIncludePad = includePad;
226 return this;
227 }
228
Raph Levien531c30c2015-04-30 16:29:59 -0700229 /**
230 * Set the width as used for ellipsizing purposes, if it differs from the
231 * normal layout width. The default is the {@code width}
232 * passed to {@link #obtain}.
233 *
234 * @param ellipsizedWidth width used for ellipsizing, in pixels
235 * @return this builder, useful for chaining
236 * @see android.widget.TextView#setEllipsize
237 */
Raph Leviend3ab6922015-03-02 14:30:53 -0800238 public Builder setEllipsizedWidth(int ellipsizedWidth) {
239 mEllipsizedWidth = ellipsizedWidth;
240 return this;
241 }
242
Raph Levien531c30c2015-04-30 16:29:59 -0700243 /**
244 * Set ellipsizing on the layout. Causes words that are longer than the view
245 * is wide, or exceeding the number of lines (see #setMaxLines) in the case
246 * of {@link android.text.TextUtils.TruncateAt#END} or
247 * {@link android.text.TextUtils.TruncateAt#MARQUEE}, to be ellipsized instead
248 * of broken. The default is
249 * {@code null}, indicating no ellipsis is to be applied.
250 *
251 * @param ellipsize type of ellipsis behavior
252 * @return this builder, useful for chaining
253 * @see android.widget.TextView#setEllipsize
254 */
255 public Builder setEllipsize(@Nullable TextUtils.TruncateAt ellipsize) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800256 mEllipsize = ellipsize;
257 return this;
258 }
259
Raph Levien531c30c2015-04-30 16:29:59 -0700260 /**
261 * Set maximum number of lines. This is particularly useful in the case of
262 * ellipsizing, where it changes the layout of the last line. The default is
263 * unlimited.
264 *
265 * @param maxLines maximum number of lines in the layout
266 * @return this builder, useful for chaining
267 * @see android.widget.TextView#setMaxLines
268 */
Raph Leviend3ab6922015-03-02 14:30:53 -0800269 public Builder setMaxLines(int maxLines) {
270 mMaxLines = maxLines;
271 return this;
272 }
273
Raph Levien531c30c2015-04-30 16:29:59 -0700274 /**
275 * Set break strategy, useful for selecting high quality or balanced paragraph
276 * layout options. The default is {@link Layout#BREAK_STRATEGY_SIMPLE}.
277 *
278 * @param breakStrategy break strategy for paragraph layout
279 * @return this builder, useful for chaining
280 * @see android.widget.TextView#setBreakStrategy
281 */
Raph Levien39b4db72015-03-25 13:18:20 -0700282 public Builder setBreakStrategy(@BreakStrategy int breakStrategy) {
283 mBreakStrategy = breakStrategy;
284 return this;
285 }
286
Raph Levien531c30c2015-04-30 16:29:59 -0700287 /**
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700288 * Set hyphenation frequency, to control the amount of automatic hyphenation used. The
289 * default is {@link Layout#HYPHENATION_FREQUENCY_NONE}.
290 *
291 * @param hyphenationFrequency hyphenation frequency for the paragraph
292 * @return this builder, useful for chaining
293 * @see android.widget.TextView#setHyphenationFrequency
294 */
295 public Builder setHyphenationFrequency(@HyphenationFrequency int hyphenationFrequency) {
296 mHyphenationFrequency = hyphenationFrequency;
297 return this;
298 }
299
300 /**
Raph Levien531c30c2015-04-30 16:29:59 -0700301 * Set indents. Arguments are arrays holding an indent amount, one per line, measured in
302 * pixels. For lines past the last element in the array, the last element repeats.
303 *
304 * @param leftIndents array of indent values for left margin, in pixels
305 * @param rightIndents array of indent values for right margin, in pixels
306 * @return this builder, useful for chaining
Raph Levien531c30c2015-04-30 16:29:59 -0700307 */
Raph Leviene319d5a2015-04-14 23:51:07 -0700308 public Builder setIndents(int[] leftIndents, int[] rightIndents) {
Raph Levien2ea52902015-07-01 14:39:31 -0700309 mLeftIndents = leftIndents;
310 mRightIndents = rightIndents;
Raph Leviene319d5a2015-04-14 23:51:07 -0700311 int leftLen = leftIndents == null ? 0 : leftIndents.length;
312 int rightLen = rightIndents == null ? 0 : rightIndents.length;
313 int[] indents = new int[Math.max(leftLen, rightLen)];
314 for (int i = 0; i < indents.length; i++) {
315 int leftMargin = i < leftLen ? leftIndents[i] : 0;
316 int rightMargin = i < rightLen ? rightIndents[i] : 0;
317 indents[i] = leftMargin + rightMargin;
318 }
319 nSetIndents(mNativePtr, indents);
320 return this;
321 }
322
Raph Levien70616ec2015-03-04 10:41:30 -0800323 /**
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700324 * Set paragraph justification mode. The default value is
325 * {@link Layout#JUSTIFICATION_MODE_NONE}. If the last line is too short for justification,
326 * the last line will be displayed with the alignment set by {@link #setAlignment}.
Seigo Nonaka09da71a2016-11-28 16:24:14 +0900327 *
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700328 * @param justificationMode justification mode for the paragraph.
Seigo Nonaka09da71a2016-11-28 16:24:14 +0900329 * @return this builder, useful for chaining.
330 */
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700331 public Builder setJustificationMode(@JustificationMode int justificationMode) {
332 mJustificationMode = justificationMode;
Seigo Nonaka09da71a2016-11-28 16:24:14 +0900333 return this;
334 }
335
336 /**
Raph Levien70616ec2015-03-04 10:41:30 -0800337 * Measurement and break iteration is done in native code. The protocol for using
338 * the native code is as follows.
339 *
Raph Levien26d443a2015-03-30 14:18:32 -0700340 * For each paragraph, do a nSetupParagraph, which sets paragraph text, line width, tab
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700341 * stops, break strategy, and hyphenation frequency (and possibly other parameters in the
342 * future).
Raph Levienc94f7422015-03-06 19:19:48 -0800343 *
344 * Then, for each run within the paragraph:
Raph Levien70616ec2015-03-04 10:41:30 -0800345 * - setLocale (this must be done at least for the first run, optional afterwards)
346 * - one of the following, depending on the type of run:
347 * + addStyleRun (a text run, to be measured in native code)
348 * + addMeasuredRun (a run already measured in Java, passed into native code)
349 * + addReplacementRun (a replacement run, width is given)
350 *
351 * After measurement, nGetWidths() is valid if the widths are needed (eg for ellipsis).
352 * Run nComputeLineBreaks() to obtain line breaks for the paragraph.
353 *
354 * After all paragraphs, call finish() to release expensive buffers.
355 */
356
357 private void setLocale(Locale locale) {
Raph Levien4c1f12e2015-03-02 16:29:23 -0800358 if (!locale.equals(mLocale)) {
Roozbeh Pournadere7eac6f2015-08-07 15:13:30 -0700359 nSetLocale(mNativePtr, locale.toLanguageTag(),
360 Hyphenator.get(locale).getNativePtr());
Raph Levien4c1f12e2015-03-02 16:29:23 -0800361 mLocale = locale;
362 }
363 }
364
Raph Levien70616ec2015-03-04 10:41:30 -0800365 /* package */ float addStyleRun(TextPaint paint, int start, int end, boolean isRtl) {
366 return nAddStyleRun(mNativePtr, paint.getNativeInstance(), paint.mNativeTypeface,
367 start, end, isRtl);
368 }
369
370 /* package */ void addMeasuredRun(int start, int end, float[] widths) {
371 nAddMeasuredRun(mNativePtr, start, end, widths);
372 }
373
374 /* package */ void addReplacementRun(int start, int end, float width) {
375 nAddReplacementRun(mNativePtr, start, end, width);
376 }
377
Raph Levien531c30c2015-04-30 16:29:59 -0700378 /**
379 * Build the {@link StaticLayout} after options have been set.
380 *
381 * <p>Note: the builder object must not be reused in any way after calling this
382 * method. Setting parameters after calling this method, or calling it a second
383 * time on the same builder object, will likely lead to unexpected results.
384 *
385 * @return the newly constructed {@link StaticLayout} object
386 */
Raph Leviend3ab6922015-03-02 14:30:53 -0800387 public StaticLayout build() {
Raph Levien39b4db72015-03-25 13:18:20 -0700388 StaticLayout result = new StaticLayout(this);
389 Builder.recycle(this);
Raph Leviend3ab6922015-03-02 14:30:53 -0800390 return result;
391 }
392
Raph Levien4c1f12e2015-03-02 16:29:23 -0800393 @Override
394 protected void finalize() throws Throwable {
395 try {
396 nFreeBuilder(mNativePtr);
397 } finally {
398 super.finalize();
399 }
400 }
401
402 /* package */ long mNativePtr;
403
Raph Leviend3ab6922015-03-02 14:30:53 -0800404 CharSequence mText;
405 int mStart;
406 int mEnd;
407 TextPaint mPaint;
408 int mWidth;
Raph Levien39b4db72015-03-25 13:18:20 -0700409 Alignment mAlignment;
Raph Leviend3ab6922015-03-02 14:30:53 -0800410 TextDirectionHeuristic mTextDir;
411 float mSpacingMult;
412 float mSpacingAdd;
413 boolean mIncludePad;
414 int mEllipsizedWidth;
415 TextUtils.TruncateAt mEllipsize;
416 int mMaxLines;
Raph Levien39b4db72015-03-25 13:18:20 -0700417 int mBreakStrategy;
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700418 int mHyphenationFrequency;
Raph Levien2ea52902015-07-01 14:39:31 -0700419 int[] mLeftIndents;
420 int[] mRightIndents;
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700421 int mJustificationMode;
Raph Leviend3ab6922015-03-02 14:30:53 -0800422
423 Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
424
425 // This will go away and be subsumed by native builder code
426 MeasuredText mMeasuredText;
427
Raph Levien4c1f12e2015-03-02 16:29:23 -0800428 Locale mLocale;
429
Raph Levien39b4db72015-03-25 13:18:20 -0700430 private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<Builder>(3);
Raph Leviend3ab6922015-03-02 14:30:53 -0800431 }
432
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800433 public StaticLayout(CharSequence source, TextPaint paint,
434 int width,
435 Alignment align, float spacingmult, float spacingadd,
436 boolean includepad) {
437 this(source, 0, source.length(), paint, width, align,
438 spacingmult, spacingadd, includepad);
439 }
440
Doug Feltcb3791202011-07-07 11:57:48 -0700441 /**
442 * @hide
443 */
444 public StaticLayout(CharSequence source, TextPaint paint,
445 int width, Alignment align, TextDirectionHeuristic textDir,
446 float spacingmult, float spacingadd,
447 boolean includepad) {
448 this(source, 0, source.length(), paint, width, align, textDir,
449 spacingmult, spacingadd, includepad);
450 }
451
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800452 public StaticLayout(CharSequence source, int bufstart, int bufend,
453 TextPaint paint, int outerwidth,
454 Alignment align,
455 float spacingmult, float spacingadd,
456 boolean includepad) {
457 this(source, bufstart, bufend, paint, outerwidth, align,
458 spacingmult, spacingadd, includepad, null, 0);
459 }
460
Doug Feltcb3791202011-07-07 11:57:48 -0700461 /**
462 * @hide
463 */
464 public StaticLayout(CharSequence source, int bufstart, int bufend,
465 TextPaint paint, int outerwidth,
466 Alignment align, TextDirectionHeuristic textDir,
467 float spacingmult, float spacingadd,
468 boolean includepad) {
469 this(source, bufstart, bufend, paint, outerwidth, align, textDir,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700470 spacingmult, spacingadd, includepad, null, 0, Integer.MAX_VALUE);
Doug Feltcb3791202011-07-07 11:57:48 -0700471}
472
473 public StaticLayout(CharSequence source, int bufstart, int bufend,
474 TextPaint paint, int outerwidth,
475 Alignment align,
476 float spacingmult, float spacingadd,
477 boolean includepad,
478 TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
479 this(source, bufstart, bufend, paint, outerwidth, align,
480 TextDirectionHeuristics.FIRSTSTRONG_LTR,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700481 spacingmult, spacingadd, includepad, ellipsize, ellipsizedWidth, Integer.MAX_VALUE);
Doug Feltcb3791202011-07-07 11:57:48 -0700482 }
483
484 /**
485 * @hide
486 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800487 public StaticLayout(CharSequence source, int bufstart, int bufend,
488 TextPaint paint, int outerwidth,
Doug Feltcb3791202011-07-07 11:57:48 -0700489 Alignment align, TextDirectionHeuristic textDir,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800490 float spacingmult, float spacingadd,
491 boolean includepad,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700492 TextUtils.TruncateAt ellipsize, int ellipsizedWidth, int maxLines) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800493 super((ellipsize == null)
Doug Felt4e0c5e52010-03-15 16:56:02 -0700494 ? source
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800495 : (source instanceof Spanned)
496 ? new SpannedEllipsizer(source)
497 : new Ellipsizer(source),
Doug Feltcb3791202011-07-07 11:57:48 -0700498 paint, outerwidth, align, textDir, spacingmult, spacingadd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800499
Raph Levienebd66ca2015-04-30 15:27:57 -0700500 Builder b = Builder.obtain(source, bufstart, bufend, paint, outerwidth)
Raph Levien39b4db72015-03-25 13:18:20 -0700501 .setAlignment(align)
Raph Leviena6a08282015-06-03 13:20:45 -0700502 .setTextDirection(textDir)
Raph Levien531c30c2015-04-30 16:29:59 -0700503 .setLineSpacing(spacingadd, spacingmult)
Raph Leviend3ab6922015-03-02 14:30:53 -0800504 .setIncludePad(includepad)
505 .setEllipsizedWidth(ellipsizedWidth)
506 .setEllipsize(ellipsize)
507 .setMaxLines(maxLines);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800508 /*
509 * This is annoying, but we can't refer to the layout until
510 * superclass construction is finished, and the superclass
511 * constructor wants the reference to the display text.
Doug Felt4e0c5e52010-03-15 16:56:02 -0700512 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800513 * This will break if the superclass constructor ever actually
514 * cares about the content instead of just holding the reference.
515 */
516 if (ellipsize != null) {
517 Ellipsizer e = (Ellipsizer) getText();
518
519 e.mLayout = this;
520 e.mWidth = ellipsizedWidth;
521 e.mMethod = ellipsize;
522 mEllipsizedWidth = ellipsizedWidth;
523
524 mColumns = COLUMNS_ELLIPSIZE;
525 } else {
526 mColumns = COLUMNS_NORMAL;
527 mEllipsizedWidth = outerwidth;
528 }
529
Adam Lesinski776abc22014-03-07 11:30:59 -0500530 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2 * mColumns);
531 mLines = new int[mLineDirections.length];
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700532 mMaximumVisibleLineCount = maxLines;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800533
Raph Levien70616ec2015-03-04 10:41:30 -0800534 generate(b, b.mIncludePad, b.mIncludePad);
Doug Felte8e45f22010-03-29 14:58:40 -0700535
Raph Leviend3ab6922015-03-02 14:30:53 -0800536 Builder.recycle(b);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800537 }
538
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700539 /* package */ StaticLayout(CharSequence text) {
540 super(text, null, 0, null, 0, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800541
542 mColumns = COLUMNS_ELLIPSIZE;
Adam Lesinski776abc22014-03-07 11:30:59 -0500543 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2 * mColumns);
544 mLines = new int[mLineDirections.length];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800545 }
546
Raph Levien39b4db72015-03-25 13:18:20 -0700547 private StaticLayout(Builder b) {
548 super((b.mEllipsize == null)
549 ? b.mText
550 : (b.mText instanceof Spanned)
551 ? new SpannedEllipsizer(b.mText)
552 : new Ellipsizer(b.mText),
553 b.mPaint, b.mWidth, b.mAlignment, b.mSpacingMult, b.mSpacingAdd);
554
555 if (b.mEllipsize != null) {
556 Ellipsizer e = (Ellipsizer) getText();
557
558 e.mLayout = this;
559 e.mWidth = b.mEllipsizedWidth;
560 e.mMethod = b.mEllipsize;
561 mEllipsizedWidth = b.mEllipsizedWidth;
562
563 mColumns = COLUMNS_ELLIPSIZE;
564 } else {
565 mColumns = COLUMNS_NORMAL;
566 mEllipsizedWidth = b.mWidth;
567 }
568
569 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2 * mColumns);
570 mLines = new int[mLineDirections.length];
571 mMaximumVisibleLineCount = b.mMaxLines;
572
Raph Levien2ea52902015-07-01 14:39:31 -0700573 mLeftIndents = b.mLeftIndents;
574 mRightIndents = b.mRightIndents;
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700575 setJustificationMode(b.mJustificationMode);
Raph Levien2ea52902015-07-01 14:39:31 -0700576
Raph Levien39b4db72015-03-25 13:18:20 -0700577 generate(b, b.mIncludePad, b.mIncludePad);
578 }
579
Raph Leviend3ab6922015-03-02 14:30:53 -0800580 /* package */ void generate(Builder b, boolean includepad, boolean trackpad) {
581 CharSequence source = b.mText;
582 int bufStart = b.mStart;
583 int bufEnd = b.mEnd;
584 TextPaint paint = b.mPaint;
585 int outerWidth = b.mWidth;
586 TextDirectionHeuristic textDir = b.mTextDir;
587 float spacingmult = b.mSpacingMult;
588 float spacingadd = b.mSpacingAdd;
589 float ellipsizedWidth = b.mEllipsizedWidth;
590 TextUtils.TruncateAt ellipsize = b.mEllipsize;
Raph Levien4c1f12e2015-03-02 16:29:23 -0800591 LineBreaks lineBreaks = new LineBreaks(); // TODO: move to builder to avoid allocation costs
Anish Athalyec8f9e622014-07-21 15:26:34 -0700592 // store span end locations
593 int[] spanEndCache = new int[4];
594 // store fontMetrics per span range
595 // must be a multiple of 4 (and > 0) (store top, bottom, ascent, and descent per range)
596 int[] fmCache = new int[4 * 4];
Raph Levien4c1f12e2015-03-02 16:29:23 -0800597 b.setLocale(paint.getTextLocale()); // TODO: also respect LocaleSpan within the text
Anish Athalye88b5b0b2014-06-24 14:39:43 -0700598
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800599 mLineCount = 0;
600
601 int v = 0;
602 boolean needMultiply = (spacingmult != 1 || spacingadd != 0);
603
Raph Leviend3ab6922015-03-02 14:30:53 -0800604 Paint.FontMetricsInt fm = b.mFontMetricsInt;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800605 int[] chooseHtv = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800606
Raph Leviend3ab6922015-03-02 14:30:53 -0800607 MeasuredText measured = b.mMeasuredText;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800608
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800609 Spanned spanned = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800610 if (source instanceof Spanned)
611 spanned = (Spanned) source;
612
Doug Felte8e45f22010-03-29 14:58:40 -0700613 int paraEnd;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800614 for (int paraStart = bufStart; paraStart <= bufEnd; paraStart = paraEnd) {
615 paraEnd = TextUtils.indexOf(source, CHAR_NEW_LINE, paraStart, bufEnd);
Doug Felte8e45f22010-03-29 14:58:40 -0700616 if (paraEnd < 0)
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800617 paraEnd = bufEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800618 else
Doug Felte8e45f22010-03-29 14:58:40 -0700619 paraEnd++;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800620
Anish Athalyec8f9e622014-07-21 15:26:34 -0700621 int firstWidthLineCount = 1;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800622 int firstWidth = outerWidth;
623 int restWidth = outerWidth;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800624
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800625 LineHeightSpan[] chooseHt = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800626
627 if (spanned != null) {
Eric Fischer74d31ef2010-08-05 15:29:36 -0700628 LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
Doug Felte8e45f22010-03-29 14:58:40 -0700629 LeadingMarginSpan.class);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800630 for (int i = 0; i < sp.length; i++) {
Mark Wagner7b5676e2009-10-16 11:44:23 -0700631 LeadingMarginSpan lms = sp[i];
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800632 firstWidth -= sp[i].getLeadingMargin(true);
633 restWidth -= sp[i].getLeadingMargin(false);
Doug Feltcb3791202011-07-07 11:57:48 -0700634
Doug Feltc982f602010-05-25 11:51:40 -0700635 // LeadingMarginSpan2 is odd. The count affects all
Anish Athalyeab08c6d2014-08-08 12:09:58 -0700636 // leading margin spans, not just this particular one
Doug Feltc982f602010-05-25 11:51:40 -0700637 if (lms instanceof LeadingMarginSpan2) {
638 LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700639 firstWidthLineCount = Math.max(firstWidthLineCount,
640 lms2.getLeadingMarginLineCount());
Mark Wagner7b5676e2009-10-16 11:44:23 -0700641 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800642 }
643
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800644 chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800645
Roozbeh Pournader431e5062015-10-16 02:27:03 -0700646 if (chooseHt.length == 0) {
647 chooseHt = null; // So that out() would not assume it has any contents
648 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800649 if (chooseHtv == null ||
650 chooseHtv.length < chooseHt.length) {
Adam Lesinski776abc22014-03-07 11:30:59 -0500651 chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800652 }
653
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800654 for (int i = 0; i < chooseHt.length; i++) {
655 int o = spanned.getSpanStart(chooseHt[i]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800656
Doug Felte8e45f22010-03-29 14:58:40 -0700657 if (o < paraStart) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800658 // starts in this layout, before the
659 // current paragraph
660
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800661 chooseHtv[i] = getLineTop(getLineForOffset(o));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800662 } else {
663 // starts in this paragraph
664
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800665 chooseHtv[i] = v;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800666 }
667 }
668 }
669 }
670
Raph Levien70616ec2015-03-04 10:41:30 -0800671 measured.setPara(source, paraStart, paraEnd, textDir, b);
Doug Felte8e45f22010-03-29 14:58:40 -0700672 char[] chs = measured.mChars;
673 float[] widths = measured.mWidths;
674 byte[] chdirs = measured.mLevels;
675 int dir = measured.mDir;
676 boolean easy = measured.mEasy;
Raph Levienc94f7422015-03-06 19:19:48 -0800677
678 // tab stop locations
679 int[] variableTabStops = null;
680 if (spanned != null) {
681 TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
682 paraEnd, TabStopSpan.class);
683 if (spans.length > 0) {
684 int[] stops = new int[spans.length];
685 for (int i = 0; i < spans.length; i++) {
686 stops[i] = spans[i].getTabStop();
687 }
688 Arrays.sort(stops, 0, stops.length);
689 variableTabStops = stops;
690 }
691 }
692
Raph Levienc94f7422015-03-06 19:19:48 -0800693 nSetupParagraph(b.mNativePtr, chs, paraEnd - paraStart,
694 firstWidth, firstWidthLineCount, restWidth,
Seigo Nonaka09da71a2016-11-28 16:24:14 +0900695 variableTabStops, TAB_INCREMENT, b.mBreakStrategy, b.mHyphenationFrequency,
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700696 // TODO: Support more justification mode, e.g. letter spacing, stretching.
697 b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE);
Raph Levien2ea52902015-07-01 14:39:31 -0700698 if (mLeftIndents != null || mRightIndents != null) {
699 // TODO(raph) performance: it would be better to do this once per layout rather
700 // than once per paragraph, but that would require a change to the native
701 // interface.
702 int leftLen = mLeftIndents == null ? 0 : mLeftIndents.length;
703 int rightLen = mRightIndents == null ? 0 : mRightIndents.length;
Siyamed Sinirf9a08862016-04-12 19:30:44 -0700704 int indentsLen = Math.max(1, Math.max(leftLen, rightLen) - mLineCount);
Raph Levien2ea52902015-07-01 14:39:31 -0700705 int[] indents = new int[indentsLen];
706 for (int i = 0; i < indentsLen; i++) {
707 int leftMargin = mLeftIndents == null ? 0 :
708 mLeftIndents[Math.min(i + mLineCount, leftLen - 1)];
709 int rightMargin = mRightIndents == null ? 0 :
710 mRightIndents[Math.min(i + mLineCount, rightLen - 1)];
711 indents[i] = leftMargin + rightMargin;
712 }
713 nSetIndents(b.mNativePtr, indents);
714 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800715
Anish Athalyec8f9e622014-07-21 15:26:34 -0700716 // measurement has to be done before performing line breaking
717 // but we don't want to recompute fontmetrics or span ranges the
718 // second time, so we cache those and then use those stored values
719 int fmCacheCount = 0;
720 int spanEndCacheCount = 0;
Gilles Debunnecd943a72012-06-07 17:54:47 -0700721 for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
Anish Athalyec8f9e622014-07-21 15:26:34 -0700722 if (fmCacheCount * 4 >= fmCache.length) {
723 int[] grow = new int[fmCacheCount * 4 * 2];
724 System.arraycopy(fmCache, 0, grow, 0, fmCacheCount * 4);
725 fmCache = grow;
726 }
727
728 if (spanEndCacheCount >= spanEndCache.length) {
729 int[] grow = new int[spanEndCacheCount * 2];
730 System.arraycopy(spanEndCache, 0, grow, 0, spanEndCacheCount);
731 spanEndCache = grow;
732 }
Doug Felte8e45f22010-03-29 14:58:40 -0700733
Gilles Debunnecd943a72012-06-07 17:54:47 -0700734 if (spanned == null) {
735 spanEnd = paraEnd;
Doug Felt23241882010-06-02 14:41:06 -0700736 int spanLen = spanEnd - spanStart;
Gilles Debunnecd943a72012-06-07 17:54:47 -0700737 measured.addStyleRun(paint, spanLen, fm);
738 } else {
739 spanEnd = spanned.nextSpanTransition(spanStart, paraEnd,
740 MetricAffectingSpan.class);
741 int spanLen = spanEnd - spanStart;
742 MetricAffectingSpan[] spans =
Doug Felt23241882010-06-02 14:41:06 -0700743 spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class);
Gilles Debunnecd943a72012-06-07 17:54:47 -0700744 spans = TextUtils.removeEmptySpans(spans, spanned, MetricAffectingSpan.class);
745 measured.addStyleRun(paint, spans, spanLen, fm);
Doug Felt23241882010-06-02 14:41:06 -0700746 }
747
Anish Athalyec8f9e622014-07-21 15:26:34 -0700748 // the order of storage here (top, bottom, ascent, descent) has to match the code below
749 // where these values are retrieved
750 fmCache[fmCacheCount * 4 + 0] = fm.top;
751 fmCache[fmCacheCount * 4 + 1] = fm.bottom;
752 fmCache[fmCacheCount * 4 + 2] = fm.ascent;
753 fmCache[fmCacheCount * 4 + 3] = fm.descent;
754 fmCacheCount++;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800755
Anish Athalyec8f9e622014-07-21 15:26:34 -0700756 spanEndCache[spanEndCacheCount] = spanEnd;
757 spanEndCacheCount++;
758 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800759
Raph Levien70616ec2015-03-04 10:41:30 -0800760 nGetWidths(b.mNativePtr, widths);
Raph Levienc94f7422015-03-06 19:19:48 -0800761 int breakCount = nComputeLineBreaks(b.mNativePtr, lineBreaks, lineBreaks.breaks,
762 lineBreaks.widths, lineBreaks.flags, lineBreaks.breaks.length);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800763
Anish Athalyec8f9e622014-07-21 15:26:34 -0700764 int[] breaks = lineBreaks.breaks;
765 float[] lineWidths = lineBreaks.widths;
Raph Levien26d443a2015-03-30 14:18:32 -0700766 int[] flags = lineBreaks.flags;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700767
Keisuke Kuroyanagif4a3f3a2015-06-10 09:37:32 +0900768 final int remainingLineCount = mMaximumVisibleLineCount - mLineCount;
769 final boolean ellipsisMayBeApplied = ellipsize != null
770 && (ellipsize == TextUtils.TruncateAt.END
771 || (mMaximumVisibleLineCount == 1
772 && ellipsize != TextUtils.TruncateAt.MARQUEE));
Raph Levien04a84552015-07-09 15:38:04 -0700773 if (remainingLineCount > 0 && remainingLineCount < breakCount &&
774 ellipsisMayBeApplied) {
Keisuke Kuroyanagif4a3f3a2015-06-10 09:37:32 +0900775 // Calculate width and flag.
776 float width = 0;
777 int flag = 0;
778 for (int i = remainingLineCount - 1; i < breakCount; i++) {
Keisuke Kuroyanagi78f0d832016-05-10 12:21:33 -0700779 if (i == breakCount - 1) {
780 width += lineWidths[i];
781 } else {
782 for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) {
783 width += widths[j];
784 }
785 }
Keisuke Kuroyanagif4a3f3a2015-06-10 09:37:32 +0900786 flag |= flags[i] & TAB_MASK;
787 }
Keisuke Kuroyanagi78f0d832016-05-10 12:21:33 -0700788 // Treat the last line and overflowed lines as a single line.
789 breaks[remainingLineCount - 1] = breaks[breakCount - 1];
Keisuke Kuroyanagif4a3f3a2015-06-10 09:37:32 +0900790 lineWidths[remainingLineCount - 1] = width;
791 flags[remainingLineCount - 1] = flag;
792
793 breakCount = remainingLineCount;
794 }
795
Anish Athalyec8f9e622014-07-21 15:26:34 -0700796 // here is the offset of the starting character of the line we are currently measuring
797 int here = paraStart;
798
799 int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
800 int fmCacheIndex = 0;
801 int spanEndCacheIndex = 0;
802 int breakIndex = 0;
803 for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
804 // retrieve end of span
805 spanEnd = spanEndCache[spanEndCacheIndex++];
806
807 // retrieve cached metrics, order matches above
808 fm.top = fmCache[fmCacheIndex * 4 + 0];
809 fm.bottom = fmCache[fmCacheIndex * 4 + 1];
810 fm.ascent = fmCache[fmCacheIndex * 4 + 2];
811 fm.descent = fmCache[fmCacheIndex * 4 + 3];
812 fmCacheIndex++;
813
814 if (fm.top < fmTop) {
815 fmTop = fm.top;
816 }
817 if (fm.ascent < fmAscent) {
818 fmAscent = fm.ascent;
819 }
820 if (fm.descent > fmDescent) {
821 fmDescent = fm.descent;
822 }
823 if (fm.bottom > fmBottom) {
824 fmBottom = fm.bottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800825 }
826
Anish Athalyec8f9e622014-07-21 15:26:34 -0700827 // skip breaks ending before current span range
828 while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
829 breakIndex++;
830 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800831
Anish Athalyec8f9e622014-07-21 15:26:34 -0700832 while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
833 int endPos = paraStart + breaks[breakIndex];
834
Raph Levience4155a2015-03-11 11:02:33 -0700835 boolean moreChars = (endPos < bufEnd);
Raph Levien4c02e832014-12-12 11:17:01 -0800836
Anish Athalyec8f9e622014-07-21 15:26:34 -0700837 v = out(source, here, endPos,
838 fmAscent, fmDescent, fmTop, fmBottom,
Roozbeh Pournader431e5062015-10-16 02:27:03 -0700839 v, spacingmult, spacingadd, chooseHt, chooseHtv, fm, flags[breakIndex],
Anish Athalyec8f9e622014-07-21 15:26:34 -0700840 needMultiply, chdirs, dir, easy, bufEnd, includepad, trackpad,
841 chs, widths, paraStart, ellipsize, ellipsizedWidth,
Raph Levien4c02e832014-12-12 11:17:01 -0800842 lineWidths[breakIndex], paint, moreChars);
Anish Athalyec8f9e622014-07-21 15:26:34 -0700843
844 if (endPos < spanEnd) {
845 // preserve metrics for current span
846 fmTop = fm.top;
847 fmBottom = fm.bottom;
848 fmAscent = fm.ascent;
849 fmDescent = fm.descent;
850 } else {
851 fmTop = fmBottom = fmAscent = fmDescent = 0;
852 }
853
854 here = endPos;
855 breakIndex++;
856
Siyamed Sinir0745c722016-05-31 20:39:33 -0700857 if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) {
Anish Athalyec8f9e622014-07-21 15:26:34 -0700858 return;
859 }
860 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800861 }
862
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800863 if (paraEnd == bufEnd)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800864 break;
865 }
866
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700867 if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE) &&
Fabrice Di Meglioad0b0512011-10-04 17:21:26 -0700868 mLineCount < mMaximumVisibleLineCount) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800869 // Log.e("text", "output last " + bufEnd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800870
Raph Levien70616ec2015-03-04 10:41:30 -0800871 measured.setPara(source, bufEnd, bufEnd, textDir, b);
Fabrice Di Meglioe6318892013-06-18 20:03:41 -0700872
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800873 paint.getFontMetricsInt(fm);
874
875 v = out(source,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800876 bufEnd, bufEnd, fm.ascent, fm.descent,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800877 fm.top, fm.bottom,
878 v,
879 spacingmult, spacingadd, null,
Raph Levien26d443a2015-03-30 14:18:32 -0700880 null, fm, 0,
Fabrice Di Meglioe6318892013-06-18 20:03:41 -0700881 needMultiply, measured.mLevels, measured.mDir, measured.mEasy, bufEnd,
Gilles Debunned300e752011-10-17 13:37:36 -0700882 includepad, trackpad, null,
883 null, bufStart, ellipsize,
884 ellipsizedWidth, 0, paint, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800885 }
886 }
887
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800888 private int out(CharSequence text, int start, int end,
889 int above, int below, int top, int bottom, int v,
890 float spacingmult, float spacingadd,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800891 LineHeightSpan[] chooseHt, int[] chooseHtv,
Raph Levien26d443a2015-03-30 14:18:32 -0700892 Paint.FontMetricsInt fm, int flags,
Gilles Debunned300e752011-10-17 13:37:36 -0700893 boolean needMultiply, byte[] chdirs, int dir,
894 boolean easy, int bufEnd, boolean includePad,
895 boolean trackPad, char[] chs,
896 float[] widths, int widthStart, TextUtils.TruncateAt ellipsize,
897 float ellipsisWidth, float textWidth,
898 TextPaint paint, boolean moreChars) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800899 int j = mLineCount;
900 int off = j * mColumns;
901 int want = off + mColumns + TOP;
902 int[] lines = mLines;
903
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800904 if (want >= lines.length) {
Adam Lesinski776abc22014-03-07 11:30:59 -0500905 Directions[] grow2 = ArrayUtils.newUnpaddedArray(
906 Directions.class, GrowingArrayUtils.growSize(want));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800907 System.arraycopy(mLineDirections, 0, grow2, 0,
908 mLineDirections.length);
909 mLineDirections = grow2;
Adam Lesinski776abc22014-03-07 11:30:59 -0500910
911 int[] grow = new int[grow2.length];
912 System.arraycopy(lines, 0, grow, 0, lines.length);
913 mLines = grow;
914 lines = grow;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800915 }
916
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800917 if (chooseHt != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800918 fm.ascent = above;
919 fm.descent = below;
920 fm.top = top;
921 fm.bottom = bottom;
922
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800923 for (int i = 0; i < chooseHt.length; i++) {
924 if (chooseHt[i] instanceof LineHeightSpan.WithDensity) {
925 ((LineHeightSpan.WithDensity) chooseHt[i]).
926 chooseHeight(text, start, end, chooseHtv[i], v, fm, paint);
Eric Fischera9f1dd02009-08-12 15:00:10 -0700927
928 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800929 chooseHt[i].chooseHeight(text, start, end, chooseHtv[i], v, fm);
Eric Fischera9f1dd02009-08-12 15:00:10 -0700930 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800931 }
932
933 above = fm.ascent;
934 below = fm.descent;
935 top = fm.top;
936 bottom = fm.bottom;
937 }
938
Raph Leviend97b0972014-04-24 12:51:35 -0700939 boolean firstLine = (j == 0);
940 boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);
Siyamed Sinir0745c722016-05-31 20:39:33 -0700941
942 if (ellipsize != null) {
943 // If there is only one line, then do any type of ellipsis except when it is MARQUEE
944 // if there are multiple lines, just allow END ellipsis on the last line
945 boolean forceEllipsis = moreChars && (mLineCount + 1 == mMaximumVisibleLineCount);
946
947 boolean doEllipsis =
948 (((mMaximumVisibleLineCount == 1 && moreChars) || (firstLine && !moreChars)) &&
949 ellipsize != TextUtils.TruncateAt.MARQUEE) ||
950 (!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
951 ellipsize == TextUtils.TruncateAt.END);
952 if (doEllipsis) {
953 calculateEllipsis(start, end, widths, widthStart,
954 ellipsisWidth, ellipsize, j,
955 textWidth, paint, forceEllipsis);
956 }
957 }
958
959 boolean lastLine = mEllipsized || (end == bufEnd);
Raph Leviend97b0972014-04-24 12:51:35 -0700960
961 if (firstLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800962 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800963 mTopPadding = top - above;
964 }
965
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800966 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800967 above = top;
968 }
969 }
Raph Leviend97b0972014-04-24 12:51:35 -0700970
971 int extra;
972
973 if (lastLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800974 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800975 mBottomPadding = bottom - below;
976 }
977
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800978 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800979 below = bottom;
980 }
981 }
982
Raph Leviend97b0972014-04-24 12:51:35 -0700983 if (needMultiply && !lastLine) {
Doug Felt10657582010-02-22 11:19:01 -0800984 double ex = (below - above) * (spacingmult - 1) + spacingadd;
985 if (ex >= 0) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800986 extra = (int)(ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -0800987 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800988 extra = -(int)(-ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -0800989 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800990 } else {
991 extra = 0;
992 }
993
994 lines[off + START] = start;
995 lines[off + TOP] = v;
996 lines[off + DESCENT] = below + extra;
997
Siyamed Sinir0745c722016-05-31 20:39:33 -0700998 // special case for non-ellipsized last visible line when maxLines is set
999 // store the height as if it was ellipsized
1000 if (!mEllipsized && currentLineIsTheLastVisibleOne) {
1001 // below calculation as if it was the last line
1002 int maxLineBelow = includePad ? bottom : below;
1003 // similar to the calculation of v below, without the extra.
1004 mMaxLineHeight = v + (maxLineBelow - above);
1005 }
1006
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001007 v += (below - above) + extra;
1008 lines[off + mColumns + START] = end;
1009 lines[off + mColumns + TOP] = v;
1010
Raph Levien26d443a2015-03-30 14:18:32 -07001011 // TODO: could move TAB to share same column as HYPHEN, simplifying this code and gaining
1012 // one bit for start field
1013 lines[off + TAB] |= flags & TAB_MASK;
1014 lines[off + HYPHEN] = flags;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001015
Doug Felt9f7a4442010-03-01 12:45:56 -08001016 lines[off + DIR] |= dir << DIR_SHIFT;
1017 Directions linedirs = DIRS_ALL_LEFT_TO_RIGHT;
1018 // easy means all chars < the first RTL, so no emoji, no nothing
Doug Felt4e0c5e52010-03-15 16:56:02 -07001019 // XXX a run with no text or all spaces is easy but might be an empty
Doug Felt9f7a4442010-03-01 12:45:56 -08001020 // RTL paragraph. Make sure easy is false if this is the case.
1021 if (easy) {
1022 mLineDirections[j] = linedirs;
1023 } else {
Gilles Debunnef3fa0cd2011-02-03 14:17:05 -08001024 mLineDirections[j] = AndroidBidi.directions(dir, chdirs, start - widthStart, chs,
1025 start - widthStart, end - start);
Gilles Debunne0a4db3c2011-01-14 12:12:04 -08001026 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001027
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001028 mLineCount++;
1029 return v;
1030 }
1031
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001032 private void calculateEllipsis(int lineStart, int lineEnd,
1033 float[] widths, int widthStart,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001034 float avail, TextUtils.TruncateAt where,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001035 int line, float textWidth, TextPaint paint,
1036 boolean forceEllipsis) {
Selim Cinek365ec092017-03-09 00:10:52 -08001037 avail -= getTotalInsets(line);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001038 if (textWidth <= avail && !forceEllipsis) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001039 // Everything fits!
1040 mLines[mColumns * line + ELLIPSIS_START] = 0;
1041 mLines[mColumns * line + ELLIPSIS_COUNT] = 0;
1042 return;
1043 }
1044
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001045 float ellipsisWidth = paint.measureText(
Fabrice Di Meglio8d44fff2012-06-13 15:45:38 -07001046 (where == TextUtils.TruncateAt.END_SMALL) ?
Neil Fullerd29bdb22015-02-06 10:03:08 +00001047 TextUtils.ELLIPSIS_TWO_DOTS : TextUtils.ELLIPSIS_NORMAL, 0, 1);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001048 int ellipsisStart = 0;
1049 int ellipsisCount = 0;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001050 int len = lineEnd - lineStart;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001051
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001052 // We only support start ellipsis on a single line
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001053 if (where == TextUtils.TruncateAt.START) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001054 if (mMaximumVisibleLineCount == 1) {
1055 float sum = 0;
1056 int i;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001057
Keisuke Kuroyanagied2eea12015-04-14 18:18:35 +09001058 for (i = len; i > 0; i--) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001059 float w = widths[i - 1 + lineStart - widthStart];
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001060 if (w + sum + ellipsisWidth > avail) {
Keisuke Kuroyanagi82d1c442016-09-14 14:30:14 +09001061 while (i < len && widths[i + lineStart - widthStart] == 0.0f) {
1062 i++;
1063 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001064 break;
1065 }
1066
1067 sum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001068 }
1069
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001070 ellipsisStart = 0;
1071 ellipsisCount = i;
1072 } else {
1073 if (Log.isLoggable(TAG, Log.WARN)) {
1074 Log.w(TAG, "Start Ellipsis only supported with one line");
1075 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001076 }
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001077 } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE ||
1078 where == TextUtils.TruncateAt.END_SMALL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001079 float sum = 0;
1080 int i;
1081
1082 for (i = 0; i < len; i++) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001083 float w = widths[i + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001084
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001085 if (w + sum + ellipsisWidth > avail) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001086 break;
1087 }
1088
1089 sum += w;
1090 }
1091
1092 ellipsisStart = i;
1093 ellipsisCount = len - i;
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -07001094 if (forceEllipsis && ellipsisCount == 0 && len > 0) {
1095 ellipsisStart = len - 1;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001096 ellipsisCount = 1;
1097 }
1098 } else {
1099 // where = TextUtils.TruncateAt.MIDDLE We only support middle ellipsis on a single line
1100 if (mMaximumVisibleLineCount == 1) {
1101 float lsum = 0, rsum = 0;
1102 int left = 0, right = len;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001103
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001104 float ravail = (avail - ellipsisWidth) / 2;
Raph Levien0e3c5e82014-12-04 13:26:07 -08001105 for (right = len; right > 0; right--) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001106 float w = widths[right - 1 + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001107
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001108 if (w + rsum > ravail) {
Keisuke Kuroyanagi82d1c442016-09-14 14:30:14 +09001109 while (right < len && widths[right + lineStart - widthStart] == 0.0f) {
1110 right++;
1111 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001112 break;
1113 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001114 rsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001115 }
1116
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001117 float lavail = avail - ellipsisWidth - rsum;
1118 for (left = 0; left < right; left++) {
1119 float w = widths[left + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001120
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001121 if (w + lsum > lavail) {
1122 break;
1123 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001124
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001125 lsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001126 }
1127
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001128 ellipsisStart = left;
1129 ellipsisCount = right - left;
1130 } else {
1131 if (Log.isLoggable(TAG, Log.WARN)) {
1132 Log.w(TAG, "Middle Ellipsis only supported with one line");
1133 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001134 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001135 }
Siyamed Sinir0745c722016-05-31 20:39:33 -07001136 mEllipsized = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001137 mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
1138 mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
1139 }
1140
Selim Cinek365ec092017-03-09 00:10:52 -08001141 private float getTotalInsets(int line) {
1142 int totalIndent = 0;
1143 if (mLeftIndents != null) {
1144 totalIndent = mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1145 }
1146 if (mRightIndents != null) {
1147 totalIndent += mRightIndents[Math.min(line, mRightIndents.length - 1)];
1148 }
1149 return totalIndent;
1150 }
1151
Doug Felte8e45f22010-03-29 14:58:40 -07001152 // Override the base class so we can directly access our members,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001153 // rather than relying on member functions.
1154 // The logic mirrors that of Layout.getLineForVertical
1155 // FIXME: It may be faster to do a linear search for layouts without many lines.
Gilles Debunne66111472010-11-19 11:04:37 -08001156 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001157 public int getLineForVertical(int vertical) {
1158 int high = mLineCount;
1159 int low = -1;
1160 int guess;
1161 int[] lines = mLines;
1162 while (high - low > 1) {
1163 guess = (high + low) >> 1;
1164 if (lines[mColumns * guess + TOP] > vertical){
1165 high = guess;
1166 } else {
1167 low = guess;
1168 }
1169 }
1170 if (low < 0) {
1171 return 0;
1172 } else {
1173 return low;
1174 }
1175 }
1176
Gilles Debunne66111472010-11-19 11:04:37 -08001177 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001178 public int getLineCount() {
1179 return mLineCount;
1180 }
1181
Gilles Debunne66111472010-11-19 11:04:37 -08001182 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001183 public int getLineTop(int line) {
Raph Levien07e6c232016-04-04 12:34:06 -07001184 return mLines[mColumns * line + TOP];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001185 }
1186
Gilles Debunne66111472010-11-19 11:04:37 -08001187 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001188 public int getLineDescent(int line) {
Raph Levien07e6c232016-04-04 12:34:06 -07001189 return mLines[mColumns * line + DESCENT];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001190 }
1191
Gilles Debunne66111472010-11-19 11:04:37 -08001192 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001193 public int getLineStart(int line) {
1194 return mLines[mColumns * line + START] & START_MASK;
1195 }
1196
Gilles Debunne66111472010-11-19 11:04:37 -08001197 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001198 public int getParagraphDirection(int line) {
1199 return mLines[mColumns * line + DIR] >> DIR_SHIFT;
1200 }
1201
Gilles Debunne66111472010-11-19 11:04:37 -08001202 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001203 public boolean getLineContainsTab(int line) {
1204 return (mLines[mColumns * line + TAB] & TAB_MASK) != 0;
1205 }
1206
Gilles Debunne66111472010-11-19 11:04:37 -08001207 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001208 public final Directions getLineDirections(int line) {
1209 return mLineDirections[line];
1210 }
1211
Gilles Debunne66111472010-11-19 11:04:37 -08001212 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001213 public int getTopPadding() {
1214 return mTopPadding;
1215 }
1216
Gilles Debunne66111472010-11-19 11:04:37 -08001217 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001218 public int getBottomPadding() {
1219 return mBottomPadding;
1220 }
1221
Raph Levien26d443a2015-03-30 14:18:32 -07001222 /**
1223 * @hide
1224 */
1225 @Override
1226 public int getHyphen(int line) {
Keisuke Kuroyanagif5af4a32016-08-31 21:40:53 +09001227 return mLines[mColumns * line + HYPHEN] & HYPHEN_MASK;
Raph Levien26d443a2015-03-30 14:18:32 -07001228 }
1229
Raph Levien2ea52902015-07-01 14:39:31 -07001230 /**
1231 * @hide
1232 */
1233 @Override
1234 public int getIndentAdjust(int line, Alignment align) {
1235 if (align == Alignment.ALIGN_LEFT) {
1236 if (mLeftIndents == null) {
1237 return 0;
1238 } else {
1239 return mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1240 }
1241 } else if (align == Alignment.ALIGN_RIGHT) {
1242 if (mRightIndents == null) {
1243 return 0;
1244 } else {
1245 return -mRightIndents[Math.min(line, mRightIndents.length - 1)];
1246 }
1247 } else if (align == Alignment.ALIGN_CENTER) {
1248 int left = 0;
1249 if (mLeftIndents != null) {
1250 left = mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1251 }
1252 int right = 0;
1253 if (mRightIndents != null) {
1254 right = mRightIndents[Math.min(line, mRightIndents.length - 1)];
1255 }
1256 return (left - right) >> 1;
1257 } else {
1258 throw new AssertionError("unhandled alignment " + align);
1259 }
1260 }
1261
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001262 @Override
1263 public int getEllipsisCount(int line) {
1264 if (mColumns < COLUMNS_ELLIPSIZE) {
1265 return 0;
1266 }
1267
1268 return mLines[mColumns * line + ELLIPSIS_COUNT];
1269 }
1270
1271 @Override
1272 public int getEllipsisStart(int line) {
1273 if (mColumns < COLUMNS_ELLIPSIZE) {
1274 return 0;
1275 }
1276
1277 return mLines[mColumns * line + ELLIPSIS_START];
1278 }
1279
1280 @Override
1281 public int getEllipsizedWidth() {
1282 return mEllipsizedWidth;
1283 }
1284
Siyamed Sinir0745c722016-05-31 20:39:33 -07001285 /**
1286 * Return the total height of this layout.
1287 *
1288 * @param cap if true and max lines is set, returns the height of the layout at the max lines.
1289 *
1290 * @hide
1291 */
1292 public int getHeight(boolean cap) {
1293 if (cap && mLineCount >= mMaximumVisibleLineCount && mMaxLineHeight == -1 &&
1294 Log.isLoggable(TAG, Log.WARN)) {
1295 Log.w(TAG, "maxLineHeight should not be -1. "
1296 + " maxLines:" + mMaximumVisibleLineCount
1297 + " lineCount:" + mLineCount);
1298 }
1299
1300 return cap && mLineCount >= mMaximumVisibleLineCount && mMaxLineHeight != -1 ?
1301 mMaxLineHeight : super.getHeight();
1302 }
1303
Raph Levien70616ec2015-03-04 10:41:30 -08001304 private static native long nNewBuilder();
1305 private static native void nFreeBuilder(long nativePtr);
1306 private static native void nFinishBuilder(long nativePtr);
Raph Levien26d443a2015-03-30 14:18:32 -07001307
Roozbeh Pournadera59c3fe2017-02-27 10:13:44 -08001308 /* package */ static native long nLoadHyphenator(ByteBuffer buf, int offset,
1309 int minPrefix, int minSuffix);
Raph Levien26d443a2015-03-30 14:18:32 -07001310
1311 private static native void nSetLocale(long nativePtr, String locale, long nativeHyphenator);
Raph Levien70616ec2015-03-04 10:41:30 -08001312
Raph Leviene319d5a2015-04-14 23:51:07 -07001313 private static native void nSetIndents(long nativePtr, int[] indents);
1314
Raph Levienc94f7422015-03-06 19:19:48 -08001315 // Set up paragraph text and settings; done as one big method to minimize jni crossings
1316 private static native void nSetupParagraph(long nativePtr, char[] text, int length,
1317 float firstWidth, int firstWidthLineCount, float restWidth,
Seigo Nonaka09da71a2016-11-28 16:24:14 +09001318 int[] variableTabStops, int defaultTabStop, int breakStrategy, int hyphenationFrequency,
1319 boolean isJustified);
Raph Levien70616ec2015-03-04 10:41:30 -08001320
1321 private static native float nAddStyleRun(long nativePtr, long nativePaint,
1322 long nativeTypeface, int start, int end, boolean isRtl);
1323
1324 private static native void nAddMeasuredRun(long nativePtr,
1325 int start, int end, float[] widths);
1326
1327 private static native void nAddReplacementRun(long nativePtr, int start, int end, float width);
1328
1329 private static native void nGetWidths(long nativePtr, float[] widths);
1330
Anish Athalyec8f9e622014-07-21 15:26:34 -07001331 // populates LineBreaks and returns the number of breaks found
1332 //
1333 // the arrays inside the LineBreaks objects are passed in as well
1334 // to reduce the number of JNI calls in the common case where the
1335 // arrays do not have to be resized
Raph Levienc94f7422015-03-06 19:19:48 -08001336 private static native int nComputeLineBreaks(long nativePtr, LineBreaks recycle,
Raph Levien26d443a2015-03-30 14:18:32 -07001337 int[] recycleBreaks, float[] recycleWidths, int[] recycleFlags, int recycleLength);
Anish Athalye88b5b0b2014-06-24 14:39:43 -07001338
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001339 private int mLineCount;
1340 private int mTopPadding, mBottomPadding;
1341 private int mColumns;
1342 private int mEllipsizedWidth;
1343
Siyamed Sinir0745c722016-05-31 20:39:33 -07001344 /**
1345 * Keeps track if ellipsize is applied to the text.
1346 */
1347 private boolean mEllipsized;
1348
1349 /**
1350 * If maxLines is set, ellipsize is not set, and the actual line count of text is greater than
1351 * or equal to maxLine, this variable holds the ideal visual height of the maxLine'th line
1352 * starting from the top of the layout. If maxLines is not set its value will be -1.
1353 *
1354 * The value is the same as getLineTop(maxLines) for ellipsized version where structurally no
1355 * more than maxLines is contained.
1356 */
1357 private int mMaxLineHeight = -1;
1358
Raph Levien26d443a2015-03-30 14:18:32 -07001359 private static final int COLUMNS_NORMAL = 4;
1360 private static final int COLUMNS_ELLIPSIZE = 6;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001361 private static final int START = 0;
1362 private static final int DIR = START;
1363 private static final int TAB = START;
1364 private static final int TOP = 1;
1365 private static final int DESCENT = 2;
Raph Levien26d443a2015-03-30 14:18:32 -07001366 private static final int HYPHEN = 3;
1367 private static final int ELLIPSIS_START = 4;
1368 private static final int ELLIPSIS_COUNT = 5;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001369
1370 private int[] mLines;
1371 private Directions[] mLineDirections;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001372 private int mMaximumVisibleLineCount = Integer.MAX_VALUE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001373
1374 private static final int START_MASK = 0x1FFFFFFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001375 private static final int DIR_SHIFT = 30;
1376 private static final int TAB_MASK = 0x20000000;
Keisuke Kuroyanagif5af4a32016-08-31 21:40:53 +09001377 private static final int HYPHEN_MASK = 0xFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001378
Doug Feltc982f602010-05-25 11:51:40 -07001379 private static final int TAB_INCREMENT = 20; // same as Layout, but that's private
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001380
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001381 private static final char CHAR_NEW_LINE = '\n';
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001382
1383 private static final double EXTRA_ROUNDING = 0.5;
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001384
Anish Athalyec8f9e622014-07-21 15:26:34 -07001385 // This is used to return three arrays from a single JNI call when
1386 // performing line breaking
Deepanshu Gupta70539192014-10-15 15:57:40 -07001387 /*package*/ static class LineBreaks {
Anish Athalyec8f9e622014-07-21 15:26:34 -07001388 private static final int INITIAL_SIZE = 16;
1389 public int[] breaks = new int[INITIAL_SIZE];
1390 public float[] widths = new float[INITIAL_SIZE];
Roozbeh Pournader112d9c72015-08-07 12:44:41 -07001391 public int[] flags = new int[INITIAL_SIZE]; // hasTab
Anish Athalyec8f9e622014-07-21 15:26:34 -07001392 // breaks, widths, and flags should all have the same length
1393 }
1394
Raph Levien2ea52902015-07-01 14:39:31 -07001395 private int[] mLeftIndents;
1396 private int[] mRightIndents;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001397}