blob: 464710b29d2ac3a4a62df780cdfcc04afe831cd4 [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
Anish Athalyec8f9e622014-07-21 15:26:34 -070032import java.util.Arrays;
Raph Levien4c1f12e2015-03-02 16:29:23 -080033import java.util.Locale;
Anish Athalyec8f9e622014-07-21 15:26:34 -070034
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080035/**
36 * StaticLayout is a Layout for text that will not be edited after it
37 * is laid out. Use {@link DynamicLayout} for text that may change.
38 * <p>This is used by widgets to control text layout. You should not need
39 * to use this class directly unless you are implementing your own widget
40 * or custom display object, or would be tempted to call
Doug Felt4e0c5e52010-03-15 16:56:02 -070041 * {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int,
42 * float, float, android.graphics.Paint)
43 * Canvas.drawText()} directly.</p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080044 */
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -080045public class StaticLayout extends Layout {
46
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -070047 static final String TAG = "StaticLayout";
48
Raph Leviend3ab6922015-03-02 14:30:53 -080049 /**
Raph Levien531c30c2015-04-30 16:29:59 -070050 * Builder for static layouts. The builder is a newer pattern for constructing
51 * StaticLayout objects and should be preferred over the constructors,
52 * particularly to access newer features. To build a static layout, first
53 * call {@link #obtain} with the required arguments (text, paint, and width),
54 * then call setters for optional parameters, and finally {@link #build}
55 * to build the StaticLayout object. Parameters not explicitly set will get
56 * default values.
Raph Leviend3ab6922015-03-02 14:30:53 -080057 */
58 public final static class Builder {
Raph Levien4c1f12e2015-03-02 16:29:23 -080059 private Builder() {
60 mNativePtr = nNewBuilder();
61 }
62
Raph Levien531c30c2015-04-30 16:29:59 -070063 /**
64 * Obtain a builder for constructing StaticLayout objects
65 *
66 * @param source The text to be laid out, optionally with spans
67 * @param start The index of the start of the text
68 * @param end The index + 1 of the end of the text
69 * @param paint The base paint used for layout
70 * @param width The width in pixels
71 * @return a builder object used for constructing the StaticLayout
72 */
Raph Levienebd66ca2015-04-30 15:27:57 -070073 public static Builder obtain(CharSequence source, int start, int end, TextPaint paint,
74 int width) {
Raph Levien39b4db72015-03-25 13:18:20 -070075 Builder b = sPool.acquire();
Raph Leviend3ab6922015-03-02 14:30:53 -080076 if (b == null) {
77 b = new Builder();
78 }
79
80 // set default initial values
Raph Levien39b4db72015-03-25 13:18:20 -070081 b.mText = source;
82 b.mStart = start;
83 b.mEnd = end;
Raph Levienebd66ca2015-04-30 15:27:57 -070084 b.mPaint = paint;
Raph Levien39b4db72015-03-25 13:18:20 -070085 b.mWidth = width;
86 b.mAlignment = Alignment.ALIGN_NORMAL;
Raph Leviend3ab6922015-03-02 14:30:53 -080087 b.mTextDir = TextDirectionHeuristics.FIRSTSTRONG_LTR;
88 b.mSpacingMult = 1.0f;
89 b.mSpacingAdd = 0.0f;
90 b.mIncludePad = true;
Raph Levien39b4db72015-03-25 13:18:20 -070091 b.mEllipsizedWidth = width;
Raph Leviend3ab6922015-03-02 14:30:53 -080092 b.mEllipsize = null;
93 b.mMaxLines = Integer.MAX_VALUE;
Raph Levien3bd60c72015-05-06 14:26:35 -070094 b.mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
Roozbeh Pournader95c7a132015-05-12 12:01:06 -070095 b.mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
Raph Leviend3ab6922015-03-02 14:30:53 -080096
97 b.mMeasuredText = MeasuredText.obtain();
98 return b;
99 }
100
Raph Levien39b4db72015-03-25 13:18:20 -0700101 private static void recycle(Builder b) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800102 b.mPaint = null;
103 b.mText = null;
104 MeasuredText.recycle(b.mMeasuredText);
Raph Levien3bd60c72015-05-06 14:26:35 -0700105 b.mMeasuredText = null;
106 nFinishBuilder(b.mNativePtr);
Raph Levien39b4db72015-03-25 13:18:20 -0700107 sPool.release(b);
Raph Leviend3ab6922015-03-02 14:30:53 -0800108 }
109
110 // release any expensive state
111 /* package */ void finish() {
Raph Levien4c1f12e2015-03-02 16:29:23 -0800112 nFinishBuilder(mNativePtr);
Raph Leviend3ab6922015-03-02 14:30:53 -0800113 mMeasuredText.finish();
114 }
115
116 public Builder setText(CharSequence source) {
117 return setText(source, 0, source.length());
118 }
119
Raph Levien531c30c2015-04-30 16:29:59 -0700120 /**
121 * Set the text. Only useful when re-using the builder, which is done for
122 * the internal implementation of {@link DynamicLayout} but not as part
123 * of normal {@link StaticLayout} usage.
124 *
125 * @param source The text to be laid out, optionally with spans
126 * @param start The index of the start of the text
127 * @param end The index + 1 of the end of the text
128 * @return this builder, useful for chaining
129 *
130 * @hide
131 */
Raph Leviend3ab6922015-03-02 14:30:53 -0800132 public Builder setText(CharSequence source, int start, int end) {
133 mText = source;
134 mStart = start;
135 mEnd = end;
136 return this;
137 }
138
Raph Levien531c30c2015-04-30 16:29:59 -0700139 /**
140 * Set the paint. Internal for reuse cases only.
141 *
142 * @param paint The base paint used for layout
143 * @return this builder, useful for chaining
144 *
145 * @hide
146 */
Raph Leviend3ab6922015-03-02 14:30:53 -0800147 public Builder setPaint(TextPaint paint) {
148 mPaint = paint;
149 return this;
150 }
151
Raph Levien531c30c2015-04-30 16:29:59 -0700152 /**
153 * Set the width. Internal for reuse cases only.
154 *
155 * @param width The width in pixels
156 * @return this builder, useful for chaining
157 *
158 * @hide
159 */
Raph Leviend3ab6922015-03-02 14:30:53 -0800160 public Builder setWidth(int width) {
161 mWidth = width;
162 if (mEllipsize == null) {
163 mEllipsizedWidth = width;
164 }
165 return this;
166 }
167
Raph Levien531c30c2015-04-30 16:29:59 -0700168 /**
169 * Set the alignment. The default is {@link Layout.Alignment#ALIGN_NORMAL}.
170 *
171 * @param alignment Alignment for the resulting {@link StaticLayout}
172 * @return this builder, useful for chaining
173 */
Raph Levien39b4db72015-03-25 13:18:20 -0700174 public Builder setAlignment(Alignment alignment) {
175 mAlignment = alignment;
176 return this;
177 }
178
Raph Levien531c30c2015-04-30 16:29:59 -0700179 /**
180 * Set the text direction heuristic. The text direction heuristic is used to
181 * resolve text direction based per-paragraph based on the input text. The default is
182 * {@link TextDirectionHeuristics#FIRSTSTRONG_LTR}.
183 *
184 * @param textDir text direction heuristic for resolving BiDi behavior.
185 * @return this builder, useful for chaining
186 */
Raph Leviena6a08282015-06-03 13:20:45 -0700187 public Builder setTextDirection(TextDirectionHeuristic textDir) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800188 mTextDir = textDir;
189 return this;
190 }
191
Raph Levien531c30c2015-04-30 16:29:59 -0700192 /**
193 * Set line spacing parameters. The default is 0.0 for {@code spacingAdd}
194 * and 1.0 for {@code spacingMult}.
195 *
196 * @param spacingAdd line spacing add
197 * @param spacingMult line spacing multiplier
198 * @return this builder, useful for chaining
199 * @see android.widget.TextView#setLineSpacing
200 */
201 public Builder setLineSpacing(float spacingAdd, float spacingMult) {
202 mSpacingAdd = spacingAdd;
Raph Leviend3ab6922015-03-02 14:30:53 -0800203 mSpacingMult = spacingMult;
204 return this;
205 }
206
Raph Levien531c30c2015-04-30 16:29:59 -0700207 /**
208 * Set whether to include extra space beyond font ascent and descent (which is
209 * needed to avoid clipping in some languages, such as Arabic and Kannada). The
210 * default is {@code true}.
211 *
212 * @param includePad whether to include padding
213 * @return this builder, useful for chaining
214 * @see android.widget.TextView#setIncludeFontPadding
215 */
Raph Leviend3ab6922015-03-02 14:30:53 -0800216 public Builder setIncludePad(boolean includePad) {
217 mIncludePad = includePad;
218 return this;
219 }
220
Raph Levien531c30c2015-04-30 16:29:59 -0700221 /**
222 * Set the width as used for ellipsizing purposes, if it differs from the
223 * normal layout width. The default is the {@code width}
224 * passed to {@link #obtain}.
225 *
226 * @param ellipsizedWidth width used for ellipsizing, in pixels
227 * @return this builder, useful for chaining
228 * @see android.widget.TextView#setEllipsize
229 */
Raph Leviend3ab6922015-03-02 14:30:53 -0800230 public Builder setEllipsizedWidth(int ellipsizedWidth) {
231 mEllipsizedWidth = ellipsizedWidth;
232 return this;
233 }
234
Raph Levien531c30c2015-04-30 16:29:59 -0700235 /**
236 * Set ellipsizing on the layout. Causes words that are longer than the view
237 * is wide, or exceeding the number of lines (see #setMaxLines) in the case
238 * of {@link android.text.TextUtils.TruncateAt#END} or
239 * {@link android.text.TextUtils.TruncateAt#MARQUEE}, to be ellipsized instead
240 * of broken. The default is
241 * {@code null}, indicating no ellipsis is to be applied.
242 *
243 * @param ellipsize type of ellipsis behavior
244 * @return this builder, useful for chaining
245 * @see android.widget.TextView#setEllipsize
246 */
247 public Builder setEllipsize(@Nullable TextUtils.TruncateAt ellipsize) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800248 mEllipsize = ellipsize;
249 return this;
250 }
251
Raph Levien531c30c2015-04-30 16:29:59 -0700252 /**
253 * Set maximum number of lines. This is particularly useful in the case of
254 * ellipsizing, where it changes the layout of the last line. The default is
255 * unlimited.
256 *
257 * @param maxLines maximum number of lines in the layout
258 * @return this builder, useful for chaining
259 * @see android.widget.TextView#setMaxLines
260 */
Raph Leviend3ab6922015-03-02 14:30:53 -0800261 public Builder setMaxLines(int maxLines) {
262 mMaxLines = maxLines;
263 return this;
264 }
265
Raph Levien531c30c2015-04-30 16:29:59 -0700266 /**
267 * Set break strategy, useful for selecting high quality or balanced paragraph
268 * layout options. The default is {@link Layout#BREAK_STRATEGY_SIMPLE}.
269 *
270 * @param breakStrategy break strategy for paragraph layout
271 * @return this builder, useful for chaining
272 * @see android.widget.TextView#setBreakStrategy
273 */
Raph Levien39b4db72015-03-25 13:18:20 -0700274 public Builder setBreakStrategy(@BreakStrategy int breakStrategy) {
275 mBreakStrategy = breakStrategy;
276 return this;
277 }
278
Raph Levien531c30c2015-04-30 16:29:59 -0700279 /**
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700280 * Set hyphenation frequency, to control the amount of automatic hyphenation used. The
281 * default is {@link Layout#HYPHENATION_FREQUENCY_NONE}.
282 *
283 * @param hyphenationFrequency hyphenation frequency for the paragraph
284 * @return this builder, useful for chaining
285 * @see android.widget.TextView#setHyphenationFrequency
286 */
287 public Builder setHyphenationFrequency(@HyphenationFrequency int hyphenationFrequency) {
288 mHyphenationFrequency = hyphenationFrequency;
289 return this;
290 }
291
292 /**
Raph Levien531c30c2015-04-30 16:29:59 -0700293 * Set indents. Arguments are arrays holding an indent amount, one per line, measured in
294 * pixels. For lines past the last element in the array, the last element repeats.
295 *
296 * @param leftIndents array of indent values for left margin, in pixels
297 * @param rightIndents array of indent values for right margin, in pixels
298 * @return this builder, useful for chaining
299 * @see android.widget.TextView#setIndents
300 */
Raph Leviene319d5a2015-04-14 23:51:07 -0700301 public Builder setIndents(int[] leftIndents, int[] rightIndents) {
302 int leftLen = leftIndents == null ? 0 : leftIndents.length;
303 int rightLen = rightIndents == null ? 0 : rightIndents.length;
304 int[] indents = new int[Math.max(leftLen, rightLen)];
305 for (int i = 0; i < indents.length; i++) {
306 int leftMargin = i < leftLen ? leftIndents[i] : 0;
307 int rightMargin = i < rightLen ? rightIndents[i] : 0;
308 indents[i] = leftMargin + rightMargin;
309 }
310 nSetIndents(mNativePtr, indents);
311 return this;
312 }
313
Raph Levien70616ec2015-03-04 10:41:30 -0800314 /**
315 * Measurement and break iteration is done in native code. The protocol for using
316 * the native code is as follows.
317 *
Raph Levien26d443a2015-03-30 14:18:32 -0700318 * For each paragraph, do a nSetupParagraph, which sets paragraph text, line width, tab
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700319 * stops, break strategy, and hyphenation frequency (and possibly other parameters in the
320 * future).
Raph Levienc94f7422015-03-06 19:19:48 -0800321 *
322 * Then, for each run within the paragraph:
Raph Levien70616ec2015-03-04 10:41:30 -0800323 * - setLocale (this must be done at least for the first run, optional afterwards)
324 * - one of the following, depending on the type of run:
325 * + addStyleRun (a text run, to be measured in native code)
326 * + addMeasuredRun (a run already measured in Java, passed into native code)
327 * + addReplacementRun (a replacement run, width is given)
328 *
329 * After measurement, nGetWidths() is valid if the widths are needed (eg for ellipsis).
330 * Run nComputeLineBreaks() to obtain line breaks for the paragraph.
331 *
332 * After all paragraphs, call finish() to release expensive buffers.
333 */
334
335 private void setLocale(Locale locale) {
Raph Levien4c1f12e2015-03-02 16:29:23 -0800336 if (!locale.equals(mLocale)) {
Raph Levien26d443a2015-03-30 14:18:32 -0700337 nSetLocale(mNativePtr, locale.toLanguageTag(), Hyphenator.get(locale));
Raph Levien4c1f12e2015-03-02 16:29:23 -0800338 mLocale = locale;
339 }
340 }
341
Raph Levien70616ec2015-03-04 10:41:30 -0800342 /* package */ float addStyleRun(TextPaint paint, int start, int end, boolean isRtl) {
343 return nAddStyleRun(mNativePtr, paint.getNativeInstance(), paint.mNativeTypeface,
344 start, end, isRtl);
345 }
346
347 /* package */ void addMeasuredRun(int start, int end, float[] widths) {
348 nAddMeasuredRun(mNativePtr, start, end, widths);
349 }
350
351 /* package */ void addReplacementRun(int start, int end, float width) {
352 nAddReplacementRun(mNativePtr, start, end, width);
353 }
354
Raph Levien531c30c2015-04-30 16:29:59 -0700355 /**
356 * Build the {@link StaticLayout} after options have been set.
357 *
358 * <p>Note: the builder object must not be reused in any way after calling this
359 * method. Setting parameters after calling this method, or calling it a second
360 * time on the same builder object, will likely lead to unexpected results.
361 *
362 * @return the newly constructed {@link StaticLayout} object
363 */
Raph Leviend3ab6922015-03-02 14:30:53 -0800364 public StaticLayout build() {
Raph Levien39b4db72015-03-25 13:18:20 -0700365 StaticLayout result = new StaticLayout(this);
366 Builder.recycle(this);
Raph Leviend3ab6922015-03-02 14:30:53 -0800367 return result;
368 }
369
Raph Levien4c1f12e2015-03-02 16:29:23 -0800370 @Override
371 protected void finalize() throws Throwable {
372 try {
373 nFreeBuilder(mNativePtr);
374 } finally {
375 super.finalize();
376 }
377 }
378
379 /* package */ long mNativePtr;
380
Raph Leviend3ab6922015-03-02 14:30:53 -0800381 CharSequence mText;
382 int mStart;
383 int mEnd;
384 TextPaint mPaint;
385 int mWidth;
Raph Levien39b4db72015-03-25 13:18:20 -0700386 Alignment mAlignment;
Raph Leviend3ab6922015-03-02 14:30:53 -0800387 TextDirectionHeuristic mTextDir;
388 float mSpacingMult;
389 float mSpacingAdd;
390 boolean mIncludePad;
391 int mEllipsizedWidth;
392 TextUtils.TruncateAt mEllipsize;
393 int mMaxLines;
Raph Levien39b4db72015-03-25 13:18:20 -0700394 int mBreakStrategy;
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700395 int mHyphenationFrequency;
Raph Leviend3ab6922015-03-02 14:30:53 -0800396
397 Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
398
399 // This will go away and be subsumed by native builder code
400 MeasuredText mMeasuredText;
401
Raph Levien4c1f12e2015-03-02 16:29:23 -0800402 Locale mLocale;
403
Raph Levien39b4db72015-03-25 13:18:20 -0700404 private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<Builder>(3);
Raph Leviend3ab6922015-03-02 14:30:53 -0800405 }
406
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800407 public StaticLayout(CharSequence source, TextPaint paint,
408 int width,
409 Alignment align, float spacingmult, float spacingadd,
410 boolean includepad) {
411 this(source, 0, source.length(), paint, width, align,
412 spacingmult, spacingadd, includepad);
413 }
414
Doug Feltcb3791202011-07-07 11:57:48 -0700415 /**
416 * @hide
417 */
418 public StaticLayout(CharSequence source, TextPaint paint,
419 int width, Alignment align, TextDirectionHeuristic textDir,
420 float spacingmult, float spacingadd,
421 boolean includepad) {
422 this(source, 0, source.length(), paint, width, align, textDir,
423 spacingmult, spacingadd, includepad);
424 }
425
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800426 public StaticLayout(CharSequence source, int bufstart, int bufend,
427 TextPaint paint, int outerwidth,
428 Alignment align,
429 float spacingmult, float spacingadd,
430 boolean includepad) {
431 this(source, bufstart, bufend, paint, outerwidth, align,
432 spacingmult, spacingadd, includepad, null, 0);
433 }
434
Doug Feltcb3791202011-07-07 11:57:48 -0700435 /**
436 * @hide
437 */
438 public StaticLayout(CharSequence source, int bufstart, int bufend,
439 TextPaint paint, int outerwidth,
440 Alignment align, TextDirectionHeuristic textDir,
441 float spacingmult, float spacingadd,
442 boolean includepad) {
443 this(source, bufstart, bufend, paint, outerwidth, align, textDir,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700444 spacingmult, spacingadd, includepad, null, 0, Integer.MAX_VALUE);
Doug Feltcb3791202011-07-07 11:57:48 -0700445}
446
447 public StaticLayout(CharSequence source, int bufstart, int bufend,
448 TextPaint paint, int outerwidth,
449 Alignment align,
450 float spacingmult, float spacingadd,
451 boolean includepad,
452 TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
453 this(source, bufstart, bufend, paint, outerwidth, align,
454 TextDirectionHeuristics.FIRSTSTRONG_LTR,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700455 spacingmult, spacingadd, includepad, ellipsize, ellipsizedWidth, Integer.MAX_VALUE);
Doug Feltcb3791202011-07-07 11:57:48 -0700456 }
457
458 /**
459 * @hide
460 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800461 public StaticLayout(CharSequence source, int bufstart, int bufend,
462 TextPaint paint, int outerwidth,
Doug Feltcb3791202011-07-07 11:57:48 -0700463 Alignment align, TextDirectionHeuristic textDir,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800464 float spacingmult, float spacingadd,
465 boolean includepad,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700466 TextUtils.TruncateAt ellipsize, int ellipsizedWidth, int maxLines) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800467 super((ellipsize == null)
Doug Felt4e0c5e52010-03-15 16:56:02 -0700468 ? source
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800469 : (source instanceof Spanned)
470 ? new SpannedEllipsizer(source)
471 : new Ellipsizer(source),
Doug Feltcb3791202011-07-07 11:57:48 -0700472 paint, outerwidth, align, textDir, spacingmult, spacingadd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800473
Raph Levienebd66ca2015-04-30 15:27:57 -0700474 Builder b = Builder.obtain(source, bufstart, bufend, paint, outerwidth)
Raph Levien39b4db72015-03-25 13:18:20 -0700475 .setAlignment(align)
Raph Leviena6a08282015-06-03 13:20:45 -0700476 .setTextDirection(textDir)
Raph Levien531c30c2015-04-30 16:29:59 -0700477 .setLineSpacing(spacingadd, spacingmult)
Raph Leviend3ab6922015-03-02 14:30:53 -0800478 .setIncludePad(includepad)
479 .setEllipsizedWidth(ellipsizedWidth)
480 .setEllipsize(ellipsize)
481 .setMaxLines(maxLines);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800482 /*
483 * This is annoying, but we can't refer to the layout until
484 * superclass construction is finished, and the superclass
485 * constructor wants the reference to the display text.
Doug Felt4e0c5e52010-03-15 16:56:02 -0700486 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800487 * This will break if the superclass constructor ever actually
488 * cares about the content instead of just holding the reference.
489 */
490 if (ellipsize != null) {
491 Ellipsizer e = (Ellipsizer) getText();
492
493 e.mLayout = this;
494 e.mWidth = ellipsizedWidth;
495 e.mMethod = ellipsize;
496 mEllipsizedWidth = ellipsizedWidth;
497
498 mColumns = COLUMNS_ELLIPSIZE;
499 } else {
500 mColumns = COLUMNS_NORMAL;
501 mEllipsizedWidth = outerwidth;
502 }
503
Adam Lesinski776abc22014-03-07 11:30:59 -0500504 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2 * mColumns);
505 mLines = new int[mLineDirections.length];
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700506 mMaximumVisibleLineCount = maxLines;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800507
Raph Levien70616ec2015-03-04 10:41:30 -0800508 generate(b, b.mIncludePad, b.mIncludePad);
Doug Felte8e45f22010-03-29 14:58:40 -0700509
Raph Leviend3ab6922015-03-02 14:30:53 -0800510 Builder.recycle(b);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800511 }
512
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700513 /* package */ StaticLayout(CharSequence text) {
514 super(text, null, 0, null, 0, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800515
516 mColumns = COLUMNS_ELLIPSIZE;
Adam Lesinski776abc22014-03-07 11:30:59 -0500517 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2 * mColumns);
518 mLines = new int[mLineDirections.length];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800519 }
520
Raph Levien39b4db72015-03-25 13:18:20 -0700521 private StaticLayout(Builder b) {
522 super((b.mEllipsize == null)
523 ? b.mText
524 : (b.mText instanceof Spanned)
525 ? new SpannedEllipsizer(b.mText)
526 : new Ellipsizer(b.mText),
527 b.mPaint, b.mWidth, b.mAlignment, b.mSpacingMult, b.mSpacingAdd);
528
529 if (b.mEllipsize != null) {
530 Ellipsizer e = (Ellipsizer) getText();
531
532 e.mLayout = this;
533 e.mWidth = b.mEllipsizedWidth;
534 e.mMethod = b.mEllipsize;
535 mEllipsizedWidth = b.mEllipsizedWidth;
536
537 mColumns = COLUMNS_ELLIPSIZE;
538 } else {
539 mColumns = COLUMNS_NORMAL;
540 mEllipsizedWidth = b.mWidth;
541 }
542
543 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2 * mColumns);
544 mLines = new int[mLineDirections.length];
545 mMaximumVisibleLineCount = b.mMaxLines;
546
547 generate(b, b.mIncludePad, b.mIncludePad);
548 }
549
Raph Leviend3ab6922015-03-02 14:30:53 -0800550 /* package */ void generate(Builder b, boolean includepad, boolean trackpad) {
551 CharSequence source = b.mText;
552 int bufStart = b.mStart;
553 int bufEnd = b.mEnd;
554 TextPaint paint = b.mPaint;
555 int outerWidth = b.mWidth;
556 TextDirectionHeuristic textDir = b.mTextDir;
557 float spacingmult = b.mSpacingMult;
558 float spacingadd = b.mSpacingAdd;
559 float ellipsizedWidth = b.mEllipsizedWidth;
560 TextUtils.TruncateAt ellipsize = b.mEllipsize;
Raph Levien4c1f12e2015-03-02 16:29:23 -0800561 LineBreaks lineBreaks = new LineBreaks(); // TODO: move to builder to avoid allocation costs
Anish Athalyec8f9e622014-07-21 15:26:34 -0700562 // store span end locations
563 int[] spanEndCache = new int[4];
564 // store fontMetrics per span range
565 // must be a multiple of 4 (and > 0) (store top, bottom, ascent, and descent per range)
566 int[] fmCache = new int[4 * 4];
Raph Levien4c1f12e2015-03-02 16:29:23 -0800567 b.setLocale(paint.getTextLocale()); // TODO: also respect LocaleSpan within the text
Anish Athalye88b5b0b2014-06-24 14:39:43 -0700568
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800569 mLineCount = 0;
570
571 int v = 0;
572 boolean needMultiply = (spacingmult != 1 || spacingadd != 0);
573
Raph Leviend3ab6922015-03-02 14:30:53 -0800574 Paint.FontMetricsInt fm = b.mFontMetricsInt;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800575 int[] chooseHtv = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800576
Raph Leviend3ab6922015-03-02 14:30:53 -0800577 MeasuredText measured = b.mMeasuredText;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800578
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800579 Spanned spanned = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800580 if (source instanceof Spanned)
581 spanned = (Spanned) source;
582
Doug Felte8e45f22010-03-29 14:58:40 -0700583 int paraEnd;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800584 for (int paraStart = bufStart; paraStart <= bufEnd; paraStart = paraEnd) {
585 paraEnd = TextUtils.indexOf(source, CHAR_NEW_LINE, paraStart, bufEnd);
Doug Felte8e45f22010-03-29 14:58:40 -0700586 if (paraEnd < 0)
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800587 paraEnd = bufEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800588 else
Doug Felte8e45f22010-03-29 14:58:40 -0700589 paraEnd++;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800590
Anish Athalyec8f9e622014-07-21 15:26:34 -0700591 int firstWidthLineCount = 1;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800592 int firstWidth = outerWidth;
593 int restWidth = outerWidth;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800594
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800595 LineHeightSpan[] chooseHt = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800596
597 if (spanned != null) {
Eric Fischer74d31ef2010-08-05 15:29:36 -0700598 LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
Doug Felte8e45f22010-03-29 14:58:40 -0700599 LeadingMarginSpan.class);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800600 for (int i = 0; i < sp.length; i++) {
Mark Wagner7b5676e2009-10-16 11:44:23 -0700601 LeadingMarginSpan lms = sp[i];
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800602 firstWidth -= sp[i].getLeadingMargin(true);
603 restWidth -= sp[i].getLeadingMargin(false);
Doug Feltcb3791202011-07-07 11:57:48 -0700604
Doug Feltc982f602010-05-25 11:51:40 -0700605 // LeadingMarginSpan2 is odd. The count affects all
Anish Athalyeab08c6d2014-08-08 12:09:58 -0700606 // leading margin spans, not just this particular one
Doug Feltc982f602010-05-25 11:51:40 -0700607 if (lms instanceof LeadingMarginSpan2) {
608 LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700609 firstWidthLineCount = Math.max(firstWidthLineCount,
610 lms2.getLeadingMarginLineCount());
Mark Wagner7b5676e2009-10-16 11:44:23 -0700611 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800612 }
613
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800614 chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800615
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800616 if (chooseHt.length != 0) {
617 if (chooseHtv == null ||
618 chooseHtv.length < chooseHt.length) {
Adam Lesinski776abc22014-03-07 11:30:59 -0500619 chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800620 }
621
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800622 for (int i = 0; i < chooseHt.length; i++) {
623 int o = spanned.getSpanStart(chooseHt[i]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800624
Doug Felte8e45f22010-03-29 14:58:40 -0700625 if (o < paraStart) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800626 // starts in this layout, before the
627 // current paragraph
628
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800629 chooseHtv[i] = getLineTop(getLineForOffset(o));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800630 } else {
631 // starts in this paragraph
632
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800633 chooseHtv[i] = v;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800634 }
635 }
636 }
637 }
638
Raph Levien70616ec2015-03-04 10:41:30 -0800639 measured.setPara(source, paraStart, paraEnd, textDir, b);
Doug Felte8e45f22010-03-29 14:58:40 -0700640 char[] chs = measured.mChars;
641 float[] widths = measured.mWidths;
642 byte[] chdirs = measured.mLevels;
643 int dir = measured.mDir;
644 boolean easy = measured.mEasy;
Raph Levienc94f7422015-03-06 19:19:48 -0800645
646 // tab stop locations
647 int[] variableTabStops = null;
648 if (spanned != null) {
649 TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
650 paraEnd, TabStopSpan.class);
651 if (spans.length > 0) {
652 int[] stops = new int[spans.length];
653 for (int i = 0; i < spans.length; i++) {
654 stops[i] = spans[i].getTabStop();
655 }
656 Arrays.sort(stops, 0, stops.length);
657 variableTabStops = stops;
658 }
659 }
660
Raph Levienc94f7422015-03-06 19:19:48 -0800661 nSetupParagraph(b.mNativePtr, chs, paraEnd - paraStart,
662 firstWidth, firstWidthLineCount, restWidth,
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700663 variableTabStops, TAB_INCREMENT, b.mBreakStrategy, b.mHyphenationFrequency);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800664
Anish Athalyec8f9e622014-07-21 15:26:34 -0700665 // measurement has to be done before performing line breaking
666 // but we don't want to recompute fontmetrics or span ranges the
667 // second time, so we cache those and then use those stored values
668 int fmCacheCount = 0;
669 int spanEndCacheCount = 0;
Gilles Debunnecd943a72012-06-07 17:54:47 -0700670 for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
Anish Athalyec8f9e622014-07-21 15:26:34 -0700671 if (fmCacheCount * 4 >= fmCache.length) {
672 int[] grow = new int[fmCacheCount * 4 * 2];
673 System.arraycopy(fmCache, 0, grow, 0, fmCacheCount * 4);
674 fmCache = grow;
675 }
676
677 if (spanEndCacheCount >= spanEndCache.length) {
678 int[] grow = new int[spanEndCacheCount * 2];
679 System.arraycopy(spanEndCache, 0, grow, 0, spanEndCacheCount);
680 spanEndCache = grow;
681 }
Doug Felte8e45f22010-03-29 14:58:40 -0700682
Gilles Debunnecd943a72012-06-07 17:54:47 -0700683 if (spanned == null) {
684 spanEnd = paraEnd;
Doug Felt23241882010-06-02 14:41:06 -0700685 int spanLen = spanEnd - spanStart;
Gilles Debunnecd943a72012-06-07 17:54:47 -0700686 measured.addStyleRun(paint, spanLen, fm);
687 } else {
688 spanEnd = spanned.nextSpanTransition(spanStart, paraEnd,
689 MetricAffectingSpan.class);
690 int spanLen = spanEnd - spanStart;
691 MetricAffectingSpan[] spans =
Doug Felt23241882010-06-02 14:41:06 -0700692 spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class);
Gilles Debunnecd943a72012-06-07 17:54:47 -0700693 spans = TextUtils.removeEmptySpans(spans, spanned, MetricAffectingSpan.class);
694 measured.addStyleRun(paint, spans, spanLen, fm);
Doug Felt23241882010-06-02 14:41:06 -0700695 }
696
Anish Athalyec8f9e622014-07-21 15:26:34 -0700697 // the order of storage here (top, bottom, ascent, descent) has to match the code below
698 // where these values are retrieved
699 fmCache[fmCacheCount * 4 + 0] = fm.top;
700 fmCache[fmCacheCount * 4 + 1] = fm.bottom;
701 fmCache[fmCacheCount * 4 + 2] = fm.ascent;
702 fmCache[fmCacheCount * 4 + 3] = fm.descent;
703 fmCacheCount++;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800704
Anish Athalyec8f9e622014-07-21 15:26:34 -0700705 spanEndCache[spanEndCacheCount] = spanEnd;
706 spanEndCacheCount++;
707 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800708
Raph Levien70616ec2015-03-04 10:41:30 -0800709 nGetWidths(b.mNativePtr, widths);
Raph Levienc94f7422015-03-06 19:19:48 -0800710 int breakCount = nComputeLineBreaks(b.mNativePtr, lineBreaks, lineBreaks.breaks,
711 lineBreaks.widths, lineBreaks.flags, lineBreaks.breaks.length);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800712
Anish Athalyec8f9e622014-07-21 15:26:34 -0700713 int[] breaks = lineBreaks.breaks;
714 float[] lineWidths = lineBreaks.widths;
Raph Levien26d443a2015-03-30 14:18:32 -0700715 int[] flags = lineBreaks.flags;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700716
Anish Athalyec8f9e622014-07-21 15:26:34 -0700717 // here is the offset of the starting character of the line we are currently measuring
718 int here = paraStart;
719
720 int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
721 int fmCacheIndex = 0;
722 int spanEndCacheIndex = 0;
723 int breakIndex = 0;
724 for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
725 // retrieve end of span
726 spanEnd = spanEndCache[spanEndCacheIndex++];
727
728 // retrieve cached metrics, order matches above
729 fm.top = fmCache[fmCacheIndex * 4 + 0];
730 fm.bottom = fmCache[fmCacheIndex * 4 + 1];
731 fm.ascent = fmCache[fmCacheIndex * 4 + 2];
732 fm.descent = fmCache[fmCacheIndex * 4 + 3];
733 fmCacheIndex++;
734
735 if (fm.top < fmTop) {
736 fmTop = fm.top;
737 }
738 if (fm.ascent < fmAscent) {
739 fmAscent = fm.ascent;
740 }
741 if (fm.descent > fmDescent) {
742 fmDescent = fm.descent;
743 }
744 if (fm.bottom > fmBottom) {
745 fmBottom = fm.bottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800746 }
747
Anish Athalyec8f9e622014-07-21 15:26:34 -0700748 // skip breaks ending before current span range
749 while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
750 breakIndex++;
751 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800752
Anish Athalyec8f9e622014-07-21 15:26:34 -0700753 while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
754 int endPos = paraStart + breaks[breakIndex];
755
Raph Levience4155a2015-03-11 11:02:33 -0700756 boolean moreChars = (endPos < bufEnd);
Raph Levien4c02e832014-12-12 11:17:01 -0800757
Anish Athalyec8f9e622014-07-21 15:26:34 -0700758 v = out(source, here, endPos,
759 fmAscent, fmDescent, fmTop, fmBottom,
760 v, spacingmult, spacingadd, chooseHt,chooseHtv, fm, flags[breakIndex],
761 needMultiply, chdirs, dir, easy, bufEnd, includepad, trackpad,
762 chs, widths, paraStart, ellipsize, ellipsizedWidth,
Raph Levien4c02e832014-12-12 11:17:01 -0800763 lineWidths[breakIndex], paint, moreChars);
Anish Athalyec8f9e622014-07-21 15:26:34 -0700764
765 if (endPos < spanEnd) {
766 // preserve metrics for current span
767 fmTop = fm.top;
768 fmBottom = fm.bottom;
769 fmAscent = fm.ascent;
770 fmDescent = fm.descent;
771 } else {
772 fmTop = fmBottom = fmAscent = fmDescent = 0;
773 }
774
775 here = endPos;
776 breakIndex++;
777
778 if (mLineCount >= mMaximumVisibleLineCount) {
779 return;
780 }
781 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800782 }
783
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800784 if (paraEnd == bufEnd)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800785 break;
786 }
787
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700788 if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE) &&
Fabrice Di Meglioad0b0512011-10-04 17:21:26 -0700789 mLineCount < mMaximumVisibleLineCount) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800790 // Log.e("text", "output last " + bufEnd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800791
Raph Levien70616ec2015-03-04 10:41:30 -0800792 measured.setPara(source, bufEnd, bufEnd, textDir, b);
Fabrice Di Meglioe6318892013-06-18 20:03:41 -0700793
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800794 paint.getFontMetricsInt(fm);
795
796 v = out(source,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800797 bufEnd, bufEnd, fm.ascent, fm.descent,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800798 fm.top, fm.bottom,
799 v,
800 spacingmult, spacingadd, null,
Raph Levien26d443a2015-03-30 14:18:32 -0700801 null, fm, 0,
Fabrice Di Meglioe6318892013-06-18 20:03:41 -0700802 needMultiply, measured.mLevels, measured.mDir, measured.mEasy, bufEnd,
Gilles Debunned300e752011-10-17 13:37:36 -0700803 includepad, trackpad, null,
804 null, bufStart, ellipsize,
805 ellipsizedWidth, 0, paint, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800806 }
807 }
808
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800809 private int out(CharSequence text, int start, int end,
810 int above, int below, int top, int bottom, int v,
811 float spacingmult, float spacingadd,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800812 LineHeightSpan[] chooseHt, int[] chooseHtv,
Raph Levien26d443a2015-03-30 14:18:32 -0700813 Paint.FontMetricsInt fm, int flags,
Gilles Debunned300e752011-10-17 13:37:36 -0700814 boolean needMultiply, byte[] chdirs, int dir,
815 boolean easy, int bufEnd, boolean includePad,
816 boolean trackPad, char[] chs,
817 float[] widths, int widthStart, TextUtils.TruncateAt ellipsize,
818 float ellipsisWidth, float textWidth,
819 TextPaint paint, boolean moreChars) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800820 int j = mLineCount;
821 int off = j * mColumns;
822 int want = off + mColumns + TOP;
823 int[] lines = mLines;
824
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800825 if (want >= lines.length) {
Adam Lesinski776abc22014-03-07 11:30:59 -0500826 Directions[] grow2 = ArrayUtils.newUnpaddedArray(
827 Directions.class, GrowingArrayUtils.growSize(want));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800828 System.arraycopy(mLineDirections, 0, grow2, 0,
829 mLineDirections.length);
830 mLineDirections = grow2;
Adam Lesinski776abc22014-03-07 11:30:59 -0500831
832 int[] grow = new int[grow2.length];
833 System.arraycopy(lines, 0, grow, 0, lines.length);
834 mLines = grow;
835 lines = grow;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800836 }
837
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800838 if (chooseHt != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800839 fm.ascent = above;
840 fm.descent = below;
841 fm.top = top;
842 fm.bottom = bottom;
843
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800844 for (int i = 0; i < chooseHt.length; i++) {
845 if (chooseHt[i] instanceof LineHeightSpan.WithDensity) {
846 ((LineHeightSpan.WithDensity) chooseHt[i]).
847 chooseHeight(text, start, end, chooseHtv[i], v, fm, paint);
Eric Fischera9f1dd02009-08-12 15:00:10 -0700848
849 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800850 chooseHt[i].chooseHeight(text, start, end, chooseHtv[i], v, fm);
Eric Fischera9f1dd02009-08-12 15:00:10 -0700851 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800852 }
853
854 above = fm.ascent;
855 below = fm.descent;
856 top = fm.top;
857 bottom = fm.bottom;
858 }
859
Raph Leviend97b0972014-04-24 12:51:35 -0700860 boolean firstLine = (j == 0);
861 boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);
862 boolean lastLine = currentLineIsTheLastVisibleOne || (end == bufEnd);
863
864 if (firstLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800865 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800866 mTopPadding = top - above;
867 }
868
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800869 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800870 above = top;
871 }
872 }
Raph Leviend97b0972014-04-24 12:51:35 -0700873
874 int extra;
875
876 if (lastLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800877 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800878 mBottomPadding = bottom - below;
879 }
880
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800881 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800882 below = bottom;
883 }
884 }
885
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800886
Raph Leviend97b0972014-04-24 12:51:35 -0700887 if (needMultiply && !lastLine) {
Doug Felt10657582010-02-22 11:19:01 -0800888 double ex = (below - above) * (spacingmult - 1) + spacingadd;
889 if (ex >= 0) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800890 extra = (int)(ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -0800891 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800892 extra = -(int)(-ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -0800893 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800894 } else {
895 extra = 0;
896 }
897
898 lines[off + START] = start;
899 lines[off + TOP] = v;
900 lines[off + DESCENT] = below + extra;
901
902 v += (below - above) + extra;
903 lines[off + mColumns + START] = end;
904 lines[off + mColumns + TOP] = v;
905
Raph Levien26d443a2015-03-30 14:18:32 -0700906 // TODO: could move TAB to share same column as HYPHEN, simplifying this code and gaining
907 // one bit for start field
908 lines[off + TAB] |= flags & TAB_MASK;
909 lines[off + HYPHEN] = flags;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800910
Doug Felt9f7a4442010-03-01 12:45:56 -0800911 lines[off + DIR] |= dir << DIR_SHIFT;
912 Directions linedirs = DIRS_ALL_LEFT_TO_RIGHT;
913 // easy means all chars < the first RTL, so no emoji, no nothing
Doug Felt4e0c5e52010-03-15 16:56:02 -0700914 // XXX a run with no text or all spaces is easy but might be an empty
Doug Felt9f7a4442010-03-01 12:45:56 -0800915 // RTL paragraph. Make sure easy is false if this is the case.
916 if (easy) {
917 mLineDirections[j] = linedirs;
918 } else {
Gilles Debunnef3fa0cd2011-02-03 14:17:05 -0800919 mLineDirections[j] = AndroidBidi.directions(dir, chdirs, start - widthStart, chs,
920 start - widthStart, end - start);
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800921 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800922
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -0700923 if (ellipsize != null) {
924 // If there is only one line, then do any type of ellipsis except when it is MARQUEE
925 // if there are multiple lines, just allow END ellipsis on the last line
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700926 boolean forceEllipsis = moreChars && (mLineCount + 1 == mMaximumVisibleLineCount);
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -0700927
Fabrice Di Meglio34a126e2012-02-29 18:43:14 -0800928 boolean doEllipsis =
929 (((mMaximumVisibleLineCount == 1 && moreChars) || (firstLine && !moreChars)) &&
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -0700930 ellipsize != TextUtils.TruncateAt.MARQUEE) ||
931 (!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
932 ellipsize == TextUtils.TruncateAt.END);
933 if (doEllipsis) {
934 calculateEllipsis(start, end, widths, widthStart,
935 ellipsisWidth, ellipsize, j,
936 textWidth, paint, forceEllipsis);
937 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800938 }
939
940 mLineCount++;
941 return v;
942 }
943
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800944 private void calculateEllipsis(int lineStart, int lineEnd,
945 float[] widths, int widthStart,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800946 float avail, TextUtils.TruncateAt where,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700947 int line, float textWidth, TextPaint paint,
948 boolean forceEllipsis) {
949 if (textWidth <= avail && !forceEllipsis) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800950 // Everything fits!
951 mLines[mColumns * line + ELLIPSIS_START] = 0;
952 mLines[mColumns * line + ELLIPSIS_COUNT] = 0;
953 return;
954 }
955
Fabrice Di Megliocb332642011-09-23 19:08:04 -0700956 float ellipsisWidth = paint.measureText(
Fabrice Di Meglio8d44fff2012-06-13 15:45:38 -0700957 (where == TextUtils.TruncateAt.END_SMALL) ?
Neil Fullerd29bdb22015-02-06 10:03:08 +0000958 TextUtils.ELLIPSIS_TWO_DOTS : TextUtils.ELLIPSIS_NORMAL, 0, 1);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700959 int ellipsisStart = 0;
960 int ellipsisCount = 0;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800961 int len = lineEnd - lineStart;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800962
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700963 // We only support start ellipsis on a single line
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800964 if (where == TextUtils.TruncateAt.START) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700965 if (mMaximumVisibleLineCount == 1) {
966 float sum = 0;
967 int i;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800968
Keisuke Kuroyanagied2eea12015-04-14 18:18:35 +0900969 for (i = len; i > 0; i--) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700970 float w = widths[i - 1 + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800971
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700972 if (w + sum + ellipsisWidth > avail) {
973 break;
974 }
975
976 sum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800977 }
978
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700979 ellipsisStart = 0;
980 ellipsisCount = i;
981 } else {
982 if (Log.isLoggable(TAG, Log.WARN)) {
983 Log.w(TAG, "Start Ellipsis only supported with one line");
984 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800985 }
Fabrice Di Megliocb332642011-09-23 19:08:04 -0700986 } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE ||
987 where == TextUtils.TruncateAt.END_SMALL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800988 float sum = 0;
989 int i;
990
991 for (i = 0; i < len; i++) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800992 float w = widths[i + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800993
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800994 if (w + sum + ellipsisWidth > avail) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800995 break;
996 }
997
998 sum += w;
999 }
1000
1001 ellipsisStart = i;
1002 ellipsisCount = len - i;
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -07001003 if (forceEllipsis && ellipsisCount == 0 && len > 0) {
1004 ellipsisStart = len - 1;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001005 ellipsisCount = 1;
1006 }
1007 } else {
1008 // where = TextUtils.TruncateAt.MIDDLE We only support middle ellipsis on a single line
1009 if (mMaximumVisibleLineCount == 1) {
1010 float lsum = 0, rsum = 0;
1011 int left = 0, right = len;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001012
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001013 float ravail = (avail - ellipsisWidth) / 2;
Raph Levien0e3c5e82014-12-04 13:26:07 -08001014 for (right = len; right > 0; right--) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001015 float w = widths[right - 1 + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001016
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001017 if (w + rsum > ravail) {
1018 break;
1019 }
1020
1021 rsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001022 }
1023
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001024 float lavail = avail - ellipsisWidth - rsum;
1025 for (left = 0; left < right; left++) {
1026 float w = widths[left + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001027
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001028 if (w + lsum > lavail) {
1029 break;
1030 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001031
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001032 lsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001033 }
1034
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001035 ellipsisStart = left;
1036 ellipsisCount = right - left;
1037 } else {
1038 if (Log.isLoggable(TAG, Log.WARN)) {
1039 Log.w(TAG, "Middle Ellipsis only supported with one line");
1040 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001041 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001042 }
1043
1044 mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
1045 mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
1046 }
1047
Doug Felte8e45f22010-03-29 14:58:40 -07001048 // Override the base class so we can directly access our members,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001049 // rather than relying on member functions.
1050 // The logic mirrors that of Layout.getLineForVertical
1051 // FIXME: It may be faster to do a linear search for layouts without many lines.
Gilles Debunne66111472010-11-19 11:04:37 -08001052 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001053 public int getLineForVertical(int vertical) {
1054 int high = mLineCount;
1055 int low = -1;
1056 int guess;
1057 int[] lines = mLines;
1058 while (high - low > 1) {
1059 guess = (high + low) >> 1;
1060 if (lines[mColumns * guess + TOP] > vertical){
1061 high = guess;
1062 } else {
1063 low = guess;
1064 }
1065 }
1066 if (low < 0) {
1067 return 0;
1068 } else {
1069 return low;
1070 }
1071 }
1072
Gilles Debunne66111472010-11-19 11:04:37 -08001073 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001074 public int getLineCount() {
1075 return mLineCount;
1076 }
1077
Gilles Debunne66111472010-11-19 11:04:37 -08001078 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001079 public int getLineTop(int line) {
Gilles Debunne0a4db3c2011-01-14 12:12:04 -08001080 int top = mLines[mColumns * line + TOP];
1081 if (mMaximumVisibleLineCount > 0 && line >= mMaximumVisibleLineCount &&
1082 line != mLineCount) {
1083 top += getBottomPadding();
1084 }
1085 return top;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001086 }
1087
Gilles Debunne66111472010-11-19 11:04:37 -08001088 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001089 public int getLineDescent(int line) {
Gilles Debunne0a4db3c2011-01-14 12:12:04 -08001090 int descent = mLines[mColumns * line + DESCENT];
Gilles Debunnef3fa0cd2011-02-03 14:17:05 -08001091 if (mMaximumVisibleLineCount > 0 && line >= mMaximumVisibleLineCount - 1 && // -1 intended
Gilles Debunne0a4db3c2011-01-14 12:12:04 -08001092 line != mLineCount) {
1093 descent += getBottomPadding();
1094 }
1095 return descent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001096 }
1097
Gilles Debunne66111472010-11-19 11:04:37 -08001098 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001099 public int getLineStart(int line) {
1100 return mLines[mColumns * line + START] & START_MASK;
1101 }
1102
Gilles Debunne66111472010-11-19 11:04:37 -08001103 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001104 public int getParagraphDirection(int line) {
1105 return mLines[mColumns * line + DIR] >> DIR_SHIFT;
1106 }
1107
Gilles Debunne66111472010-11-19 11:04:37 -08001108 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001109 public boolean getLineContainsTab(int line) {
1110 return (mLines[mColumns * line + TAB] & TAB_MASK) != 0;
1111 }
1112
Gilles Debunne66111472010-11-19 11:04:37 -08001113 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001114 public final Directions getLineDirections(int line) {
1115 return mLineDirections[line];
1116 }
1117
Gilles Debunne66111472010-11-19 11:04:37 -08001118 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001119 public int getTopPadding() {
1120 return mTopPadding;
1121 }
1122
Gilles Debunne66111472010-11-19 11:04:37 -08001123 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001124 public int getBottomPadding() {
1125 return mBottomPadding;
1126 }
1127
Raph Levien26d443a2015-03-30 14:18:32 -07001128 /**
1129 * @hide
1130 */
1131 @Override
1132 public int getHyphen(int line) {
1133 return mLines[mColumns * line + HYPHEN] & 0xff;
1134 }
1135
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001136 @Override
1137 public int getEllipsisCount(int line) {
1138 if (mColumns < COLUMNS_ELLIPSIZE) {
1139 return 0;
1140 }
1141
1142 return mLines[mColumns * line + ELLIPSIS_COUNT];
1143 }
1144
1145 @Override
1146 public int getEllipsisStart(int line) {
1147 if (mColumns < COLUMNS_ELLIPSIZE) {
1148 return 0;
1149 }
1150
1151 return mLines[mColumns * line + ELLIPSIS_START];
1152 }
1153
1154 @Override
1155 public int getEllipsizedWidth() {
1156 return mEllipsizedWidth;
1157 }
1158
Raph Levien70616ec2015-03-04 10:41:30 -08001159 private static native long nNewBuilder();
1160 private static native void nFreeBuilder(long nativePtr);
1161 private static native void nFinishBuilder(long nativePtr);
Raph Levien26d443a2015-03-30 14:18:32 -07001162
1163 /* package */ static native long nLoadHyphenator(String patternData);
1164
1165 private static native void nSetLocale(long nativePtr, String locale, long nativeHyphenator);
Raph Levien70616ec2015-03-04 10:41:30 -08001166
Raph Leviene319d5a2015-04-14 23:51:07 -07001167 private static native void nSetIndents(long nativePtr, int[] indents);
1168
Raph Levienc94f7422015-03-06 19:19:48 -08001169 // Set up paragraph text and settings; done as one big method to minimize jni crossings
1170 private static native void nSetupParagraph(long nativePtr, char[] text, int length,
1171 float firstWidth, int firstWidthLineCount, float restWidth,
Roozbeh Pournader95c7a132015-05-12 12:01:06 -07001172 int[] variableTabStops, int defaultTabStop, int breakStrategy, int hyphenationFrequency);
Raph Levien70616ec2015-03-04 10:41:30 -08001173
1174 private static native float nAddStyleRun(long nativePtr, long nativePaint,
1175 long nativeTypeface, int start, int end, boolean isRtl);
1176
1177 private static native void nAddMeasuredRun(long nativePtr,
1178 int start, int end, float[] widths);
1179
1180 private static native void nAddReplacementRun(long nativePtr, int start, int end, float width);
1181
1182 private static native void nGetWidths(long nativePtr, float[] widths);
1183
Anish Athalyec8f9e622014-07-21 15:26:34 -07001184 // populates LineBreaks and returns the number of breaks found
1185 //
1186 // the arrays inside the LineBreaks objects are passed in as well
1187 // to reduce the number of JNI calls in the common case where the
1188 // arrays do not have to be resized
Raph Levienc94f7422015-03-06 19:19:48 -08001189 private static native int nComputeLineBreaks(long nativePtr, LineBreaks recycle,
Raph Levien26d443a2015-03-30 14:18:32 -07001190 int[] recycleBreaks, float[] recycleWidths, int[] recycleFlags, int recycleLength);
Anish Athalye88b5b0b2014-06-24 14:39:43 -07001191
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001192 private int mLineCount;
1193 private int mTopPadding, mBottomPadding;
1194 private int mColumns;
1195 private int mEllipsizedWidth;
1196
Raph Levien26d443a2015-03-30 14:18:32 -07001197 private static final int COLUMNS_NORMAL = 4;
1198 private static final int COLUMNS_ELLIPSIZE = 6;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001199 private static final int START = 0;
1200 private static final int DIR = START;
1201 private static final int TAB = START;
1202 private static final int TOP = 1;
1203 private static final int DESCENT = 2;
Raph Levien26d443a2015-03-30 14:18:32 -07001204 private static final int HYPHEN = 3;
1205 private static final int ELLIPSIS_START = 4;
1206 private static final int ELLIPSIS_COUNT = 5;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001207
1208 private int[] mLines;
1209 private Directions[] mLineDirections;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001210 private int mMaximumVisibleLineCount = Integer.MAX_VALUE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001211
1212 private static final int START_MASK = 0x1FFFFFFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001213 private static final int DIR_SHIFT = 30;
1214 private static final int TAB_MASK = 0x20000000;
1215
Doug Feltc982f602010-05-25 11:51:40 -07001216 private static final int TAB_INCREMENT = 20; // same as Layout, but that's private
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001217
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001218 private static final char CHAR_NEW_LINE = '\n';
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001219
1220 private static final double EXTRA_ROUNDING = 0.5;
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001221
Anish Athalyec8f9e622014-07-21 15:26:34 -07001222 // This is used to return three arrays from a single JNI call when
1223 // performing line breaking
Deepanshu Gupta70539192014-10-15 15:57:40 -07001224 /*package*/ static class LineBreaks {
Anish Athalyec8f9e622014-07-21 15:26:34 -07001225 private static final int INITIAL_SIZE = 16;
1226 public int[] breaks = new int[INITIAL_SIZE];
1227 public float[] widths = new float[INITIAL_SIZE];
Raph Levien26d443a2015-03-30 14:18:32 -07001228 public int[] flags = new int[INITIAL_SIZE]; // hasTabOrEmoji
Anish Athalyec8f9e622014-07-21 15:26:34 -07001229 // breaks, widths, and flags should all have the same length
1230 }
1231
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001232}