blob: 69c04b33dc71b1969cfd80decc9db572ad4217c9 [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;
Roozbeh Pournader737dfea2017-08-10 11:32:24 -070093 b.mFallbackLineSpacing = false;
Raph Levien39b4db72015-03-25 13:18:20 -070094 b.mEllipsizedWidth = width;
Raph Leviend3ab6922015-03-02 14:30:53 -080095 b.mEllipsize = null;
96 b.mMaxLines = Integer.MAX_VALUE;
Raph Levien3bd60c72015-05-06 14:26:35 -070097 b.mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
Roozbeh Pournader95c7a132015-05-12 12:01:06 -070098 b.mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
Seigo Nonaka4b4730d2017-03-31 09:42:16 -070099 b.mJustificationMode = Layout.JUSTIFICATION_MODE_NONE;
Raph Leviend3ab6922015-03-02 14:30:53 -0800100
101 b.mMeasuredText = MeasuredText.obtain();
102 return b;
103 }
104
Raph Levien39b4db72015-03-25 13:18:20 -0700105 private static void recycle(Builder b) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800106 b.mPaint = null;
107 b.mText = null;
108 MeasuredText.recycle(b.mMeasuredText);
Raph Levien3bd60c72015-05-06 14:26:35 -0700109 b.mMeasuredText = null;
Raph Levien2ea52902015-07-01 14:39:31 -0700110 b.mLeftIndents = null;
111 b.mRightIndents = null;
Raph Levien3bd60c72015-05-06 14:26:35 -0700112 nFinishBuilder(b.mNativePtr);
Raph Levien39b4db72015-03-25 13:18:20 -0700113 sPool.release(b);
Raph Leviend3ab6922015-03-02 14:30:53 -0800114 }
115
116 // release any expensive state
117 /* package */ void finish() {
Raph Levien4c1f12e2015-03-02 16:29:23 -0800118 nFinishBuilder(mNativePtr);
Raph Levien22ba7862015-07-29 12:34:13 -0700119 mText = null;
120 mPaint = null;
121 mLeftIndents = null;
122 mRightIndents = null;
Raph Leviend3ab6922015-03-02 14:30:53 -0800123 mMeasuredText.finish();
124 }
125
126 public Builder setText(CharSequence source) {
127 return setText(source, 0, source.length());
128 }
129
Raph Levien531c30c2015-04-30 16:29:59 -0700130 /**
131 * Set the text. Only useful when re-using the builder, which is done for
132 * the internal implementation of {@link DynamicLayout} but not as part
133 * of normal {@link StaticLayout} usage.
134 *
135 * @param source The text to be laid out, optionally with spans
136 * @param start The index of the start of the text
137 * @param end The index + 1 of the end of the text
138 * @return this builder, useful for chaining
139 *
140 * @hide
141 */
Raph Leviend3ab6922015-03-02 14:30:53 -0800142 public Builder setText(CharSequence source, int start, int end) {
143 mText = source;
144 mStart = start;
145 mEnd = end;
146 return this;
147 }
148
Raph Levien531c30c2015-04-30 16:29:59 -0700149 /**
150 * Set the paint. Internal for reuse cases only.
151 *
152 * @param paint The base paint used for layout
153 * @return this builder, useful for chaining
154 *
155 * @hide
156 */
Raph Leviend3ab6922015-03-02 14:30:53 -0800157 public Builder setPaint(TextPaint paint) {
158 mPaint = paint;
159 return this;
160 }
161
Raph Levien531c30c2015-04-30 16:29:59 -0700162 /**
163 * Set the width. Internal for reuse cases only.
164 *
165 * @param width The width in pixels
166 * @return this builder, useful for chaining
167 *
168 * @hide
169 */
Raph Leviend3ab6922015-03-02 14:30:53 -0800170 public Builder setWidth(int width) {
171 mWidth = width;
172 if (mEllipsize == null) {
173 mEllipsizedWidth = width;
174 }
175 return this;
176 }
177
Raph Levien531c30c2015-04-30 16:29:59 -0700178 /**
179 * Set the alignment. The default is {@link Layout.Alignment#ALIGN_NORMAL}.
180 *
181 * @param alignment Alignment for the resulting {@link StaticLayout}
182 * @return this builder, useful for chaining
183 */
Raph Levien39b4db72015-03-25 13:18:20 -0700184 public Builder setAlignment(Alignment alignment) {
185 mAlignment = alignment;
186 return this;
187 }
188
Raph Levien531c30c2015-04-30 16:29:59 -0700189 /**
190 * Set the text direction heuristic. The text direction heuristic is used to
191 * resolve text direction based per-paragraph based on the input text. The default is
192 * {@link TextDirectionHeuristics#FIRSTSTRONG_LTR}.
193 *
194 * @param textDir text direction heuristic for resolving BiDi behavior.
195 * @return this builder, useful for chaining
196 */
Raph Leviena6a08282015-06-03 13:20:45 -0700197 public Builder setTextDirection(TextDirectionHeuristic textDir) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800198 mTextDir = textDir;
199 return this;
200 }
201
Raph Levien531c30c2015-04-30 16:29:59 -0700202 /**
203 * Set line spacing parameters. The default is 0.0 for {@code spacingAdd}
204 * and 1.0 for {@code spacingMult}.
205 *
206 * @param spacingAdd line spacing add
207 * @param spacingMult line spacing multiplier
208 * @return this builder, useful for chaining
209 * @see android.widget.TextView#setLineSpacing
210 */
211 public Builder setLineSpacing(float spacingAdd, float spacingMult) {
212 mSpacingAdd = spacingAdd;
Raph Leviend3ab6922015-03-02 14:30:53 -0800213 mSpacingMult = spacingMult;
214 return this;
215 }
216
Raph Levien531c30c2015-04-30 16:29:59 -0700217 /**
218 * Set whether to include extra space beyond font ascent and descent (which is
219 * needed to avoid clipping in some languages, such as Arabic and Kannada). The
220 * default is {@code true}.
221 *
222 * @param includePad whether to include padding
223 * @return this builder, useful for chaining
224 * @see android.widget.TextView#setIncludeFontPadding
225 */
Raph Leviend3ab6922015-03-02 14:30:53 -0800226 public Builder setIncludePad(boolean includePad) {
227 mIncludePad = includePad;
228 return this;
229 }
230
Raph Levien531c30c2015-04-30 16:29:59 -0700231 /**
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700232 * Set whether to respect the ascent and descent of the fallback fonts that are used in
233 * displaying the text (which is needed to avoid text from consecutive lines running into
234 * each other). If set, fallback fonts that end up getting used can increase the ascent
235 * and descent of the lines that they are used on.
236 *
237 * <p>For backward compatibility reasons, the default is {@code false}, but setting this to
238 * true is strongly recommended. It is required to be true if text could be in languages
239 * like Burmese or Tibetan where text is typically much taller or deeper than Latin text.
240 *
241 * @param useLineSpacingFromFallbacks whether to expand linespacing based on fallback fonts
242 * @return this builder, useful for chaining
243 */
244 public Builder setUseLineSpacingFromFallbacks(boolean useLineSpacingFromFallbacks) {
245 mFallbackLineSpacing = useLineSpacingFromFallbacks;
246 return this;
247 }
248
249 /**
Raph Levien531c30c2015-04-30 16:29:59 -0700250 * Set the width as used for ellipsizing purposes, if it differs from the
251 * normal layout width. The default is the {@code width}
252 * passed to {@link #obtain}.
253 *
254 * @param ellipsizedWidth width used for ellipsizing, in pixels
255 * @return this builder, useful for chaining
256 * @see android.widget.TextView#setEllipsize
257 */
Raph Leviend3ab6922015-03-02 14:30:53 -0800258 public Builder setEllipsizedWidth(int ellipsizedWidth) {
259 mEllipsizedWidth = ellipsizedWidth;
260 return this;
261 }
262
Raph Levien531c30c2015-04-30 16:29:59 -0700263 /**
264 * Set ellipsizing on the layout. Causes words that are longer than the view
265 * is wide, or exceeding the number of lines (see #setMaxLines) in the case
266 * of {@link android.text.TextUtils.TruncateAt#END} or
267 * {@link android.text.TextUtils.TruncateAt#MARQUEE}, to be ellipsized instead
268 * of broken. The default is
269 * {@code null}, indicating no ellipsis is to be applied.
270 *
271 * @param ellipsize type of ellipsis behavior
272 * @return this builder, useful for chaining
273 * @see android.widget.TextView#setEllipsize
274 */
275 public Builder setEllipsize(@Nullable TextUtils.TruncateAt ellipsize) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800276 mEllipsize = ellipsize;
277 return this;
278 }
279
Raph Levien531c30c2015-04-30 16:29:59 -0700280 /**
281 * Set maximum number of lines. This is particularly useful in the case of
282 * ellipsizing, where it changes the layout of the last line. The default is
283 * unlimited.
284 *
285 * @param maxLines maximum number of lines in the layout
286 * @return this builder, useful for chaining
287 * @see android.widget.TextView#setMaxLines
288 */
Raph Leviend3ab6922015-03-02 14:30:53 -0800289 public Builder setMaxLines(int maxLines) {
290 mMaxLines = maxLines;
291 return this;
292 }
293
Raph Levien531c30c2015-04-30 16:29:59 -0700294 /**
295 * Set break strategy, useful for selecting high quality or balanced paragraph
296 * layout options. The default is {@link Layout#BREAK_STRATEGY_SIMPLE}.
297 *
298 * @param breakStrategy break strategy for paragraph layout
299 * @return this builder, useful for chaining
300 * @see android.widget.TextView#setBreakStrategy
301 */
Raph Levien39b4db72015-03-25 13:18:20 -0700302 public Builder setBreakStrategy(@BreakStrategy int breakStrategy) {
303 mBreakStrategy = breakStrategy;
304 return this;
305 }
306
Raph Levien531c30c2015-04-30 16:29:59 -0700307 /**
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700308 * Set hyphenation frequency, to control the amount of automatic hyphenation used. The
309 * default is {@link Layout#HYPHENATION_FREQUENCY_NONE}.
310 *
311 * @param hyphenationFrequency hyphenation frequency for the paragraph
312 * @return this builder, useful for chaining
313 * @see android.widget.TextView#setHyphenationFrequency
314 */
315 public Builder setHyphenationFrequency(@HyphenationFrequency int hyphenationFrequency) {
316 mHyphenationFrequency = hyphenationFrequency;
317 return this;
318 }
319
320 /**
Raph Levien531c30c2015-04-30 16:29:59 -0700321 * Set indents. Arguments are arrays holding an indent amount, one per line, measured in
322 * pixels. For lines past the last element in the array, the last element repeats.
323 *
324 * @param leftIndents array of indent values for left margin, in pixels
325 * @param rightIndents array of indent values for right margin, in pixels
326 * @return this builder, useful for chaining
Raph Levien531c30c2015-04-30 16:29:59 -0700327 */
Raph Leviene319d5a2015-04-14 23:51:07 -0700328 public Builder setIndents(int[] leftIndents, int[] rightIndents) {
Raph Levien2ea52902015-07-01 14:39:31 -0700329 mLeftIndents = leftIndents;
330 mRightIndents = rightIndents;
Raph Leviene319d5a2015-04-14 23:51:07 -0700331 int leftLen = leftIndents == null ? 0 : leftIndents.length;
332 int rightLen = rightIndents == null ? 0 : rightIndents.length;
333 int[] indents = new int[Math.max(leftLen, rightLen)];
334 for (int i = 0; i < indents.length; i++) {
335 int leftMargin = i < leftLen ? leftIndents[i] : 0;
336 int rightMargin = i < rightLen ? rightIndents[i] : 0;
337 indents[i] = leftMargin + rightMargin;
338 }
339 nSetIndents(mNativePtr, indents);
340 return this;
341 }
342
Raph Levien70616ec2015-03-04 10:41:30 -0800343 /**
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700344 * Set paragraph justification mode. The default value is
345 * {@link Layout#JUSTIFICATION_MODE_NONE}. If the last line is too short for justification,
346 * the last line will be displayed with the alignment set by {@link #setAlignment}.
Seigo Nonaka09da71a2016-11-28 16:24:14 +0900347 *
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700348 * @param justificationMode justification mode for the paragraph.
Seigo Nonaka09da71a2016-11-28 16:24:14 +0900349 * @return this builder, useful for chaining.
350 */
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700351 public Builder setJustificationMode(@JustificationMode int justificationMode) {
352 mJustificationMode = justificationMode;
Seigo Nonaka09da71a2016-11-28 16:24:14 +0900353 return this;
354 }
355
Siyamed Sinir442c1512017-07-24 12:18:27 -0700356 /**
357 * Sets whether the line spacing should be applied for the last line. Default value is
358 * {@code false}.
359 *
360 * @hide
361 */
362 /* package */ Builder setAddLastLineLineSpacing(boolean value) {
363 mAddLastLineLineSpacing = value;
364 return this;
365 }
366
Roozbeh Pournaderef7cfa12017-06-15 12:39:04 -0700367 private long[] getHyphenators(LocaleList locales) {
368 final int length = locales.size();
369 final long[] result = new long[length];
370 for (int i = 0; i < length; i++) {
371 final Locale locale = locales.get(i);
372 result[i] = Hyphenator.get(locale).getNativePtr();
373 }
374 return result;
375 }
376
Seigo Nonaka09da71a2016-11-28 16:24:14 +0900377 /**
Raph Levien70616ec2015-03-04 10:41:30 -0800378 * Measurement and break iteration is done in native code. The protocol for using
379 * the native code is as follows.
380 *
Raph Levien26d443a2015-03-30 14:18:32 -0700381 * For each paragraph, do a nSetupParagraph, which sets paragraph text, line width, tab
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700382 * stops, break strategy, and hyphenation frequency (and possibly other parameters in the
383 * future).
Raph Levienc94f7422015-03-06 19:19:48 -0800384 *
385 * Then, for each run within the paragraph:
Roozbeh Pournaderef7cfa12017-06-15 12:39:04 -0700386 * - setLocales (this must be done at least for the first run, optional afterwards)
Raph Levien70616ec2015-03-04 10:41:30 -0800387 * - one of the following, depending on the type of run:
388 * + addStyleRun (a text run, to be measured in native code)
389 * + addMeasuredRun (a run already measured in Java, passed into native code)
390 * + addReplacementRun (a replacement run, width is given)
391 *
392 * After measurement, nGetWidths() is valid if the widths are needed (eg for ellipsis).
393 * Run nComputeLineBreaks() to obtain line breaks for the paragraph.
394 *
395 * After all paragraphs, call finish() to release expensive buffers.
396 */
397
Roozbeh Pournaderef7cfa12017-06-15 12:39:04 -0700398 private void setLocales(LocaleList locales) {
399 if (!locales.equals(mLocales)) {
400 nSetLocales(mNativePtr, locales.toLanguageTags(), getHyphenators(locales));
401 mLocales = locales;
Raph Levien4c1f12e2015-03-02 16:29:23 -0800402 }
403 }
404
Raph Levien70616ec2015-03-04 10:41:30 -0800405 /* package */ float addStyleRun(TextPaint paint, int start, int end, boolean isRtl) {
Roozbeh Pournaderef7cfa12017-06-15 12:39:04 -0700406 setLocales(paint.getTextLocales());
Seigo Nonaka318ca042017-08-01 16:36:18 -0700407 return nAddStyleRun(mNativePtr, paint.getNativeInstance(), start, end, isRtl);
Raph Levien70616ec2015-03-04 10:41:30 -0800408 }
409
410 /* package */ void addMeasuredRun(int start, int end, float[] widths) {
411 nAddMeasuredRun(mNativePtr, start, end, widths);
412 }
413
414 /* package */ void addReplacementRun(int start, int end, float width) {
415 nAddReplacementRun(mNativePtr, start, end, width);
416 }
417
Raph Levien531c30c2015-04-30 16:29:59 -0700418 /**
419 * Build the {@link StaticLayout} after options have been set.
420 *
421 * <p>Note: the builder object must not be reused in any way after calling this
422 * method. Setting parameters after calling this method, or calling it a second
423 * time on the same builder object, will likely lead to unexpected results.
424 *
425 * @return the newly constructed {@link StaticLayout} object
426 */
Raph Leviend3ab6922015-03-02 14:30:53 -0800427 public StaticLayout build() {
Raph Levien39b4db72015-03-25 13:18:20 -0700428 StaticLayout result = new StaticLayout(this);
429 Builder.recycle(this);
Raph Leviend3ab6922015-03-02 14:30:53 -0800430 return result;
431 }
432
Raph Levien4c1f12e2015-03-02 16:29:23 -0800433 @Override
434 protected void finalize() throws Throwable {
435 try {
436 nFreeBuilder(mNativePtr);
437 } finally {
438 super.finalize();
439 }
440 }
441
442 /* package */ long mNativePtr;
443
Raph Leviend3ab6922015-03-02 14:30:53 -0800444 CharSequence mText;
445 int mStart;
446 int mEnd;
447 TextPaint mPaint;
448 int mWidth;
Raph Levien39b4db72015-03-25 13:18:20 -0700449 Alignment mAlignment;
Raph Leviend3ab6922015-03-02 14:30:53 -0800450 TextDirectionHeuristic mTextDir;
451 float mSpacingMult;
452 float mSpacingAdd;
453 boolean mIncludePad;
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700454 boolean mFallbackLineSpacing;
Raph Leviend3ab6922015-03-02 14:30:53 -0800455 int mEllipsizedWidth;
456 TextUtils.TruncateAt mEllipsize;
457 int mMaxLines;
Raph Levien39b4db72015-03-25 13:18:20 -0700458 int mBreakStrategy;
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700459 int mHyphenationFrequency;
Raph Levien2ea52902015-07-01 14:39:31 -0700460 int[] mLeftIndents;
461 int[] mRightIndents;
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700462 int mJustificationMode;
Siyamed Sinir442c1512017-07-24 12:18:27 -0700463 boolean mAddLastLineLineSpacing;
Raph Leviend3ab6922015-03-02 14:30:53 -0800464
465 Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
466
467 // This will go away and be subsumed by native builder code
468 MeasuredText mMeasuredText;
469
Roozbeh Pournaderef7cfa12017-06-15 12:39:04 -0700470 LocaleList mLocales;
Raph Levien4c1f12e2015-03-02 16:29:23 -0800471
Raph Levien39b4db72015-03-25 13:18:20 -0700472 private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<Builder>(3);
Raph Leviend3ab6922015-03-02 14:30:53 -0800473 }
474
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800475 public StaticLayout(CharSequence source, TextPaint paint,
476 int width,
477 Alignment align, float spacingmult, float spacingadd,
478 boolean includepad) {
479 this(source, 0, source.length(), paint, width, align,
480 spacingmult, spacingadd, includepad);
481 }
482
Doug Feltcb3791202011-07-07 11:57:48 -0700483 /**
484 * @hide
485 */
486 public StaticLayout(CharSequence source, TextPaint paint,
487 int width, Alignment align, TextDirectionHeuristic textDir,
488 float spacingmult, float spacingadd,
489 boolean includepad) {
490 this(source, 0, source.length(), paint, width, align, textDir,
491 spacingmult, spacingadd, includepad);
492 }
493
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800494 public StaticLayout(CharSequence source, int bufstart, int bufend,
495 TextPaint paint, int outerwidth,
496 Alignment align,
497 float spacingmult, float spacingadd,
498 boolean includepad) {
499 this(source, bufstart, bufend, paint, outerwidth, align,
500 spacingmult, spacingadd, includepad, null, 0);
501 }
502
Doug Feltcb3791202011-07-07 11:57:48 -0700503 /**
504 * @hide
505 */
506 public StaticLayout(CharSequence source, int bufstart, int bufend,
507 TextPaint paint, int outerwidth,
508 Alignment align, TextDirectionHeuristic textDir,
509 float spacingmult, float spacingadd,
510 boolean includepad) {
511 this(source, bufstart, bufend, paint, outerwidth, align, textDir,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700512 spacingmult, spacingadd, includepad, null, 0, Integer.MAX_VALUE);
Doug Feltcb3791202011-07-07 11:57:48 -0700513}
514
515 public StaticLayout(CharSequence source, int bufstart, int bufend,
516 TextPaint paint, int outerwidth,
517 Alignment align,
518 float spacingmult, float spacingadd,
519 boolean includepad,
520 TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
521 this(source, bufstart, bufend, paint, outerwidth, align,
522 TextDirectionHeuristics.FIRSTSTRONG_LTR,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700523 spacingmult, spacingadd, includepad, ellipsize, ellipsizedWidth, Integer.MAX_VALUE);
Doug Feltcb3791202011-07-07 11:57:48 -0700524 }
525
526 /**
527 * @hide
528 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800529 public StaticLayout(CharSequence source, int bufstart, int bufend,
530 TextPaint paint, int outerwidth,
Doug Feltcb3791202011-07-07 11:57:48 -0700531 Alignment align, TextDirectionHeuristic textDir,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800532 float spacingmult, float spacingadd,
533 boolean includepad,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700534 TextUtils.TruncateAt ellipsize, int ellipsizedWidth, int maxLines) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800535 super((ellipsize == null)
Doug Felt4e0c5e52010-03-15 16:56:02 -0700536 ? source
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800537 : (source instanceof Spanned)
538 ? new SpannedEllipsizer(source)
539 : new Ellipsizer(source),
Doug Feltcb3791202011-07-07 11:57:48 -0700540 paint, outerwidth, align, textDir, spacingmult, spacingadd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800541
Raph Levienebd66ca2015-04-30 15:27:57 -0700542 Builder b = Builder.obtain(source, bufstart, bufend, paint, outerwidth)
Raph Levien39b4db72015-03-25 13:18:20 -0700543 .setAlignment(align)
Raph Leviena6a08282015-06-03 13:20:45 -0700544 .setTextDirection(textDir)
Raph Levien531c30c2015-04-30 16:29:59 -0700545 .setLineSpacing(spacingadd, spacingmult)
Raph Leviend3ab6922015-03-02 14:30:53 -0800546 .setIncludePad(includepad)
547 .setEllipsizedWidth(ellipsizedWidth)
548 .setEllipsize(ellipsize)
549 .setMaxLines(maxLines);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800550 /*
551 * This is annoying, but we can't refer to the layout until
552 * superclass construction is finished, and the superclass
553 * constructor wants the reference to the display text.
Doug Felt4e0c5e52010-03-15 16:56:02 -0700554 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800555 * This will break if the superclass constructor ever actually
556 * cares about the content instead of just holding the reference.
557 */
558 if (ellipsize != null) {
559 Ellipsizer e = (Ellipsizer) getText();
560
561 e.mLayout = this;
562 e.mWidth = ellipsizedWidth;
563 e.mMethod = ellipsize;
564 mEllipsizedWidth = ellipsizedWidth;
565
566 mColumns = COLUMNS_ELLIPSIZE;
567 } else {
568 mColumns = COLUMNS_NORMAL;
569 mEllipsizedWidth = outerwidth;
570 }
571
Siyamed Siniraf398512017-07-25 19:08:42 -0700572 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2);
573 mLines = ArrayUtils.newUnpaddedIntArray(2 * mColumns);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700574 mMaximumVisibleLineCount = maxLines;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800575
Raph Levien70616ec2015-03-04 10:41:30 -0800576 generate(b, b.mIncludePad, b.mIncludePad);
Doug Felte8e45f22010-03-29 14:58:40 -0700577
Raph Leviend3ab6922015-03-02 14:30:53 -0800578 Builder.recycle(b);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800579 }
580
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700581 /* package */ StaticLayout(@Nullable CharSequence text) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700582 super(text, null, 0, null, 0, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800583
584 mColumns = COLUMNS_ELLIPSIZE;
Siyamed Siniraf398512017-07-25 19:08:42 -0700585 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2);
586 mLines = ArrayUtils.newUnpaddedIntArray(2 * mColumns);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800587 }
588
Raph Levien39b4db72015-03-25 13:18:20 -0700589 private StaticLayout(Builder b) {
590 super((b.mEllipsize == null)
591 ? b.mText
592 : (b.mText instanceof Spanned)
593 ? new SpannedEllipsizer(b.mText)
594 : new Ellipsizer(b.mText),
595 b.mPaint, b.mWidth, b.mAlignment, b.mSpacingMult, b.mSpacingAdd);
596
597 if (b.mEllipsize != null) {
598 Ellipsizer e = (Ellipsizer) getText();
599
600 e.mLayout = this;
601 e.mWidth = b.mEllipsizedWidth;
602 e.mMethod = b.mEllipsize;
603 mEllipsizedWidth = b.mEllipsizedWidth;
604
605 mColumns = COLUMNS_ELLIPSIZE;
606 } else {
607 mColumns = COLUMNS_NORMAL;
608 mEllipsizedWidth = b.mWidth;
609 }
610
Siyamed Siniraf398512017-07-25 19:08:42 -0700611 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2);
612 mLines = ArrayUtils.newUnpaddedIntArray(2 * mColumns);
Raph Levien39b4db72015-03-25 13:18:20 -0700613 mMaximumVisibleLineCount = b.mMaxLines;
614
Raph Levien2ea52902015-07-01 14:39:31 -0700615 mLeftIndents = b.mLeftIndents;
616 mRightIndents = b.mRightIndents;
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700617 setJustificationMode(b.mJustificationMode);
Raph Levien2ea52902015-07-01 14:39:31 -0700618
Raph Levien39b4db72015-03-25 13:18:20 -0700619 generate(b, b.mIncludePad, b.mIncludePad);
620 }
621
Raph Leviend3ab6922015-03-02 14:30:53 -0800622 /* package */ void generate(Builder b, boolean includepad, boolean trackpad) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700623 final CharSequence source = b.mText;
Raph Leviend3ab6922015-03-02 14:30:53 -0800624 int bufStart = b.mStart;
625 int bufEnd = b.mEnd;
626 TextPaint paint = b.mPaint;
627 int outerWidth = b.mWidth;
628 TextDirectionHeuristic textDir = b.mTextDir;
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700629 final boolean fallbackLineSpacing = b.mFallbackLineSpacing;
Raph Leviend3ab6922015-03-02 14:30:53 -0800630 float spacingmult = b.mSpacingMult;
631 float spacingadd = b.mSpacingAdd;
632 float ellipsizedWidth = b.mEllipsizedWidth;
633 TextUtils.TruncateAt ellipsize = b.mEllipsize;
Siyamed Sinir442c1512017-07-24 12:18:27 -0700634 final boolean addLastLineSpacing = b.mAddLastLineLineSpacing;
Raph Levien4c1f12e2015-03-02 16:29:23 -0800635 LineBreaks lineBreaks = new LineBreaks(); // TODO: move to builder to avoid allocation costs
Anish Athalyec8f9e622014-07-21 15:26:34 -0700636 // store span end locations
637 int[] spanEndCache = new int[4];
638 // store fontMetrics per span range
639 // must be a multiple of 4 (and > 0) (store top, bottom, ascent, and descent per range)
640 int[] fmCache = new int[4 * 4];
Roozbeh Pournaderef7cfa12017-06-15 12:39:04 -0700641 b.setLocales(paint.getTextLocales());
Anish Athalye88b5b0b2014-06-24 14:39:43 -0700642
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800643 mLineCount = 0;
Siyamed Sinira19cd512017-08-03 22:01:56 -0700644 mEllipsized = false;
Siyamed Sinira8d982d2017-08-21 13:27:47 -0700645 mMaxLineHeight = mMaximumVisibleLineCount < 1 ? 0 : DEFAULT_MAX_LINE_HEIGHT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800646
647 int v = 0;
648 boolean needMultiply = (spacingmult != 1 || spacingadd != 0);
649
Raph Leviend3ab6922015-03-02 14:30:53 -0800650 Paint.FontMetricsInt fm = b.mFontMetricsInt;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800651 int[] chooseHtv = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800652
Raph Leviend3ab6922015-03-02 14:30:53 -0800653 MeasuredText measured = b.mMeasuredText;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800654
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800655 Spanned spanned = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800656 if (source instanceof Spanned)
657 spanned = (Spanned) source;
658
Doug Felte8e45f22010-03-29 14:58:40 -0700659 int paraEnd;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800660 for (int paraStart = bufStart; paraStart <= bufEnd; paraStart = paraEnd) {
661 paraEnd = TextUtils.indexOf(source, CHAR_NEW_LINE, paraStart, bufEnd);
Doug Felte8e45f22010-03-29 14:58:40 -0700662 if (paraEnd < 0)
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800663 paraEnd = bufEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800664 else
Doug Felte8e45f22010-03-29 14:58:40 -0700665 paraEnd++;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800666
Anish Athalyec8f9e622014-07-21 15:26:34 -0700667 int firstWidthLineCount = 1;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800668 int firstWidth = outerWidth;
669 int restWidth = outerWidth;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800670
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800671 LineHeightSpan[] chooseHt = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800672
673 if (spanned != null) {
Eric Fischer74d31ef2010-08-05 15:29:36 -0700674 LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
Doug Felte8e45f22010-03-29 14:58:40 -0700675 LeadingMarginSpan.class);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800676 for (int i = 0; i < sp.length; i++) {
Mark Wagner7b5676e2009-10-16 11:44:23 -0700677 LeadingMarginSpan lms = sp[i];
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800678 firstWidth -= sp[i].getLeadingMargin(true);
679 restWidth -= sp[i].getLeadingMargin(false);
Doug Feltcb3791202011-07-07 11:57:48 -0700680
Doug Feltc982f602010-05-25 11:51:40 -0700681 // LeadingMarginSpan2 is odd. The count affects all
Anish Athalyeab08c6d2014-08-08 12:09:58 -0700682 // leading margin spans, not just this particular one
Doug Feltc982f602010-05-25 11:51:40 -0700683 if (lms instanceof LeadingMarginSpan2) {
684 LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700685 firstWidthLineCount = Math.max(firstWidthLineCount,
686 lms2.getLeadingMarginLineCount());
Mark Wagner7b5676e2009-10-16 11:44:23 -0700687 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800688 }
689
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800690 chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800691
Roozbeh Pournader431e5062015-10-16 02:27:03 -0700692 if (chooseHt.length == 0) {
693 chooseHt = null; // So that out() would not assume it has any contents
694 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800695 if (chooseHtv == null ||
696 chooseHtv.length < chooseHt.length) {
Adam Lesinski776abc22014-03-07 11:30:59 -0500697 chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800698 }
699
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800700 for (int i = 0; i < chooseHt.length; i++) {
701 int o = spanned.getSpanStart(chooseHt[i]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800702
Doug Felte8e45f22010-03-29 14:58:40 -0700703 if (o < paraStart) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800704 // starts in this layout, before the
705 // current paragraph
706
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800707 chooseHtv[i] = getLineTop(getLineForOffset(o));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800708 } else {
709 // starts in this paragraph
710
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800711 chooseHtv[i] = v;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800712 }
713 }
714 }
715 }
716
Raph Levien70616ec2015-03-04 10:41:30 -0800717 measured.setPara(source, paraStart, paraEnd, textDir, b);
Doug Felte8e45f22010-03-29 14:58:40 -0700718 char[] chs = measured.mChars;
719 float[] widths = measured.mWidths;
720 byte[] chdirs = measured.mLevels;
721 int dir = measured.mDir;
722 boolean easy = measured.mEasy;
Raph Levienc94f7422015-03-06 19:19:48 -0800723
724 // tab stop locations
725 int[] variableTabStops = null;
726 if (spanned != null) {
727 TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
728 paraEnd, TabStopSpan.class);
729 if (spans.length > 0) {
730 int[] stops = new int[spans.length];
731 for (int i = 0; i < spans.length; i++) {
732 stops[i] = spans[i].getTabStop();
733 }
734 Arrays.sort(stops, 0, stops.length);
735 variableTabStops = stops;
736 }
737 }
738
Raph Levienc94f7422015-03-06 19:19:48 -0800739 nSetupParagraph(b.mNativePtr, chs, paraEnd - paraStart,
740 firstWidth, firstWidthLineCount, restWidth,
Seigo Nonaka09da71a2016-11-28 16:24:14 +0900741 variableTabStops, TAB_INCREMENT, b.mBreakStrategy, b.mHyphenationFrequency,
Seigo Nonaka4b4730d2017-03-31 09:42:16 -0700742 // TODO: Support more justification mode, e.g. letter spacing, stretching.
743 b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE);
Raph Levien2ea52902015-07-01 14:39:31 -0700744 if (mLeftIndents != null || mRightIndents != null) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700745 // TODO(performance): it would be better to do this once per layout rather
Raph Levien2ea52902015-07-01 14:39:31 -0700746 // than once per paragraph, but that would require a change to the native
747 // interface.
748 int leftLen = mLeftIndents == null ? 0 : mLeftIndents.length;
749 int rightLen = mRightIndents == null ? 0 : mRightIndents.length;
Siyamed Sinirf9a08862016-04-12 19:30:44 -0700750 int indentsLen = Math.max(1, Math.max(leftLen, rightLen) - mLineCount);
Raph Levien2ea52902015-07-01 14:39:31 -0700751 int[] indents = new int[indentsLen];
752 for (int i = 0; i < indentsLen; i++) {
753 int leftMargin = mLeftIndents == null ? 0 :
754 mLeftIndents[Math.min(i + mLineCount, leftLen - 1)];
755 int rightMargin = mRightIndents == null ? 0 :
756 mRightIndents[Math.min(i + mLineCount, rightLen - 1)];
757 indents[i] = leftMargin + rightMargin;
758 }
759 nSetIndents(b.mNativePtr, indents);
760 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800761
Anish Athalyec8f9e622014-07-21 15:26:34 -0700762 // measurement has to be done before performing line breaking
763 // but we don't want to recompute fontmetrics or span ranges the
764 // second time, so we cache those and then use those stored values
765 int fmCacheCount = 0;
766 int spanEndCacheCount = 0;
Gilles Debunnecd943a72012-06-07 17:54:47 -0700767 for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
Anish Athalyec8f9e622014-07-21 15:26:34 -0700768 if (fmCacheCount * 4 >= fmCache.length) {
769 int[] grow = new int[fmCacheCount * 4 * 2];
770 System.arraycopy(fmCache, 0, grow, 0, fmCacheCount * 4);
771 fmCache = grow;
772 }
773
774 if (spanEndCacheCount >= spanEndCache.length) {
775 int[] grow = new int[spanEndCacheCount * 2];
776 System.arraycopy(spanEndCache, 0, grow, 0, spanEndCacheCount);
777 spanEndCache = grow;
778 }
Doug Felte8e45f22010-03-29 14:58:40 -0700779
Gilles Debunnecd943a72012-06-07 17:54:47 -0700780 if (spanned == null) {
781 spanEnd = paraEnd;
Doug Felt23241882010-06-02 14:41:06 -0700782 int spanLen = spanEnd - spanStart;
Gilles Debunnecd943a72012-06-07 17:54:47 -0700783 measured.addStyleRun(paint, spanLen, fm);
784 } else {
785 spanEnd = spanned.nextSpanTransition(spanStart, paraEnd,
786 MetricAffectingSpan.class);
787 int spanLen = spanEnd - spanStart;
788 MetricAffectingSpan[] spans =
Doug Felt23241882010-06-02 14:41:06 -0700789 spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class);
Gilles Debunnecd943a72012-06-07 17:54:47 -0700790 spans = TextUtils.removeEmptySpans(spans, spanned, MetricAffectingSpan.class);
791 measured.addStyleRun(paint, spans, spanLen, fm);
Doug Felt23241882010-06-02 14:41:06 -0700792 }
793
Anish Athalyec8f9e622014-07-21 15:26:34 -0700794 // the order of storage here (top, bottom, ascent, descent) has to match the code below
795 // where these values are retrieved
796 fmCache[fmCacheCount * 4 + 0] = fm.top;
797 fmCache[fmCacheCount * 4 + 1] = fm.bottom;
798 fmCache[fmCacheCount * 4 + 2] = fm.ascent;
799 fmCache[fmCacheCount * 4 + 3] = fm.descent;
800 fmCacheCount++;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800801
Anish Athalyec8f9e622014-07-21 15:26:34 -0700802 spanEndCache[spanEndCacheCount] = spanEnd;
803 spanEndCacheCount++;
804 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800805
Raph Levien70616ec2015-03-04 10:41:30 -0800806 nGetWidths(b.mNativePtr, widths);
Raph Levienc94f7422015-03-06 19:19:48 -0800807 int breakCount = nComputeLineBreaks(b.mNativePtr, lineBreaks, lineBreaks.breaks,
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700808 lineBreaks.widths, lineBreaks.ascents, lineBreaks.descents, lineBreaks.flags,
809 lineBreaks.breaks.length);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800810
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700811 final int[] breaks = lineBreaks.breaks;
812 final float[] lineWidths = lineBreaks.widths;
813 final float[] ascents = lineBreaks.ascents;
814 final float[] descents = lineBreaks.descents;
815 final int[] flags = lineBreaks.flags;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700816
Keisuke Kuroyanagif4a3f3a2015-06-10 09:37:32 +0900817 final int remainingLineCount = mMaximumVisibleLineCount - mLineCount;
818 final boolean ellipsisMayBeApplied = ellipsize != null
819 && (ellipsize == TextUtils.TruncateAt.END
820 || (mMaximumVisibleLineCount == 1
821 && ellipsize != TextUtils.TruncateAt.MARQUEE));
Roozbeh Pournader9ea756f2017-07-25 11:20:29 -0700822 if (0 < remainingLineCount && remainingLineCount < breakCount
823 && ellipsisMayBeApplied) {
Keisuke Kuroyanagif4a3f3a2015-06-10 09:37:32 +0900824 // Calculate width and flag.
825 float width = 0;
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700826 int flag = 0; // XXX May need to also have starting hyphen edit
Keisuke Kuroyanagif4a3f3a2015-06-10 09:37:32 +0900827 for (int i = remainingLineCount - 1; i < breakCount; i++) {
Keisuke Kuroyanagi78f0d832016-05-10 12:21:33 -0700828 if (i == breakCount - 1) {
829 width += lineWidths[i];
830 } else {
831 for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) {
832 width += widths[j];
833 }
834 }
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700835 flag |= flags[i] & TAB_MASK;
Keisuke Kuroyanagif4a3f3a2015-06-10 09:37:32 +0900836 }
Keisuke Kuroyanagi78f0d832016-05-10 12:21:33 -0700837 // Treat the last line and overflowed lines as a single line.
838 breaks[remainingLineCount - 1] = breaks[breakCount - 1];
Keisuke Kuroyanagif4a3f3a2015-06-10 09:37:32 +0900839 lineWidths[remainingLineCount - 1] = width;
840 flags[remainingLineCount - 1] = flag;
841
842 breakCount = remainingLineCount;
843 }
844
Anish Athalyec8f9e622014-07-21 15:26:34 -0700845 // here is the offset of the starting character of the line we are currently measuring
846 int here = paraStart;
847
848 int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
849 int fmCacheIndex = 0;
850 int spanEndCacheIndex = 0;
851 int breakIndex = 0;
852 for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
853 // retrieve end of span
854 spanEnd = spanEndCache[spanEndCacheIndex++];
855
856 // retrieve cached metrics, order matches above
857 fm.top = fmCache[fmCacheIndex * 4 + 0];
858 fm.bottom = fmCache[fmCacheIndex * 4 + 1];
859 fm.ascent = fmCache[fmCacheIndex * 4 + 2];
860 fm.descent = fmCache[fmCacheIndex * 4 + 3];
861 fmCacheIndex++;
862
863 if (fm.top < fmTop) {
864 fmTop = fm.top;
865 }
866 if (fm.ascent < fmAscent) {
867 fmAscent = fm.ascent;
868 }
869 if (fm.descent > fmDescent) {
870 fmDescent = fm.descent;
871 }
872 if (fm.bottom > fmBottom) {
873 fmBottom = fm.bottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800874 }
875
Anish Athalyec8f9e622014-07-21 15:26:34 -0700876 // skip breaks ending before current span range
877 while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
878 breakIndex++;
879 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800880
Anish Athalyec8f9e622014-07-21 15:26:34 -0700881 while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
882 int endPos = paraStart + breaks[breakIndex];
883
Raph Levience4155a2015-03-11 11:02:33 -0700884 boolean moreChars = (endPos < bufEnd);
Raph Levien4c02e832014-12-12 11:17:01 -0800885
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700886 final int ascent = fallbackLineSpacing
887 ? Math.min(fmAscent, (int) Math.round(ascents[breakIndex]))
888 : fmAscent;
889 final int descent = fallbackLineSpacing
890 ? Math.max(fmDescent, (int) Math.round(descents[breakIndex]))
891 : fmDescent;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700892 v = out(source, here, endPos,
Roozbeh Pournader737dfea2017-08-10 11:32:24 -0700893 ascent, descent, fmTop, fmBottom,
Roozbeh Pournader431e5062015-10-16 02:27:03 -0700894 v, spacingmult, spacingadd, chooseHt, chooseHtv, fm, flags[breakIndex],
Anish Athalyec8f9e622014-07-21 15:26:34 -0700895 needMultiply, chdirs, dir, easy, bufEnd, includepad, trackpad,
Siyamed Sinir442c1512017-07-24 12:18:27 -0700896 addLastLineSpacing, chs, widths, paraStart, ellipsize,
897 ellipsizedWidth, lineWidths[breakIndex], paint, moreChars);
Anish Athalyec8f9e622014-07-21 15:26:34 -0700898
899 if (endPos < spanEnd) {
900 // preserve metrics for current span
901 fmTop = fm.top;
902 fmBottom = fm.bottom;
903 fmAscent = fm.ascent;
904 fmDescent = fm.descent;
905 } else {
906 fmTop = fmBottom = fmAscent = fmDescent = 0;
907 }
908
909 here = endPos;
910 breakIndex++;
911
Siyamed Sinir0745c722016-05-31 20:39:33 -0700912 if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) {
Anish Athalyec8f9e622014-07-21 15:26:34 -0700913 return;
914 }
915 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800916 }
917
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800918 if (paraEnd == bufEnd)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800919 break;
920 }
921
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700922 if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE) &&
Fabrice Di Meglioad0b0512011-10-04 17:21:26 -0700923 mLineCount < mMaximumVisibleLineCount) {
Raph Levien70616ec2015-03-04 10:41:30 -0800924 measured.setPara(source, bufEnd, bufEnd, textDir, b);
Fabrice Di Meglioe6318892013-06-18 20:03:41 -0700925
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800926 paint.getFontMetricsInt(fm);
927
928 v = out(source,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800929 bufEnd, bufEnd, fm.ascent, fm.descent,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800930 fm.top, fm.bottom,
931 v,
932 spacingmult, spacingadd, null,
Raph Levien26d443a2015-03-30 14:18:32 -0700933 null, fm, 0,
Fabrice Di Meglioe6318892013-06-18 20:03:41 -0700934 needMultiply, measured.mLevels, measured.mDir, measured.mEasy, bufEnd,
Siyamed Sinir442c1512017-07-24 12:18:27 -0700935 includepad, trackpad, addLastLineSpacing, null,
Gilles Debunned300e752011-10-17 13:37:36 -0700936 null, bufStart, ellipsize,
937 ellipsizedWidth, 0, paint, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800938 }
939 }
940
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700941 // The parameters that are not changed in the method are marked as final to make the code
942 // easier to understand.
943 private int out(final CharSequence text, final int start, final int end, int above, int below,
944 int top, int bottom, int v, final float spacingmult, final float spacingadd,
945 final LineHeightSpan[] chooseHt, final int[] chooseHtv, final Paint.FontMetricsInt fm,
946 final int flags, final boolean needMultiply, final byte[] chdirs, final int dir,
947 final boolean easy, final int bufEnd, final boolean includePad, final boolean trackPad,
948 final boolean addLastLineLineSpacing, final char[] chs, final float[] widths,
949 final int widthStart, final TextUtils.TruncateAt ellipsize, final float ellipsisWidth,
950 final float textWidth, final TextPaint paint, final boolean moreChars) {
Siyamed Siniraf398512017-07-25 19:08:42 -0700951 final int j = mLineCount;
952 final int off = j * mColumns;
953 final int want = off + mColumns + TOP;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800954 int[] lines = mLines;
955
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800956 if (want >= lines.length) {
Siyamed Siniraf398512017-07-25 19:08:42 -0700957 final int[] grow = ArrayUtils.newUnpaddedIntArray(GrowingArrayUtils.growSize(want));
Adam Lesinski776abc22014-03-07 11:30:59 -0500958 System.arraycopy(lines, 0, grow, 0, lines.length);
959 mLines = grow;
960 lines = grow;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800961 }
962
Siyamed Siniraf398512017-07-25 19:08:42 -0700963 if (j >= mLineDirections.length) {
964 final Directions[] grow = ArrayUtils.newUnpaddedArray(Directions.class,
965 GrowingArrayUtils.growSize(j));
966 System.arraycopy(mLineDirections, 0, grow, 0, mLineDirections.length);
967 mLineDirections = grow;
968 }
969
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700970 lines[off + START] = start;
971 lines[off + TOP] = v;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800972
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700973 // Information about hyphenation, tabs, and directions are needed for determining
974 // ellipsization, so the values should be assigned before ellipsization.
Eric Fischera9f1dd02009-08-12 15:00:10 -0700975
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700976 // TODO: could move TAB to share same column as HYPHEN, simplifying this code and gaining
977 // one bit for start field
978 lines[off + TAB] |= flags & TAB_MASK;
979 lines[off + HYPHEN] = flags;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800980
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700981 lines[off + DIR] |= dir << DIR_SHIFT;
982 // easy means all chars < the first RTL, so no emoji, no nothing
983 // XXX a run with no text or all spaces is easy but might be an empty
984 // RTL paragraph. Make sure easy is false if this is the case.
985 if (easy) {
986 mLineDirections[j] = DIRS_ALL_LEFT_TO_RIGHT;
987 } else {
988 mLineDirections[j] = AndroidBidi.directions(dir, chdirs, start - widthStart, chs,
989 start - widthStart, end - start);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800990 }
991
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -0700992 final boolean firstLine = (j == 0);
993 final boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);
Siyamed Sinir0745c722016-05-31 20:39:33 -0700994
995 if (ellipsize != null) {
996 // If there is only one line, then do any type of ellipsis except when it is MARQUEE
997 // if there are multiple lines, just allow END ellipsis on the last line
998 boolean forceEllipsis = moreChars && (mLineCount + 1 == mMaximumVisibleLineCount);
999
1000 boolean doEllipsis =
1001 (((mMaximumVisibleLineCount == 1 && moreChars) || (firstLine && !moreChars)) &&
1002 ellipsize != TextUtils.TruncateAt.MARQUEE) ||
1003 (!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
1004 ellipsize == TextUtils.TruncateAt.END);
1005 if (doEllipsis) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001006 calculateEllipsis(text, start, end, widths, widthStart,
1007 ellipsisWidth - getTotalInsets(j), ellipsize, j,
1008 textWidth, paint, forceEllipsis, dir);
Siyamed Sinir0745c722016-05-31 20:39:33 -07001009 }
1010 }
1011
Siyamed Sinirfb0b2dc2017-07-26 22:08:24 -07001012 final boolean lastLine;
1013 if (mEllipsized) {
1014 lastLine = true;
1015 } else {
1016 final boolean lastCharIsNewLine = widthStart != bufEnd && bufEnd > 0
1017 && text.charAt(bufEnd - 1) == CHAR_NEW_LINE;
1018 if (end == bufEnd && !lastCharIsNewLine) {
1019 lastLine = true;
1020 } else if (start == bufEnd && lastCharIsNewLine) {
1021 lastLine = true;
1022 } else {
1023 lastLine = false;
1024 }
1025 }
Raph Leviend97b0972014-04-24 12:51:35 -07001026
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001027 if (chooseHt != null) {
1028 fm.ascent = above;
1029 fm.descent = below;
1030 fm.top = top;
1031 fm.bottom = bottom;
1032
1033 for (int i = 0; i < chooseHt.length; i++) {
1034 if (chooseHt[i] instanceof LineHeightSpan.WithDensity) {
1035 ((LineHeightSpan.WithDensity) chooseHt[i])
1036 .chooseHeight(text, start, end, chooseHtv[i], v, fm, paint);
1037
1038 } else {
1039 chooseHt[i].chooseHeight(text, start, end, chooseHtv[i], v, fm);
1040 }
1041 }
1042
1043 above = fm.ascent;
1044 below = fm.descent;
1045 top = fm.top;
1046 bottom = fm.bottom;
1047 }
1048
Raph Leviend97b0972014-04-24 12:51:35 -07001049 if (firstLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001050 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001051 mTopPadding = top - above;
1052 }
1053
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001054 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001055 above = top;
1056 }
1057 }
Raph Leviend97b0972014-04-24 12:51:35 -07001058
Raph Leviend97b0972014-04-24 12:51:35 -07001059 if (lastLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001060 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001061 mBottomPadding = bottom - below;
1062 }
1063
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001064 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001065 below = bottom;
1066 }
1067 }
1068
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001069 final int extra;
Siyamed Sinir442c1512017-07-24 12:18:27 -07001070 if (needMultiply && (addLastLineLineSpacing || !lastLine)) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001071 final double ex = (below - above) * (spacingmult - 1) + spacingadd;
Doug Felt10657582010-02-22 11:19:01 -08001072 if (ex >= 0) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001073 extra = (int)(ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -08001074 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001075 extra = -(int)(-ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -08001076 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001077 } else {
1078 extra = 0;
1079 }
1080
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001081 lines[off + DESCENT] = below + extra;
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001082 lines[off + EXTRA] = extra;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001083
Siyamed Sinir0745c722016-05-31 20:39:33 -07001084 // special case for non-ellipsized last visible line when maxLines is set
1085 // store the height as if it was ellipsized
1086 if (!mEllipsized && currentLineIsTheLastVisibleOne) {
1087 // below calculation as if it was the last line
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001088 final int maxLineBelow = includePad ? bottom : below;
Siyamed Sinir0745c722016-05-31 20:39:33 -07001089 // similar to the calculation of v below, without the extra.
1090 mMaxLineHeight = v + (maxLineBelow - above);
1091 }
1092
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001093 v += (below - above) + extra;
1094 lines[off + mColumns + START] = end;
1095 lines[off + mColumns + TOP] = v;
1096
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001097 mLineCount++;
1098 return v;
1099 }
1100
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001101 private void calculateEllipsis(CharSequence text, int lineStart, int lineEnd, float[] widths,
1102 int widthStart, float avail, TextUtils.TruncateAt where, int line, float textWidth,
1103 TextPaint paint, boolean forceEllipsis, int dir) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001104 if (textWidth <= avail && !forceEllipsis) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001105 // Everything fits!
1106 mLines[mColumns * line + ELLIPSIS_START] = 0;
1107 mLines[mColumns * line + ELLIPSIS_COUNT] = 0;
1108 return;
1109 }
1110
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001111 float tempAvail = avail;
1112 int numberOfTries = 0;
1113 boolean lineFits = false;
1114 mWorkPaint.set(paint);
1115 do {
1116 final float ellipsizedWidth = guessEllipsis(text, lineStart, lineEnd, widths,
1117 widthStart, tempAvail, where, line, textWidth, mWorkPaint, forceEllipsis, dir);
1118 if (ellipsizedWidth <= avail) {
1119 lineFits = true;
1120 } else {
1121 numberOfTries++;
1122 if (numberOfTries > 10) {
1123 // If the text still doesn't fit after ten tries, assume it will never fit and
1124 // ellipsize it all.
1125 mLines[mColumns * line + ELLIPSIS_START] = 0;
1126 mLines[mColumns * line + ELLIPSIS_COUNT] = lineEnd - lineStart;
1127 lineFits = true;
1128 } else {
1129 // Some side effect of ellipsization has caused the text to go over the
1130 // available width. Let's make the available width shorter by exactly that
1131 // amount and retry.
1132 tempAvail -= ellipsizedWidth - avail;
1133 }
1134 }
1135 } while (!lineFits);
1136 mEllipsized = true;
1137 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001138
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001139 // Returns the width of the ellipsized line which in some rare cases can actually be larger
1140 // than 'avail' (due to kerning or other context-based effect of replacement of text by
1141 // ellipsis). If all the line needs to ellipsized away, or it's an invalud hyphenation mode,
1142 // returns 0 so the caller can stop iterating.
1143 //
1144 // This method temporarily modifies the TextPaint passed to it, so the TextPaint passed to it
1145 // should not be accessed while the method is running.
1146 private float guessEllipsis(CharSequence text, int lineStart, int lineEnd, float[] widths,
1147 int widthStart, float avail, TextUtils.TruncateAt where, int line, float textWidth,
1148 TextPaint paint, boolean forceEllipsis, int dir) {
1149 final int savedHyphenEdit = paint.getHyphenEdit();
1150 paint.setHyphenEdit(0);
1151 final float ellipsisWidth = paint.measureText(TextUtils.getEllipsisString(where));
1152 final int ellipsisStart;
1153 final int ellipsisCount;
1154 final int len = lineEnd - lineStart;
1155 final int offset = lineStart - widthStart;
1156
1157 int hyphen = getHyphen(line);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001158 // We only support start ellipsis on a single line
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001159 if (where == TextUtils.TruncateAt.START) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001160 if (mMaximumVisibleLineCount == 1) {
1161 float sum = 0;
1162 int i;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001163
Keisuke Kuroyanagied2eea12015-04-14 18:18:35 +09001164 for (i = len; i > 0; i--) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001165 final float w = widths[i - 1 + offset];
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001166 if (w + sum + ellipsisWidth > avail) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001167 while (i < len && widths[i + offset] == 0.0f) {
Keisuke Kuroyanagi82d1c442016-09-14 14:30:14 +09001168 i++;
1169 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001170 break;
1171 }
1172
1173 sum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001174 }
1175
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001176 ellipsisStart = 0;
1177 ellipsisCount = i;
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001178 // Strip the potential hyphenation at beginning of line.
1179 hyphen &= ~Paint.HYPHENEDIT_MASK_START_OF_LINE;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001180 } else {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001181 ellipsisStart = 0;
1182 ellipsisCount = 0;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001183 if (Log.isLoggable(TAG, Log.WARN)) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001184 Log.w(TAG, "Start ellipsis only supported with one line");
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001185 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001186 }
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001187 } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE ||
1188 where == TextUtils.TruncateAt.END_SMALL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001189 float sum = 0;
1190 int i;
1191
1192 for (i = 0; i < len; i++) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001193 final float w = widths[i + offset];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001194
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001195 if (w + sum + ellipsisWidth > avail) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001196 break;
1197 }
1198
1199 sum += w;
1200 }
1201
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001202 if (forceEllipsis && i == len && len > 0) {
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -07001203 ellipsisStart = len - 1;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001204 ellipsisCount = 1;
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001205 } else {
1206 ellipsisStart = i;
1207 ellipsisCount = len - i;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001208 }
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001209 // Strip the potential hyphenation at end of line.
1210 hyphen &= ~Paint.HYPHENEDIT_MASK_END_OF_LINE;
1211 } else { // where = TextUtils.TruncateAt.MIDDLE
1212 // We only support middle ellipsis on a single line.
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001213 if (mMaximumVisibleLineCount == 1) {
1214 float lsum = 0, rsum = 0;
1215 int left = 0, right = len;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001216
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001217 final float ravail = (avail - ellipsisWidth) / 2;
Raph Levien0e3c5e82014-12-04 13:26:07 -08001218 for (right = len; right > 0; right--) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001219 final float w = widths[right - 1 + offset];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001220
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001221 if (w + rsum > ravail) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001222 while (right < len && widths[right + offset] == 0.0f) {
Keisuke Kuroyanagi82d1c442016-09-14 14:30:14 +09001223 right++;
1224 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001225 break;
1226 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001227 rsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001228 }
1229
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001230 final float lavail = avail - ellipsisWidth - rsum;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001231 for (left = 0; left < right; left++) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001232 final float w = widths[left + offset];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001233
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001234 if (w + lsum > lavail) {
1235 break;
1236 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001237
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001238 lsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001239 }
1240
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001241 ellipsisStart = left;
1242 ellipsisCount = right - left;
1243 } else {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001244 ellipsisStart = 0;
1245 ellipsisCount = 0;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001246 if (Log.isLoggable(TAG, Log.WARN)) {
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001247 Log.w(TAG, "Middle ellipsis only supported with one line");
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001248 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001249 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001250 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001251 mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
1252 mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001253
1254 if (ellipsisStart == 0 && (ellipsisCount == 0 || ellipsisCount == len)) {
1255 // Unsupported ellipsization mode or all text is ellipsized away. Return 0.
1256 return 0.0f;
1257 }
1258
1259 final boolean isSpanned = text instanceof Spanned;
1260 final Ellipsizer ellipsizedText = isSpanned
1261 ? new SpannedEllipsizer(text)
1262 : new Ellipsizer(text);
1263 ellipsizedText.mLayout = this;
1264 ellipsizedText.mMethod = where;
1265
1266 final boolean hasTabs = getLineContainsTab(line);
1267 final TabStops tabStops;
1268 if (hasTabs && isSpanned) {
1269 final TabStopSpan[] tabs = getParagraphSpans((Spanned) ellipsizedText, lineStart,
1270 lineEnd, TabStopSpan.class);
1271 if (tabs.length == 0) {
1272 tabStops = null;
1273 } else {
1274 tabStops = new TabStops(TAB_INCREMENT, tabs);
1275 }
1276 } else {
1277 tabStops = null;
1278 }
1279 paint.setHyphenEdit(hyphen);
1280 final TextLine textline = TextLine.obtain();
1281 textline.set(paint, ellipsizedText, lineStart, lineEnd, dir, getLineDirections(line),
1282 hasTabs, tabStops);
1283 // Since TextLine.metric() returns negative values for RTL text, multiplication by dir
1284 // converts it to an actual width. Note that we don't want to use the absolute value,
1285 // since we may actually have glyphs with negative advances, which by definition always
1286 // fit.
1287 final float ellipsizedWidth = textline.metrics(null) * dir;
1288 TextLine.recycle(textline);
1289 paint.setHyphenEdit(savedHyphenEdit);
1290 return ellipsizedWidth;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001291 }
1292
Selim Cinek365ec092017-03-09 00:10:52 -08001293 private float getTotalInsets(int line) {
1294 int totalIndent = 0;
1295 if (mLeftIndents != null) {
1296 totalIndent = mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1297 }
1298 if (mRightIndents != null) {
1299 totalIndent += mRightIndents[Math.min(line, mRightIndents.length - 1)];
1300 }
1301 return totalIndent;
1302 }
1303
Doug Felte8e45f22010-03-29 14:58:40 -07001304 // Override the base class so we can directly access our members,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001305 // rather than relying on member functions.
1306 // The logic mirrors that of Layout.getLineForVertical
1307 // FIXME: It may be faster to do a linear search for layouts without many lines.
Gilles Debunne66111472010-11-19 11:04:37 -08001308 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001309 public int getLineForVertical(int vertical) {
1310 int high = mLineCount;
1311 int low = -1;
1312 int guess;
1313 int[] lines = mLines;
1314 while (high - low > 1) {
1315 guess = (high + low) >> 1;
1316 if (lines[mColumns * guess + TOP] > vertical){
1317 high = guess;
1318 } else {
1319 low = guess;
1320 }
1321 }
1322 if (low < 0) {
1323 return 0;
1324 } else {
1325 return low;
1326 }
1327 }
1328
Gilles Debunne66111472010-11-19 11:04:37 -08001329 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001330 public int getLineCount() {
1331 return mLineCount;
1332 }
1333
Gilles Debunne66111472010-11-19 11:04:37 -08001334 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001335 public int getLineTop(int line) {
Raph Levien07e6c232016-04-04 12:34:06 -07001336 return mLines[mColumns * line + TOP];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001337 }
1338
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001339 /**
1340 * @hide
1341 */
1342 @Override
1343 public int getLineExtra(int line) {
1344 return mLines[mColumns * line + EXTRA];
1345 }
1346
Gilles Debunne66111472010-11-19 11:04:37 -08001347 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001348 public int getLineDescent(int line) {
Raph Levien07e6c232016-04-04 12:34:06 -07001349 return mLines[mColumns * line + DESCENT];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001350 }
1351
Gilles Debunne66111472010-11-19 11:04:37 -08001352 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001353 public int getLineStart(int line) {
1354 return mLines[mColumns * line + START] & START_MASK;
1355 }
1356
Gilles Debunne66111472010-11-19 11:04:37 -08001357 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001358 public int getParagraphDirection(int line) {
1359 return mLines[mColumns * line + DIR] >> DIR_SHIFT;
1360 }
1361
Gilles Debunne66111472010-11-19 11:04:37 -08001362 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001363 public boolean getLineContainsTab(int line) {
1364 return (mLines[mColumns * line + TAB] & TAB_MASK) != 0;
1365 }
1366
Gilles Debunne66111472010-11-19 11:04:37 -08001367 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001368 public final Directions getLineDirections(int line) {
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001369 if (line > getLineCount()) {
1370 throw new ArrayIndexOutOfBoundsException();
1371 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001372 return mLineDirections[line];
1373 }
1374
Gilles Debunne66111472010-11-19 11:04:37 -08001375 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001376 public int getTopPadding() {
1377 return mTopPadding;
1378 }
1379
Gilles Debunne66111472010-11-19 11:04:37 -08001380 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001381 public int getBottomPadding() {
1382 return mBottomPadding;
1383 }
1384
Raph Levien26d443a2015-03-30 14:18:32 -07001385 /**
1386 * @hide
1387 */
1388 @Override
1389 public int getHyphen(int line) {
Keisuke Kuroyanagif5af4a32016-08-31 21:40:53 +09001390 return mLines[mColumns * line + HYPHEN] & HYPHEN_MASK;
Raph Levien26d443a2015-03-30 14:18:32 -07001391 }
1392
Raph Levien2ea52902015-07-01 14:39:31 -07001393 /**
1394 * @hide
1395 */
1396 @Override
1397 public int getIndentAdjust(int line, Alignment align) {
1398 if (align == Alignment.ALIGN_LEFT) {
1399 if (mLeftIndents == null) {
1400 return 0;
1401 } else {
1402 return mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1403 }
1404 } else if (align == Alignment.ALIGN_RIGHT) {
1405 if (mRightIndents == null) {
1406 return 0;
1407 } else {
1408 return -mRightIndents[Math.min(line, mRightIndents.length - 1)];
1409 }
1410 } else if (align == Alignment.ALIGN_CENTER) {
1411 int left = 0;
1412 if (mLeftIndents != null) {
1413 left = mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1414 }
1415 int right = 0;
1416 if (mRightIndents != null) {
1417 right = mRightIndents[Math.min(line, mRightIndents.length - 1)];
1418 }
1419 return (left - right) >> 1;
1420 } else {
1421 throw new AssertionError("unhandled alignment " + align);
1422 }
1423 }
1424
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001425 @Override
1426 public int getEllipsisCount(int line) {
1427 if (mColumns < COLUMNS_ELLIPSIZE) {
1428 return 0;
1429 }
1430
1431 return mLines[mColumns * line + ELLIPSIS_COUNT];
1432 }
1433
1434 @Override
1435 public int getEllipsisStart(int line) {
1436 if (mColumns < COLUMNS_ELLIPSIZE) {
1437 return 0;
1438 }
1439
1440 return mLines[mColumns * line + ELLIPSIS_START];
1441 }
1442
1443 @Override
1444 public int getEllipsizedWidth() {
1445 return mEllipsizedWidth;
1446 }
1447
Siyamed Sinir0745c722016-05-31 20:39:33 -07001448 /**
1449 * Return the total height of this layout.
1450 *
1451 * @param cap if true and max lines is set, returns the height of the layout at the max lines.
1452 *
1453 * @hide
1454 */
1455 public int getHeight(boolean cap) {
1456 if (cap && mLineCount >= mMaximumVisibleLineCount && mMaxLineHeight == -1 &&
1457 Log.isLoggable(TAG, Log.WARN)) {
1458 Log.w(TAG, "maxLineHeight should not be -1. "
1459 + " maxLines:" + mMaximumVisibleLineCount
1460 + " lineCount:" + mLineCount);
1461 }
1462
1463 return cap && mLineCount >= mMaximumVisibleLineCount && mMaxLineHeight != -1 ?
1464 mMaxLineHeight : super.getHeight();
1465 }
1466
Raph Levien70616ec2015-03-04 10:41:30 -08001467 private static native long nNewBuilder();
1468 private static native void nFreeBuilder(long nativePtr);
1469 private static native void nFinishBuilder(long nativePtr);
Raph Levien26d443a2015-03-30 14:18:32 -07001470
Roozbeh Pournadera59c3fe2017-02-27 10:13:44 -08001471 /* package */ static native long nLoadHyphenator(ByteBuffer buf, int offset,
1472 int minPrefix, int minSuffix);
Raph Levien26d443a2015-03-30 14:18:32 -07001473
Roozbeh Pournaderef7cfa12017-06-15 12:39:04 -07001474 private static native void nSetLocales(long nativePtr, String locales,
1475 long[] nativeHyphenators);
Raph Levien70616ec2015-03-04 10:41:30 -08001476
Raph Leviene319d5a2015-04-14 23:51:07 -07001477 private static native void nSetIndents(long nativePtr, int[] indents);
1478
Raph Levienc94f7422015-03-06 19:19:48 -08001479 // Set up paragraph text and settings; done as one big method to minimize jni crossings
1480 private static native void nSetupParagraph(long nativePtr, char[] text, int length,
1481 float firstWidth, int firstWidthLineCount, float restWidth,
Seigo Nonaka09da71a2016-11-28 16:24:14 +09001482 int[] variableTabStops, int defaultTabStop, int breakStrategy, int hyphenationFrequency,
1483 boolean isJustified);
Raph Levien70616ec2015-03-04 10:41:30 -08001484
Seigo Nonaka318ca042017-08-01 16:36:18 -07001485 private static native float nAddStyleRun(long nativePtr, long nativePaint, int start, int end,
1486 boolean isRtl);
Raph Levien70616ec2015-03-04 10:41:30 -08001487
1488 private static native void nAddMeasuredRun(long nativePtr,
1489 int start, int end, float[] widths);
1490
1491 private static native void nAddReplacementRun(long nativePtr, int start, int end, float width);
1492
1493 private static native void nGetWidths(long nativePtr, float[] widths);
1494
Anish Athalyec8f9e622014-07-21 15:26:34 -07001495 // populates LineBreaks and returns the number of breaks found
1496 //
1497 // the arrays inside the LineBreaks objects are passed in as well
1498 // to reduce the number of JNI calls in the common case where the
1499 // arrays do not have to be resized
Raph Levienc94f7422015-03-06 19:19:48 -08001500 private static native int nComputeLineBreaks(long nativePtr, LineBreaks recycle,
Roozbeh Pournader737dfea2017-08-10 11:32:24 -07001501 int[] recycleBreaks, float[] recycleWidths, float[] recycleAscents,
1502 float[] recycleDescents, int[] recycleFlags, int recycleLength);
Anish Athalye88b5b0b2014-06-24 14:39:43 -07001503
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001504 private int mLineCount;
1505 private int mTopPadding, mBottomPadding;
1506 private int mColumns;
1507 private int mEllipsizedWidth;
1508
Siyamed Sinir0745c722016-05-31 20:39:33 -07001509 /**
1510 * Keeps track if ellipsize is applied to the text.
1511 */
1512 private boolean mEllipsized;
1513
1514 /**
1515 * If maxLines is set, ellipsize is not set, and the actual line count of text is greater than
1516 * or equal to maxLine, this variable holds the ideal visual height of the maxLine'th line
1517 * starting from the top of the layout. If maxLines is not set its value will be -1.
1518 *
1519 * The value is the same as getLineTop(maxLines) for ellipsized version where structurally no
1520 * more than maxLines is contained.
1521 */
Siyamed Sinira19cd512017-08-03 22:01:56 -07001522 private int mMaxLineHeight = DEFAULT_MAX_LINE_HEIGHT;
Siyamed Sinir0745c722016-05-31 20:39:33 -07001523
Roozbeh Pournadere88b5df2017-07-25 13:52:57 -07001524 private TextPaint mWorkPaint = new TextPaint();
1525
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001526 private static final int COLUMNS_NORMAL = 5;
1527 private static final int COLUMNS_ELLIPSIZE = 7;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001528 private static final int START = 0;
1529 private static final int DIR = START;
1530 private static final int TAB = START;
1531 private static final int TOP = 1;
1532 private static final int DESCENT = 2;
Siyamed Sinir0fa89d62017-07-24 20:46:41 -07001533 private static final int EXTRA = 3;
1534 private static final int HYPHEN = 4;
1535 private static final int ELLIPSIS_START = 5;
1536 private static final int ELLIPSIS_COUNT = 6;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001537
1538 private int[] mLines;
1539 private Directions[] mLineDirections;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001540 private int mMaximumVisibleLineCount = Integer.MAX_VALUE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001541
1542 private static final int START_MASK = 0x1FFFFFFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001543 private static final int DIR_SHIFT = 30;
1544 private static final int TAB_MASK = 0x20000000;
Keisuke Kuroyanagif5af4a32016-08-31 21:40:53 +09001545 private static final int HYPHEN_MASK = 0xFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001546
Doug Feltc982f602010-05-25 11:51:40 -07001547 private static final int TAB_INCREMENT = 20; // same as Layout, but that's private
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001548
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001549 private static final char CHAR_NEW_LINE = '\n';
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001550
1551 private static final double EXTRA_ROUNDING = 0.5;
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001552
Siyamed Sinira19cd512017-08-03 22:01:56 -07001553 private static final int DEFAULT_MAX_LINE_HEIGHT = -1;
1554
Anish Athalyec8f9e622014-07-21 15:26:34 -07001555 // This is used to return three arrays from a single JNI call when
1556 // performing line breaking
Deepanshu Gupta70539192014-10-15 15:57:40 -07001557 /*package*/ static class LineBreaks {
Anish Athalyec8f9e622014-07-21 15:26:34 -07001558 private static final int INITIAL_SIZE = 16;
1559 public int[] breaks = new int[INITIAL_SIZE];
1560 public float[] widths = new float[INITIAL_SIZE];
Roozbeh Pournader737dfea2017-08-10 11:32:24 -07001561 public float[] ascents = new float[INITIAL_SIZE];
1562 public float[] descents = new float[INITIAL_SIZE];
Roozbeh Pournader112d9c72015-08-07 12:44:41 -07001563 public int[] flags = new int[INITIAL_SIZE]; // hasTab
Anish Athalyec8f9e622014-07-21 15:26:34 -07001564 // breaks, widths, and flags should all have the same length
1565 }
1566
Raph Levien2ea52902015-07-01 14:39:31 -07001567 private int[] mLeftIndents;
1568 private int[] mRightIndents;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001569}