blob: 59c7c6d34eb8369ba7348eb92fe34437d601783a [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;
Raph Leviend3ab6922015-03-02 14:30:53 -080095
96 b.mMeasuredText = MeasuredText.obtain();
97 return b;
98 }
99
Raph Levien39b4db72015-03-25 13:18:20 -0700100 private static void recycle(Builder b) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800101 b.mPaint = null;
102 b.mText = null;
103 MeasuredText.recycle(b.mMeasuredText);
Raph Levien3bd60c72015-05-06 14:26:35 -0700104 b.mMeasuredText = null;
105 nFinishBuilder(b.mNativePtr);
Raph Levien39b4db72015-03-25 13:18:20 -0700106 sPool.release(b);
Raph Leviend3ab6922015-03-02 14:30:53 -0800107 }
108
109 // release any expensive state
110 /* package */ void finish() {
Raph Levien4c1f12e2015-03-02 16:29:23 -0800111 nFinishBuilder(mNativePtr);
Raph Leviend3ab6922015-03-02 14:30:53 -0800112 mMeasuredText.finish();
113 }
114
115 public Builder setText(CharSequence source) {
116 return setText(source, 0, source.length());
117 }
118
Raph Levien531c30c2015-04-30 16:29:59 -0700119 /**
120 * Set the text. Only useful when re-using the builder, which is done for
121 * the internal implementation of {@link DynamicLayout} but not as part
122 * of normal {@link StaticLayout} usage.
123 *
124 * @param source The text to be laid out, optionally with spans
125 * @param start The index of the start of the text
126 * @param end The index + 1 of the end of the text
127 * @return this builder, useful for chaining
128 *
129 * @hide
130 */
Raph Leviend3ab6922015-03-02 14:30:53 -0800131 public Builder setText(CharSequence source, int start, int end) {
132 mText = source;
133 mStart = start;
134 mEnd = end;
135 return this;
136 }
137
Raph Levien531c30c2015-04-30 16:29:59 -0700138 /**
139 * Set the paint. Internal for reuse cases only.
140 *
141 * @param paint The base paint used for layout
142 * @return this builder, useful for chaining
143 *
144 * @hide
145 */
Raph Leviend3ab6922015-03-02 14:30:53 -0800146 public Builder setPaint(TextPaint paint) {
147 mPaint = paint;
148 return this;
149 }
150
Raph Levien531c30c2015-04-30 16:29:59 -0700151 /**
152 * Set the width. Internal for reuse cases only.
153 *
154 * @param width The width in pixels
155 * @return this builder, useful for chaining
156 *
157 * @hide
158 */
Raph Leviend3ab6922015-03-02 14:30:53 -0800159 public Builder setWidth(int width) {
160 mWidth = width;
161 if (mEllipsize == null) {
162 mEllipsizedWidth = width;
163 }
164 return this;
165 }
166
Raph Levien531c30c2015-04-30 16:29:59 -0700167 /**
168 * Set the alignment. The default is {@link Layout.Alignment#ALIGN_NORMAL}.
169 *
170 * @param alignment Alignment for the resulting {@link StaticLayout}
171 * @return this builder, useful for chaining
172 */
Raph Levien39b4db72015-03-25 13:18:20 -0700173 public Builder setAlignment(Alignment alignment) {
174 mAlignment = alignment;
175 return this;
176 }
177
Raph Levien531c30c2015-04-30 16:29:59 -0700178 /**
179 * Set the text direction heuristic. The text direction heuristic is used to
180 * resolve text direction based per-paragraph based on the input text. The default is
181 * {@link TextDirectionHeuristics#FIRSTSTRONG_LTR}.
182 *
183 * @param textDir text direction heuristic for resolving BiDi behavior.
184 * @return this builder, useful for chaining
185 */
Raph Leviend3ab6922015-03-02 14:30:53 -0800186 public Builder setTextDir(TextDirectionHeuristic textDir) {
187 mTextDir = textDir;
188 return this;
189 }
190
Raph Levien531c30c2015-04-30 16:29:59 -0700191 /**
192 * Set line spacing parameters. The default is 0.0 for {@code spacingAdd}
193 * and 1.0 for {@code spacingMult}.
194 *
195 * @param spacingAdd line spacing add
196 * @param spacingMult line spacing multiplier
197 * @return this builder, useful for chaining
198 * @see android.widget.TextView#setLineSpacing
199 */
200 public Builder setLineSpacing(float spacingAdd, float spacingMult) {
201 mSpacingAdd = spacingAdd;
Raph Leviend3ab6922015-03-02 14:30:53 -0800202 mSpacingMult = spacingMult;
203 return this;
204 }
205
Raph Levien531c30c2015-04-30 16:29:59 -0700206 /**
207 * Set whether to include extra space beyond font ascent and descent (which is
208 * needed to avoid clipping in some languages, such as Arabic and Kannada). The
209 * default is {@code true}.
210 *
211 * @param includePad whether to include padding
212 * @return this builder, useful for chaining
213 * @see android.widget.TextView#setIncludeFontPadding
214 */
Raph Leviend3ab6922015-03-02 14:30:53 -0800215 public Builder setIncludePad(boolean includePad) {
216 mIncludePad = includePad;
217 return this;
218 }
219
Raph Levien531c30c2015-04-30 16:29:59 -0700220 /**
221 * Set the width as used for ellipsizing purposes, if it differs from the
222 * normal layout width. The default is the {@code width}
223 * passed to {@link #obtain}.
224 *
225 * @param ellipsizedWidth width used for ellipsizing, in pixels
226 * @return this builder, useful for chaining
227 * @see android.widget.TextView#setEllipsize
228 */
Raph Leviend3ab6922015-03-02 14:30:53 -0800229 public Builder setEllipsizedWidth(int ellipsizedWidth) {
230 mEllipsizedWidth = ellipsizedWidth;
231 return this;
232 }
233
Raph Levien531c30c2015-04-30 16:29:59 -0700234 /**
235 * Set ellipsizing on the layout. Causes words that are longer than the view
236 * is wide, or exceeding the number of lines (see #setMaxLines) in the case
237 * of {@link android.text.TextUtils.TruncateAt#END} or
238 * {@link android.text.TextUtils.TruncateAt#MARQUEE}, to be ellipsized instead
239 * of broken. The default is
240 * {@code null}, indicating no ellipsis is to be applied.
241 *
242 * @param ellipsize type of ellipsis behavior
243 * @return this builder, useful for chaining
244 * @see android.widget.TextView#setEllipsize
245 */
246 public Builder setEllipsize(@Nullable TextUtils.TruncateAt ellipsize) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800247 mEllipsize = ellipsize;
248 return this;
249 }
250
Raph Levien531c30c2015-04-30 16:29:59 -0700251 /**
252 * Set maximum number of lines. This is particularly useful in the case of
253 * ellipsizing, where it changes the layout of the last line. The default is
254 * unlimited.
255 *
256 * @param maxLines maximum number of lines in the layout
257 * @return this builder, useful for chaining
258 * @see android.widget.TextView#setMaxLines
259 */
Raph Leviend3ab6922015-03-02 14:30:53 -0800260 public Builder setMaxLines(int maxLines) {
261 mMaxLines = maxLines;
262 return this;
263 }
264
Raph Levien531c30c2015-04-30 16:29:59 -0700265 /**
266 * Set break strategy, useful for selecting high quality or balanced paragraph
267 * layout options. The default is {@link Layout#BREAK_STRATEGY_SIMPLE}.
268 *
269 * @param breakStrategy break strategy for paragraph layout
270 * @return this builder, useful for chaining
271 * @see android.widget.TextView#setBreakStrategy
272 */
Raph Levien39b4db72015-03-25 13:18:20 -0700273 public Builder setBreakStrategy(@BreakStrategy int breakStrategy) {
274 mBreakStrategy = breakStrategy;
275 return this;
276 }
277
Raph Levien531c30c2015-04-30 16:29:59 -0700278 /**
279 * Set indents. Arguments are arrays holding an indent amount, one per line, measured in
280 * pixels. For lines past the last element in the array, the last element repeats.
281 *
282 * @param leftIndents array of indent values for left margin, in pixels
283 * @param rightIndents array of indent values for right margin, in pixels
284 * @return this builder, useful for chaining
285 * @see android.widget.TextView#setIndents
286 */
Raph Leviene319d5a2015-04-14 23:51:07 -0700287 public Builder setIndents(int[] leftIndents, int[] rightIndents) {
288 int leftLen = leftIndents == null ? 0 : leftIndents.length;
289 int rightLen = rightIndents == null ? 0 : rightIndents.length;
290 int[] indents = new int[Math.max(leftLen, rightLen)];
291 for (int i = 0; i < indents.length; i++) {
292 int leftMargin = i < leftLen ? leftIndents[i] : 0;
293 int rightMargin = i < rightLen ? rightIndents[i] : 0;
294 indents[i] = leftMargin + rightMargin;
295 }
296 nSetIndents(mNativePtr, indents);
297 return this;
298 }
299
Raph Levien70616ec2015-03-04 10:41:30 -0800300 /**
301 * Measurement and break iteration is done in native code. The protocol for using
302 * the native code is as follows.
303 *
Raph Levien26d443a2015-03-30 14:18:32 -0700304 * For each paragraph, do a nSetupParagraph, which sets paragraph text, line width, tab
305 * stops, break strategy (and possibly other parameters in the future).
Raph Levienc94f7422015-03-06 19:19:48 -0800306 *
307 * Then, for each run within the paragraph:
Raph Levien70616ec2015-03-04 10:41:30 -0800308 * - setLocale (this must be done at least for the first run, optional afterwards)
309 * - one of the following, depending on the type of run:
310 * + addStyleRun (a text run, to be measured in native code)
311 * + addMeasuredRun (a run already measured in Java, passed into native code)
312 * + addReplacementRun (a replacement run, width is given)
313 *
314 * After measurement, nGetWidths() is valid if the widths are needed (eg for ellipsis).
315 * Run nComputeLineBreaks() to obtain line breaks for the paragraph.
316 *
317 * After all paragraphs, call finish() to release expensive buffers.
318 */
319
320 private void setLocale(Locale locale) {
Raph Levien4c1f12e2015-03-02 16:29:23 -0800321 if (!locale.equals(mLocale)) {
Raph Levien26d443a2015-03-30 14:18:32 -0700322 nSetLocale(mNativePtr, locale.toLanguageTag(), Hyphenator.get(locale));
Raph Levien4c1f12e2015-03-02 16:29:23 -0800323 mLocale = locale;
324 }
325 }
326
Raph Levien70616ec2015-03-04 10:41:30 -0800327 /* package */ float addStyleRun(TextPaint paint, int start, int end, boolean isRtl) {
328 return nAddStyleRun(mNativePtr, paint.getNativeInstance(), paint.mNativeTypeface,
329 start, end, isRtl);
330 }
331
332 /* package */ void addMeasuredRun(int start, int end, float[] widths) {
333 nAddMeasuredRun(mNativePtr, start, end, widths);
334 }
335
336 /* package */ void addReplacementRun(int start, int end, float width) {
337 nAddReplacementRun(mNativePtr, start, end, width);
338 }
339
Raph Levien531c30c2015-04-30 16:29:59 -0700340 /**
341 * Build the {@link StaticLayout} after options have been set.
342 *
343 * <p>Note: the builder object must not be reused in any way after calling this
344 * method. Setting parameters after calling this method, or calling it a second
345 * time on the same builder object, will likely lead to unexpected results.
346 *
347 * @return the newly constructed {@link StaticLayout} object
348 */
Raph Leviend3ab6922015-03-02 14:30:53 -0800349 public StaticLayout build() {
Raph Levien39b4db72015-03-25 13:18:20 -0700350 StaticLayout result = new StaticLayout(this);
351 Builder.recycle(this);
Raph Leviend3ab6922015-03-02 14:30:53 -0800352 return result;
353 }
354
Raph Levien4c1f12e2015-03-02 16:29:23 -0800355 @Override
356 protected void finalize() throws Throwable {
357 try {
358 nFreeBuilder(mNativePtr);
359 } finally {
360 super.finalize();
361 }
362 }
363
364 /* package */ long mNativePtr;
365
Raph Leviend3ab6922015-03-02 14:30:53 -0800366 CharSequence mText;
367 int mStart;
368 int mEnd;
369 TextPaint mPaint;
370 int mWidth;
Raph Levien39b4db72015-03-25 13:18:20 -0700371 Alignment mAlignment;
Raph Leviend3ab6922015-03-02 14:30:53 -0800372 TextDirectionHeuristic mTextDir;
373 float mSpacingMult;
374 float mSpacingAdd;
375 boolean mIncludePad;
376 int mEllipsizedWidth;
377 TextUtils.TruncateAt mEllipsize;
378 int mMaxLines;
Raph Levien39b4db72015-03-25 13:18:20 -0700379 int mBreakStrategy;
Raph Leviend3ab6922015-03-02 14:30:53 -0800380
381 Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
382
383 // This will go away and be subsumed by native builder code
384 MeasuredText mMeasuredText;
385
Raph Levien4c1f12e2015-03-02 16:29:23 -0800386 Locale mLocale;
387
Raph Levien39b4db72015-03-25 13:18:20 -0700388 private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<Builder>(3);
Raph Leviend3ab6922015-03-02 14:30:53 -0800389 }
390
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800391 public StaticLayout(CharSequence source, TextPaint paint,
392 int width,
393 Alignment align, float spacingmult, float spacingadd,
394 boolean includepad) {
395 this(source, 0, source.length(), paint, width, align,
396 spacingmult, spacingadd, includepad);
397 }
398
Doug Feltcb3791202011-07-07 11:57:48 -0700399 /**
400 * @hide
401 */
402 public StaticLayout(CharSequence source, TextPaint paint,
403 int width, Alignment align, TextDirectionHeuristic textDir,
404 float spacingmult, float spacingadd,
405 boolean includepad) {
406 this(source, 0, source.length(), paint, width, align, textDir,
407 spacingmult, spacingadd, includepad);
408 }
409
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800410 public StaticLayout(CharSequence source, int bufstart, int bufend,
411 TextPaint paint, int outerwidth,
412 Alignment align,
413 float spacingmult, float spacingadd,
414 boolean includepad) {
415 this(source, bufstart, bufend, paint, outerwidth, align,
416 spacingmult, spacingadd, includepad, null, 0);
417 }
418
Doug Feltcb3791202011-07-07 11:57:48 -0700419 /**
420 * @hide
421 */
422 public StaticLayout(CharSequence source, int bufstart, int bufend,
423 TextPaint paint, int outerwidth,
424 Alignment align, TextDirectionHeuristic textDir,
425 float spacingmult, float spacingadd,
426 boolean includepad) {
427 this(source, bufstart, bufend, paint, outerwidth, align, textDir,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700428 spacingmult, spacingadd, includepad, null, 0, Integer.MAX_VALUE);
Doug Feltcb3791202011-07-07 11:57:48 -0700429}
430
431 public StaticLayout(CharSequence source, int bufstart, int bufend,
432 TextPaint paint, int outerwidth,
433 Alignment align,
434 float spacingmult, float spacingadd,
435 boolean includepad,
436 TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
437 this(source, bufstart, bufend, paint, outerwidth, align,
438 TextDirectionHeuristics.FIRSTSTRONG_LTR,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700439 spacingmult, spacingadd, includepad, ellipsize, ellipsizedWidth, Integer.MAX_VALUE);
Doug Feltcb3791202011-07-07 11:57:48 -0700440 }
441
442 /**
443 * @hide
444 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800445 public StaticLayout(CharSequence source, int bufstart, int bufend,
446 TextPaint paint, int outerwidth,
Doug Feltcb3791202011-07-07 11:57:48 -0700447 Alignment align, TextDirectionHeuristic textDir,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800448 float spacingmult, float spacingadd,
449 boolean includepad,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700450 TextUtils.TruncateAt ellipsize, int ellipsizedWidth, int maxLines) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800451 super((ellipsize == null)
Doug Felt4e0c5e52010-03-15 16:56:02 -0700452 ? source
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800453 : (source instanceof Spanned)
454 ? new SpannedEllipsizer(source)
455 : new Ellipsizer(source),
Doug Feltcb3791202011-07-07 11:57:48 -0700456 paint, outerwidth, align, textDir, spacingmult, spacingadd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800457
Raph Levienebd66ca2015-04-30 15:27:57 -0700458 Builder b = Builder.obtain(source, bufstart, bufend, paint, outerwidth)
Raph Levien39b4db72015-03-25 13:18:20 -0700459 .setAlignment(align)
Raph Leviend3ab6922015-03-02 14:30:53 -0800460 .setTextDir(textDir)
Raph Levien531c30c2015-04-30 16:29:59 -0700461 .setLineSpacing(spacingadd, spacingmult)
Raph Leviend3ab6922015-03-02 14:30:53 -0800462 .setIncludePad(includepad)
463 .setEllipsizedWidth(ellipsizedWidth)
464 .setEllipsize(ellipsize)
465 .setMaxLines(maxLines);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800466 /*
467 * This is annoying, but we can't refer to the layout until
468 * superclass construction is finished, and the superclass
469 * constructor wants the reference to the display text.
Doug Felt4e0c5e52010-03-15 16:56:02 -0700470 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800471 * This will break if the superclass constructor ever actually
472 * cares about the content instead of just holding the reference.
473 */
474 if (ellipsize != null) {
475 Ellipsizer e = (Ellipsizer) getText();
476
477 e.mLayout = this;
478 e.mWidth = ellipsizedWidth;
479 e.mMethod = ellipsize;
480 mEllipsizedWidth = ellipsizedWidth;
481
482 mColumns = COLUMNS_ELLIPSIZE;
483 } else {
484 mColumns = COLUMNS_NORMAL;
485 mEllipsizedWidth = outerwidth;
486 }
487
Adam Lesinski776abc22014-03-07 11:30:59 -0500488 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2 * mColumns);
489 mLines = new int[mLineDirections.length];
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700490 mMaximumVisibleLineCount = maxLines;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800491
Raph Levien70616ec2015-03-04 10:41:30 -0800492 generate(b, b.mIncludePad, b.mIncludePad);
Doug Felte8e45f22010-03-29 14:58:40 -0700493
Raph Leviend3ab6922015-03-02 14:30:53 -0800494 Builder.recycle(b);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800495 }
496
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700497 /* package */ StaticLayout(CharSequence text) {
498 super(text, null, 0, null, 0, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800499
500 mColumns = COLUMNS_ELLIPSIZE;
Adam Lesinski776abc22014-03-07 11:30:59 -0500501 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2 * mColumns);
502 mLines = new int[mLineDirections.length];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800503 }
504
Raph Levien39b4db72015-03-25 13:18:20 -0700505 private StaticLayout(Builder b) {
506 super((b.mEllipsize == null)
507 ? b.mText
508 : (b.mText instanceof Spanned)
509 ? new SpannedEllipsizer(b.mText)
510 : new Ellipsizer(b.mText),
511 b.mPaint, b.mWidth, b.mAlignment, b.mSpacingMult, b.mSpacingAdd);
512
513 if (b.mEllipsize != null) {
514 Ellipsizer e = (Ellipsizer) getText();
515
516 e.mLayout = this;
517 e.mWidth = b.mEllipsizedWidth;
518 e.mMethod = b.mEllipsize;
519 mEllipsizedWidth = b.mEllipsizedWidth;
520
521 mColumns = COLUMNS_ELLIPSIZE;
522 } else {
523 mColumns = COLUMNS_NORMAL;
524 mEllipsizedWidth = b.mWidth;
525 }
526
527 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2 * mColumns);
528 mLines = new int[mLineDirections.length];
529 mMaximumVisibleLineCount = b.mMaxLines;
530
531 generate(b, b.mIncludePad, b.mIncludePad);
532 }
533
Raph Leviend3ab6922015-03-02 14:30:53 -0800534 /* package */ void generate(Builder b, boolean includepad, boolean trackpad) {
535 CharSequence source = b.mText;
536 int bufStart = b.mStart;
537 int bufEnd = b.mEnd;
538 TextPaint paint = b.mPaint;
539 int outerWidth = b.mWidth;
540 TextDirectionHeuristic textDir = b.mTextDir;
541 float spacingmult = b.mSpacingMult;
542 float spacingadd = b.mSpacingAdd;
543 float ellipsizedWidth = b.mEllipsizedWidth;
544 TextUtils.TruncateAt ellipsize = b.mEllipsize;
Raph Levien4c1f12e2015-03-02 16:29:23 -0800545 LineBreaks lineBreaks = new LineBreaks(); // TODO: move to builder to avoid allocation costs
Anish Athalyec8f9e622014-07-21 15:26:34 -0700546 // store span end locations
547 int[] spanEndCache = new int[4];
548 // store fontMetrics per span range
549 // must be a multiple of 4 (and > 0) (store top, bottom, ascent, and descent per range)
550 int[] fmCache = new int[4 * 4];
Raph Levien4c1f12e2015-03-02 16:29:23 -0800551 b.setLocale(paint.getTextLocale()); // TODO: also respect LocaleSpan within the text
Anish Athalye88b5b0b2014-06-24 14:39:43 -0700552
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800553 mLineCount = 0;
554
555 int v = 0;
556 boolean needMultiply = (spacingmult != 1 || spacingadd != 0);
557
Raph Leviend3ab6922015-03-02 14:30:53 -0800558 Paint.FontMetricsInt fm = b.mFontMetricsInt;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800559 int[] chooseHtv = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800560
Raph Leviend3ab6922015-03-02 14:30:53 -0800561 MeasuredText measured = b.mMeasuredText;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800562
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800563 Spanned spanned = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800564 if (source instanceof Spanned)
565 spanned = (Spanned) source;
566
Doug Felte8e45f22010-03-29 14:58:40 -0700567 int paraEnd;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800568 for (int paraStart = bufStart; paraStart <= bufEnd; paraStart = paraEnd) {
569 paraEnd = TextUtils.indexOf(source, CHAR_NEW_LINE, paraStart, bufEnd);
Doug Felte8e45f22010-03-29 14:58:40 -0700570 if (paraEnd < 0)
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800571 paraEnd = bufEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800572 else
Doug Felte8e45f22010-03-29 14:58:40 -0700573 paraEnd++;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800574
Anish Athalyec8f9e622014-07-21 15:26:34 -0700575 int firstWidthLineCount = 1;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800576 int firstWidth = outerWidth;
577 int restWidth = outerWidth;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800578
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800579 LineHeightSpan[] chooseHt = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800580
581 if (spanned != null) {
Eric Fischer74d31ef2010-08-05 15:29:36 -0700582 LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
Doug Felte8e45f22010-03-29 14:58:40 -0700583 LeadingMarginSpan.class);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800584 for (int i = 0; i < sp.length; i++) {
Mark Wagner7b5676e2009-10-16 11:44:23 -0700585 LeadingMarginSpan lms = sp[i];
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800586 firstWidth -= sp[i].getLeadingMargin(true);
587 restWidth -= sp[i].getLeadingMargin(false);
Doug Feltcb3791202011-07-07 11:57:48 -0700588
Doug Feltc982f602010-05-25 11:51:40 -0700589 // LeadingMarginSpan2 is odd. The count affects all
Anish Athalyeab08c6d2014-08-08 12:09:58 -0700590 // leading margin spans, not just this particular one
Doug Feltc982f602010-05-25 11:51:40 -0700591 if (lms instanceof LeadingMarginSpan2) {
592 LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700593 firstWidthLineCount = Math.max(firstWidthLineCount,
594 lms2.getLeadingMarginLineCount());
Mark Wagner7b5676e2009-10-16 11:44:23 -0700595 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800596 }
597
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800598 chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800599
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800600 if (chooseHt.length != 0) {
601 if (chooseHtv == null ||
602 chooseHtv.length < chooseHt.length) {
Adam Lesinski776abc22014-03-07 11:30:59 -0500603 chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800604 }
605
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800606 for (int i = 0; i < chooseHt.length; i++) {
607 int o = spanned.getSpanStart(chooseHt[i]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800608
Doug Felte8e45f22010-03-29 14:58:40 -0700609 if (o < paraStart) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800610 // starts in this layout, before the
611 // current paragraph
612
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800613 chooseHtv[i] = getLineTop(getLineForOffset(o));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800614 } else {
615 // starts in this paragraph
616
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800617 chooseHtv[i] = v;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800618 }
619 }
620 }
621 }
622
Raph Levien70616ec2015-03-04 10:41:30 -0800623 measured.setPara(source, paraStart, paraEnd, textDir, b);
Doug Felte8e45f22010-03-29 14:58:40 -0700624 char[] chs = measured.mChars;
625 float[] widths = measured.mWidths;
626 byte[] chdirs = measured.mLevels;
627 int dir = measured.mDir;
628 boolean easy = measured.mEasy;
Raph Levienc94f7422015-03-06 19:19:48 -0800629
630 // tab stop locations
631 int[] variableTabStops = null;
632 if (spanned != null) {
633 TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
634 paraEnd, TabStopSpan.class);
635 if (spans.length > 0) {
636 int[] stops = new int[spans.length];
637 for (int i = 0; i < spans.length; i++) {
638 stops[i] = spans[i].getTabStop();
639 }
640 Arrays.sort(stops, 0, stops.length);
641 variableTabStops = stops;
642 }
643 }
644
Raph Levienc94f7422015-03-06 19:19:48 -0800645 nSetupParagraph(b.mNativePtr, chs, paraEnd - paraStart,
646 firstWidth, firstWidthLineCount, restWidth,
Raph Levien39b4db72015-03-25 13:18:20 -0700647 variableTabStops, TAB_INCREMENT, b.mBreakStrategy);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800648
Anish Athalyec8f9e622014-07-21 15:26:34 -0700649 // measurement has to be done before performing line breaking
650 // but we don't want to recompute fontmetrics or span ranges the
651 // second time, so we cache those and then use those stored values
652 int fmCacheCount = 0;
653 int spanEndCacheCount = 0;
Gilles Debunnecd943a72012-06-07 17:54:47 -0700654 for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
Anish Athalyec8f9e622014-07-21 15:26:34 -0700655 if (fmCacheCount * 4 >= fmCache.length) {
656 int[] grow = new int[fmCacheCount * 4 * 2];
657 System.arraycopy(fmCache, 0, grow, 0, fmCacheCount * 4);
658 fmCache = grow;
659 }
660
661 if (spanEndCacheCount >= spanEndCache.length) {
662 int[] grow = new int[spanEndCacheCount * 2];
663 System.arraycopy(spanEndCache, 0, grow, 0, spanEndCacheCount);
664 spanEndCache = grow;
665 }
Doug Felte8e45f22010-03-29 14:58:40 -0700666
Gilles Debunnecd943a72012-06-07 17:54:47 -0700667 if (spanned == null) {
668 spanEnd = paraEnd;
Doug Felt23241882010-06-02 14:41:06 -0700669 int spanLen = spanEnd - spanStart;
Gilles Debunnecd943a72012-06-07 17:54:47 -0700670 measured.addStyleRun(paint, spanLen, fm);
671 } else {
672 spanEnd = spanned.nextSpanTransition(spanStart, paraEnd,
673 MetricAffectingSpan.class);
674 int spanLen = spanEnd - spanStart;
675 MetricAffectingSpan[] spans =
Doug Felt23241882010-06-02 14:41:06 -0700676 spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class);
Gilles Debunnecd943a72012-06-07 17:54:47 -0700677 spans = TextUtils.removeEmptySpans(spans, spanned, MetricAffectingSpan.class);
678 measured.addStyleRun(paint, spans, spanLen, fm);
Doug Felt23241882010-06-02 14:41:06 -0700679 }
680
Anish Athalyec8f9e622014-07-21 15:26:34 -0700681 // the order of storage here (top, bottom, ascent, descent) has to match the code below
682 // where these values are retrieved
683 fmCache[fmCacheCount * 4 + 0] = fm.top;
684 fmCache[fmCacheCount * 4 + 1] = fm.bottom;
685 fmCache[fmCacheCount * 4 + 2] = fm.ascent;
686 fmCache[fmCacheCount * 4 + 3] = fm.descent;
687 fmCacheCount++;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800688
Anish Athalyec8f9e622014-07-21 15:26:34 -0700689 spanEndCache[spanEndCacheCount] = spanEnd;
690 spanEndCacheCount++;
691 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800692
Raph Levien70616ec2015-03-04 10:41:30 -0800693 nGetWidths(b.mNativePtr, widths);
Raph Levienc94f7422015-03-06 19:19:48 -0800694 int breakCount = nComputeLineBreaks(b.mNativePtr, lineBreaks, lineBreaks.breaks,
695 lineBreaks.widths, lineBreaks.flags, lineBreaks.breaks.length);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800696
Anish Athalyec8f9e622014-07-21 15:26:34 -0700697 int[] breaks = lineBreaks.breaks;
698 float[] lineWidths = lineBreaks.widths;
Raph Levien26d443a2015-03-30 14:18:32 -0700699 int[] flags = lineBreaks.flags;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700700
Anish Athalyec8f9e622014-07-21 15:26:34 -0700701 // here is the offset of the starting character of the line we are currently measuring
702 int here = paraStart;
703
704 int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
705 int fmCacheIndex = 0;
706 int spanEndCacheIndex = 0;
707 int breakIndex = 0;
708 for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
709 // retrieve end of span
710 spanEnd = spanEndCache[spanEndCacheIndex++];
711
712 // retrieve cached metrics, order matches above
713 fm.top = fmCache[fmCacheIndex * 4 + 0];
714 fm.bottom = fmCache[fmCacheIndex * 4 + 1];
715 fm.ascent = fmCache[fmCacheIndex * 4 + 2];
716 fm.descent = fmCache[fmCacheIndex * 4 + 3];
717 fmCacheIndex++;
718
719 if (fm.top < fmTop) {
720 fmTop = fm.top;
721 }
722 if (fm.ascent < fmAscent) {
723 fmAscent = fm.ascent;
724 }
725 if (fm.descent > fmDescent) {
726 fmDescent = fm.descent;
727 }
728 if (fm.bottom > fmBottom) {
729 fmBottom = fm.bottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800730 }
731
Anish Athalyec8f9e622014-07-21 15:26:34 -0700732 // skip breaks ending before current span range
733 while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
734 breakIndex++;
735 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800736
Anish Athalyec8f9e622014-07-21 15:26:34 -0700737 while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
738 int endPos = paraStart + breaks[breakIndex];
739
Raph Levience4155a2015-03-11 11:02:33 -0700740 boolean moreChars = (endPos < bufEnd);
Raph Levien4c02e832014-12-12 11:17:01 -0800741
Anish Athalyec8f9e622014-07-21 15:26:34 -0700742 v = out(source, here, endPos,
743 fmAscent, fmDescent, fmTop, fmBottom,
744 v, spacingmult, spacingadd, chooseHt,chooseHtv, fm, flags[breakIndex],
745 needMultiply, chdirs, dir, easy, bufEnd, includepad, trackpad,
746 chs, widths, paraStart, ellipsize, ellipsizedWidth,
Raph Levien4c02e832014-12-12 11:17:01 -0800747 lineWidths[breakIndex], paint, moreChars);
Anish Athalyec8f9e622014-07-21 15:26:34 -0700748
749 if (endPos < spanEnd) {
750 // preserve metrics for current span
751 fmTop = fm.top;
752 fmBottom = fm.bottom;
753 fmAscent = fm.ascent;
754 fmDescent = fm.descent;
755 } else {
756 fmTop = fmBottom = fmAscent = fmDescent = 0;
757 }
758
759 here = endPos;
760 breakIndex++;
761
762 if (mLineCount >= mMaximumVisibleLineCount) {
763 return;
764 }
765 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800766 }
767
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800768 if (paraEnd == bufEnd)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800769 break;
770 }
771
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700772 if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE) &&
Fabrice Di Meglioad0b0512011-10-04 17:21:26 -0700773 mLineCount < mMaximumVisibleLineCount) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800774 // Log.e("text", "output last " + bufEnd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800775
Raph Levien70616ec2015-03-04 10:41:30 -0800776 measured.setPara(source, bufEnd, bufEnd, textDir, b);
Fabrice Di Meglioe6318892013-06-18 20:03:41 -0700777
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800778 paint.getFontMetricsInt(fm);
779
780 v = out(source,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800781 bufEnd, bufEnd, fm.ascent, fm.descent,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800782 fm.top, fm.bottom,
783 v,
784 spacingmult, spacingadd, null,
Raph Levien26d443a2015-03-30 14:18:32 -0700785 null, fm, 0,
Fabrice Di Meglioe6318892013-06-18 20:03:41 -0700786 needMultiply, measured.mLevels, measured.mDir, measured.mEasy, bufEnd,
Gilles Debunned300e752011-10-17 13:37:36 -0700787 includepad, trackpad, null,
788 null, bufStart, ellipsize,
789 ellipsizedWidth, 0, paint, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800790 }
791 }
792
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800793 private int out(CharSequence text, int start, int end,
794 int above, int below, int top, int bottom, int v,
795 float spacingmult, float spacingadd,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800796 LineHeightSpan[] chooseHt, int[] chooseHtv,
Raph Levien26d443a2015-03-30 14:18:32 -0700797 Paint.FontMetricsInt fm, int flags,
Gilles Debunned300e752011-10-17 13:37:36 -0700798 boolean needMultiply, byte[] chdirs, int dir,
799 boolean easy, int bufEnd, boolean includePad,
800 boolean trackPad, char[] chs,
801 float[] widths, int widthStart, TextUtils.TruncateAt ellipsize,
802 float ellipsisWidth, float textWidth,
803 TextPaint paint, boolean moreChars) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800804 int j = mLineCount;
805 int off = j * mColumns;
806 int want = off + mColumns + TOP;
807 int[] lines = mLines;
808
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800809 if (want >= lines.length) {
Adam Lesinski776abc22014-03-07 11:30:59 -0500810 Directions[] grow2 = ArrayUtils.newUnpaddedArray(
811 Directions.class, GrowingArrayUtils.growSize(want));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800812 System.arraycopy(mLineDirections, 0, grow2, 0,
813 mLineDirections.length);
814 mLineDirections = grow2;
Adam Lesinski776abc22014-03-07 11:30:59 -0500815
816 int[] grow = new int[grow2.length];
817 System.arraycopy(lines, 0, grow, 0, lines.length);
818 mLines = grow;
819 lines = grow;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800820 }
821
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800822 if (chooseHt != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800823 fm.ascent = above;
824 fm.descent = below;
825 fm.top = top;
826 fm.bottom = bottom;
827
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800828 for (int i = 0; i < chooseHt.length; i++) {
829 if (chooseHt[i] instanceof LineHeightSpan.WithDensity) {
830 ((LineHeightSpan.WithDensity) chooseHt[i]).
831 chooseHeight(text, start, end, chooseHtv[i], v, fm, paint);
Eric Fischera9f1dd02009-08-12 15:00:10 -0700832
833 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800834 chooseHt[i].chooseHeight(text, start, end, chooseHtv[i], v, fm);
Eric Fischera9f1dd02009-08-12 15:00:10 -0700835 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800836 }
837
838 above = fm.ascent;
839 below = fm.descent;
840 top = fm.top;
841 bottom = fm.bottom;
842 }
843
Raph Leviend97b0972014-04-24 12:51:35 -0700844 boolean firstLine = (j == 0);
845 boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);
846 boolean lastLine = currentLineIsTheLastVisibleOne || (end == bufEnd);
847
848 if (firstLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800849 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800850 mTopPadding = top - above;
851 }
852
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800853 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800854 above = top;
855 }
856 }
Raph Leviend97b0972014-04-24 12:51:35 -0700857
858 int extra;
859
860 if (lastLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800861 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800862 mBottomPadding = bottom - below;
863 }
864
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800865 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800866 below = bottom;
867 }
868 }
869
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800870
Raph Leviend97b0972014-04-24 12:51:35 -0700871 if (needMultiply && !lastLine) {
Doug Felt10657582010-02-22 11:19:01 -0800872 double ex = (below - above) * (spacingmult - 1) + spacingadd;
873 if (ex >= 0) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800874 extra = (int)(ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -0800875 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800876 extra = -(int)(-ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -0800877 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800878 } else {
879 extra = 0;
880 }
881
882 lines[off + START] = start;
883 lines[off + TOP] = v;
884 lines[off + DESCENT] = below + extra;
885
886 v += (below - above) + extra;
887 lines[off + mColumns + START] = end;
888 lines[off + mColumns + TOP] = v;
889
Raph Levien26d443a2015-03-30 14:18:32 -0700890 // TODO: could move TAB to share same column as HYPHEN, simplifying this code and gaining
891 // one bit for start field
892 lines[off + TAB] |= flags & TAB_MASK;
893 lines[off + HYPHEN] = flags;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800894
Doug Felt9f7a4442010-03-01 12:45:56 -0800895 lines[off + DIR] |= dir << DIR_SHIFT;
896 Directions linedirs = DIRS_ALL_LEFT_TO_RIGHT;
897 // easy means all chars < the first RTL, so no emoji, no nothing
Doug Felt4e0c5e52010-03-15 16:56:02 -0700898 // XXX a run with no text or all spaces is easy but might be an empty
Doug Felt9f7a4442010-03-01 12:45:56 -0800899 // RTL paragraph. Make sure easy is false if this is the case.
900 if (easy) {
901 mLineDirections[j] = linedirs;
902 } else {
Gilles Debunnef3fa0cd2011-02-03 14:17:05 -0800903 mLineDirections[j] = AndroidBidi.directions(dir, chdirs, start - widthStart, chs,
904 start - widthStart, end - start);
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800905 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800906
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -0700907 if (ellipsize != null) {
908 // If there is only one line, then do any type of ellipsis except when it is MARQUEE
909 // if there are multiple lines, just allow END ellipsis on the last line
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700910 boolean forceEllipsis = moreChars && (mLineCount + 1 == mMaximumVisibleLineCount);
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -0700911
Fabrice Di Meglio34a126e2012-02-29 18:43:14 -0800912 boolean doEllipsis =
913 (((mMaximumVisibleLineCount == 1 && moreChars) || (firstLine && !moreChars)) &&
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -0700914 ellipsize != TextUtils.TruncateAt.MARQUEE) ||
915 (!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
916 ellipsize == TextUtils.TruncateAt.END);
917 if (doEllipsis) {
918 calculateEllipsis(start, end, widths, widthStart,
919 ellipsisWidth, ellipsize, j,
920 textWidth, paint, forceEllipsis);
921 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800922 }
923
924 mLineCount++;
925 return v;
926 }
927
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800928 private void calculateEllipsis(int lineStart, int lineEnd,
929 float[] widths, int widthStart,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800930 float avail, TextUtils.TruncateAt where,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700931 int line, float textWidth, TextPaint paint,
932 boolean forceEllipsis) {
933 if (textWidth <= avail && !forceEllipsis) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800934 // Everything fits!
935 mLines[mColumns * line + ELLIPSIS_START] = 0;
936 mLines[mColumns * line + ELLIPSIS_COUNT] = 0;
937 return;
938 }
939
Fabrice Di Megliocb332642011-09-23 19:08:04 -0700940 float ellipsisWidth = paint.measureText(
Fabrice Di Meglio8d44fff2012-06-13 15:45:38 -0700941 (where == TextUtils.TruncateAt.END_SMALL) ?
Neil Fullerd29bdb22015-02-06 10:03:08 +0000942 TextUtils.ELLIPSIS_TWO_DOTS : TextUtils.ELLIPSIS_NORMAL, 0, 1);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700943 int ellipsisStart = 0;
944 int ellipsisCount = 0;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800945 int len = lineEnd - lineStart;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800946
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700947 // We only support start ellipsis on a single line
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800948 if (where == TextUtils.TruncateAt.START) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700949 if (mMaximumVisibleLineCount == 1) {
950 float sum = 0;
951 int i;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800952
Keisuke Kuroyanagied2eea12015-04-14 18:18:35 +0900953 for (i = len; i > 0; i--) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700954 float w = widths[i - 1 + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800955
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700956 if (w + sum + ellipsisWidth > avail) {
957 break;
958 }
959
960 sum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800961 }
962
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700963 ellipsisStart = 0;
964 ellipsisCount = i;
965 } else {
966 if (Log.isLoggable(TAG, Log.WARN)) {
967 Log.w(TAG, "Start Ellipsis only supported with one line");
968 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800969 }
Fabrice Di Megliocb332642011-09-23 19:08:04 -0700970 } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE ||
971 where == TextUtils.TruncateAt.END_SMALL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800972 float sum = 0;
973 int i;
974
975 for (i = 0; i < len; i++) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800976 float w = widths[i + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800977
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800978 if (w + sum + ellipsisWidth > avail) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800979 break;
980 }
981
982 sum += w;
983 }
984
985 ellipsisStart = i;
986 ellipsisCount = len - i;
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -0700987 if (forceEllipsis && ellipsisCount == 0 && len > 0) {
988 ellipsisStart = len - 1;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700989 ellipsisCount = 1;
990 }
991 } else {
992 // where = TextUtils.TruncateAt.MIDDLE We only support middle ellipsis on a single line
993 if (mMaximumVisibleLineCount == 1) {
994 float lsum = 0, rsum = 0;
995 int left = 0, right = len;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800996
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700997 float ravail = (avail - ellipsisWidth) / 2;
Raph Levien0e3c5e82014-12-04 13:26:07 -0800998 for (right = len; right > 0; right--) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700999 float w = widths[right - 1 + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001000
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001001 if (w + rsum > ravail) {
1002 break;
1003 }
1004
1005 rsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001006 }
1007
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001008 float lavail = avail - ellipsisWidth - rsum;
1009 for (left = 0; left < right; left++) {
1010 float w = widths[left + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001011
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001012 if (w + lsum > lavail) {
1013 break;
1014 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001015
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001016 lsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001017 }
1018
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001019 ellipsisStart = left;
1020 ellipsisCount = right - left;
1021 } else {
1022 if (Log.isLoggable(TAG, Log.WARN)) {
1023 Log.w(TAG, "Middle Ellipsis only supported with one line");
1024 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001025 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001026 }
1027
1028 mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
1029 mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
1030 }
1031
Doug Felte8e45f22010-03-29 14:58:40 -07001032 // Override the base class so we can directly access our members,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001033 // rather than relying on member functions.
1034 // The logic mirrors that of Layout.getLineForVertical
1035 // FIXME: It may be faster to do a linear search for layouts without many lines.
Gilles Debunne66111472010-11-19 11:04:37 -08001036 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001037 public int getLineForVertical(int vertical) {
1038 int high = mLineCount;
1039 int low = -1;
1040 int guess;
1041 int[] lines = mLines;
1042 while (high - low > 1) {
1043 guess = (high + low) >> 1;
1044 if (lines[mColumns * guess + TOP] > vertical){
1045 high = guess;
1046 } else {
1047 low = guess;
1048 }
1049 }
1050 if (low < 0) {
1051 return 0;
1052 } else {
1053 return low;
1054 }
1055 }
1056
Gilles Debunne66111472010-11-19 11:04:37 -08001057 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001058 public int getLineCount() {
1059 return mLineCount;
1060 }
1061
Gilles Debunne66111472010-11-19 11:04:37 -08001062 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001063 public int getLineTop(int line) {
Gilles Debunne0a4db3c2011-01-14 12:12:04 -08001064 int top = mLines[mColumns * line + TOP];
1065 if (mMaximumVisibleLineCount > 0 && line >= mMaximumVisibleLineCount &&
1066 line != mLineCount) {
1067 top += getBottomPadding();
1068 }
1069 return top;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001070 }
1071
Gilles Debunne66111472010-11-19 11:04:37 -08001072 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001073 public int getLineDescent(int line) {
Gilles Debunne0a4db3c2011-01-14 12:12:04 -08001074 int descent = mLines[mColumns * line + DESCENT];
Gilles Debunnef3fa0cd2011-02-03 14:17:05 -08001075 if (mMaximumVisibleLineCount > 0 && line >= mMaximumVisibleLineCount - 1 && // -1 intended
Gilles Debunne0a4db3c2011-01-14 12:12:04 -08001076 line != mLineCount) {
1077 descent += getBottomPadding();
1078 }
1079 return descent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001080 }
1081
Gilles Debunne66111472010-11-19 11:04:37 -08001082 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001083 public int getLineStart(int line) {
1084 return mLines[mColumns * line + START] & START_MASK;
1085 }
1086
Gilles Debunne66111472010-11-19 11:04:37 -08001087 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001088 public int getParagraphDirection(int line) {
1089 return mLines[mColumns * line + DIR] >> DIR_SHIFT;
1090 }
1091
Gilles Debunne66111472010-11-19 11:04:37 -08001092 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001093 public boolean getLineContainsTab(int line) {
1094 return (mLines[mColumns * line + TAB] & TAB_MASK) != 0;
1095 }
1096
Gilles Debunne66111472010-11-19 11:04:37 -08001097 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001098 public final Directions getLineDirections(int line) {
1099 return mLineDirections[line];
1100 }
1101
Gilles Debunne66111472010-11-19 11:04:37 -08001102 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001103 public int getTopPadding() {
1104 return mTopPadding;
1105 }
1106
Gilles Debunne66111472010-11-19 11:04:37 -08001107 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001108 public int getBottomPadding() {
1109 return mBottomPadding;
1110 }
1111
Raph Levien26d443a2015-03-30 14:18:32 -07001112 /**
1113 * @hide
1114 */
1115 @Override
1116 public int getHyphen(int line) {
1117 return mLines[mColumns * line + HYPHEN] & 0xff;
1118 }
1119
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001120 @Override
1121 public int getEllipsisCount(int line) {
1122 if (mColumns < COLUMNS_ELLIPSIZE) {
1123 return 0;
1124 }
1125
1126 return mLines[mColumns * line + ELLIPSIS_COUNT];
1127 }
1128
1129 @Override
1130 public int getEllipsisStart(int line) {
1131 if (mColumns < COLUMNS_ELLIPSIZE) {
1132 return 0;
1133 }
1134
1135 return mLines[mColumns * line + ELLIPSIS_START];
1136 }
1137
1138 @Override
1139 public int getEllipsizedWidth() {
1140 return mEllipsizedWidth;
1141 }
1142
Raph Levien70616ec2015-03-04 10:41:30 -08001143 private static native long nNewBuilder();
1144 private static native void nFreeBuilder(long nativePtr);
1145 private static native void nFinishBuilder(long nativePtr);
Raph Levien26d443a2015-03-30 14:18:32 -07001146
1147 /* package */ static native long nLoadHyphenator(String patternData);
1148
1149 private static native void nSetLocale(long nativePtr, String locale, long nativeHyphenator);
Raph Levien70616ec2015-03-04 10:41:30 -08001150
Raph Leviene319d5a2015-04-14 23:51:07 -07001151 private static native void nSetIndents(long nativePtr, int[] indents);
1152
Raph Levienc94f7422015-03-06 19:19:48 -08001153 // Set up paragraph text and settings; done as one big method to minimize jni crossings
1154 private static native void nSetupParagraph(long nativePtr, char[] text, int length,
1155 float firstWidth, int firstWidthLineCount, float restWidth,
1156 int[] variableTabStops, int defaultTabStop, int breakStrategy);
Raph Levien70616ec2015-03-04 10:41:30 -08001157
1158 private static native float nAddStyleRun(long nativePtr, long nativePaint,
1159 long nativeTypeface, int start, int end, boolean isRtl);
1160
1161 private static native void nAddMeasuredRun(long nativePtr,
1162 int start, int end, float[] widths);
1163
1164 private static native void nAddReplacementRun(long nativePtr, int start, int end, float width);
1165
1166 private static native void nGetWidths(long nativePtr, float[] widths);
1167
Anish Athalyec8f9e622014-07-21 15:26:34 -07001168 // populates LineBreaks and returns the number of breaks found
1169 //
1170 // the arrays inside the LineBreaks objects are passed in as well
1171 // to reduce the number of JNI calls in the common case where the
1172 // arrays do not have to be resized
Raph Levienc94f7422015-03-06 19:19:48 -08001173 private static native int nComputeLineBreaks(long nativePtr, LineBreaks recycle,
Raph Levien26d443a2015-03-30 14:18:32 -07001174 int[] recycleBreaks, float[] recycleWidths, int[] recycleFlags, int recycleLength);
Anish Athalye88b5b0b2014-06-24 14:39:43 -07001175
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001176 private int mLineCount;
1177 private int mTopPadding, mBottomPadding;
1178 private int mColumns;
1179 private int mEllipsizedWidth;
1180
Raph Levien26d443a2015-03-30 14:18:32 -07001181 private static final int COLUMNS_NORMAL = 4;
1182 private static final int COLUMNS_ELLIPSIZE = 6;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001183 private static final int START = 0;
1184 private static final int DIR = START;
1185 private static final int TAB = START;
1186 private static final int TOP = 1;
1187 private static final int DESCENT = 2;
Raph Levien26d443a2015-03-30 14:18:32 -07001188 private static final int HYPHEN = 3;
1189 private static final int ELLIPSIS_START = 4;
1190 private static final int ELLIPSIS_COUNT = 5;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001191
1192 private int[] mLines;
1193 private Directions[] mLineDirections;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001194 private int mMaximumVisibleLineCount = Integer.MAX_VALUE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001195
1196 private static final int START_MASK = 0x1FFFFFFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001197 private static final int DIR_SHIFT = 30;
1198 private static final int TAB_MASK = 0x20000000;
1199
Doug Feltc982f602010-05-25 11:51:40 -07001200 private static final int TAB_INCREMENT = 20; // same as Layout, but that's private
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001201
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001202 private static final char CHAR_NEW_LINE = '\n';
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001203
1204 private static final double EXTRA_ROUNDING = 0.5;
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001205
Anish Athalyec8f9e622014-07-21 15:26:34 -07001206 // This is used to return three arrays from a single JNI call when
1207 // performing line breaking
Deepanshu Gupta70539192014-10-15 15:57:40 -07001208 /*package*/ static class LineBreaks {
Anish Athalyec8f9e622014-07-21 15:26:34 -07001209 private static final int INITIAL_SIZE = 16;
1210 public int[] breaks = new int[INITIAL_SIZE];
1211 public float[] widths = new float[INITIAL_SIZE];
Raph Levien26d443a2015-03-30 14:18:32 -07001212 public int[] flags = new int[INITIAL_SIZE]; // hasTabOrEmoji
Anish Athalyec8f9e622014-07-21 15:26:34 -07001213 // breaks, widths, and flags should all have the same length
1214 }
1215
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001216}