blob: 94ce57ac7cb9f29efb14c8598aa711640f4dfb8f [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.text;
18
Raph Levien531c30c2015-04-30 16:29:59 -070019import android.annotation.Nullable;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080020import android.graphics.Paint;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080021import android.text.style.LeadingMarginSpan;
Gilles Debunne66111472010-11-19 11:04:37 -080022import android.text.style.LeadingMarginSpan.LeadingMarginSpan2;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080023import android.text.style.LineHeightSpan;
24import android.text.style.MetricAffectingSpan;
Doug Feltc982f602010-05-25 11:51:40 -070025import android.text.style.TabStopSpan;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -070026import android.util.Log;
Raph Levien39b4db72015-03-25 13:18:20 -070027import android.util.Pools.SynchronizedPool;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080028
Doug Feltcb3791202011-07-07 11:57:48 -070029import com.android.internal.util.ArrayUtils;
Adam Lesinski776abc22014-03-07 11:30:59 -050030import com.android.internal.util.GrowingArrayUtils;
Doug Feltcb3791202011-07-07 11:57:48 -070031
Raph Levien091dba22015-08-31 16:21:20 -070032import java.nio.ByteBuffer;
Anish Athalyec8f9e622014-07-21 15:26:34 -070033import java.util.Arrays;
Raph Levien4c1f12e2015-03-02 16:29:23 -080034import java.util.Locale;
Anish Athalyec8f9e622014-07-21 15:26:34 -070035
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080036/**
37 * StaticLayout is a Layout for text that will not be edited after it
38 * is laid out. Use {@link DynamicLayout} for text that may change.
39 * <p>This is used by widgets to control text layout. You should not need
40 * to use this class directly unless you are implementing your own widget
41 * or custom display object, or would be tempted to call
Doug Felt4e0c5e52010-03-15 16:56:02 -070042 * {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int,
43 * float, float, android.graphics.Paint)
44 * Canvas.drawText()} directly.</p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080045 */
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -080046public class StaticLayout extends Layout {
47
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -070048 static final String TAG = "StaticLayout";
49
Raph Leviend3ab6922015-03-02 14:30:53 -080050 /**
Raph Levien531c30c2015-04-30 16:29:59 -070051 * Builder for static layouts. The builder is a newer pattern for constructing
52 * StaticLayout objects and should be preferred over the constructors,
53 * particularly to access newer features. To build a static layout, first
54 * call {@link #obtain} with the required arguments (text, paint, and width),
55 * then call setters for optional parameters, and finally {@link #build}
56 * to build the StaticLayout object. Parameters not explicitly set will get
57 * default values.
Raph Leviend3ab6922015-03-02 14:30:53 -080058 */
59 public final static class Builder {
Raph Levien4c1f12e2015-03-02 16:29:23 -080060 private Builder() {
61 mNativePtr = nNewBuilder();
62 }
63
Raph Levien531c30c2015-04-30 16:29:59 -070064 /**
65 * Obtain a builder for constructing StaticLayout objects
66 *
67 * @param source The text to be laid out, optionally with spans
68 * @param start The index of the start of the text
69 * @param end The index + 1 of the end of the text
70 * @param paint The base paint used for layout
71 * @param width The width in pixels
72 * @return a builder object used for constructing the StaticLayout
73 */
Raph Levienebd66ca2015-04-30 15:27:57 -070074 public static Builder obtain(CharSequence source, int start, int end, TextPaint paint,
75 int width) {
Raph Levien39b4db72015-03-25 13:18:20 -070076 Builder b = sPool.acquire();
Raph Leviend3ab6922015-03-02 14:30:53 -080077 if (b == null) {
78 b = new Builder();
79 }
80
81 // set default initial values
Raph Levien39b4db72015-03-25 13:18:20 -070082 b.mText = source;
83 b.mStart = start;
84 b.mEnd = end;
Raph Levienebd66ca2015-04-30 15:27:57 -070085 b.mPaint = paint;
Raph Levien39b4db72015-03-25 13:18:20 -070086 b.mWidth = width;
87 b.mAlignment = Alignment.ALIGN_NORMAL;
Raph Leviend3ab6922015-03-02 14:30:53 -080088 b.mTextDir = TextDirectionHeuristics.FIRSTSTRONG_LTR;
89 b.mSpacingMult = 1.0f;
90 b.mSpacingAdd = 0.0f;
91 b.mIncludePad = true;
Raph Levien39b4db72015-03-25 13:18:20 -070092 b.mEllipsizedWidth = width;
Raph Leviend3ab6922015-03-02 14:30:53 -080093 b.mEllipsize = null;
94 b.mMaxLines = Integer.MAX_VALUE;
Raph Levien3bd60c72015-05-06 14:26:35 -070095 b.mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
Roozbeh Pournader95c7a132015-05-12 12:01:06 -070096 b.mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
Raph Leviend3ab6922015-03-02 14:30:53 -080097
98 b.mMeasuredText = MeasuredText.obtain();
99 return b;
100 }
101
Raph Levien39b4db72015-03-25 13:18:20 -0700102 private static void recycle(Builder b) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800103 b.mPaint = null;
104 b.mText = null;
105 MeasuredText.recycle(b.mMeasuredText);
Raph Levien3bd60c72015-05-06 14:26:35 -0700106 b.mMeasuredText = null;
Raph Levien2ea52902015-07-01 14:39:31 -0700107 b.mLeftIndents = null;
108 b.mRightIndents = null;
Raph Levien3bd60c72015-05-06 14:26:35 -0700109 nFinishBuilder(b.mNativePtr);
Raph Levien39b4db72015-03-25 13:18:20 -0700110 sPool.release(b);
Raph Leviend3ab6922015-03-02 14:30:53 -0800111 }
112
113 // release any expensive state
114 /* package */ void finish() {
Raph Levien4c1f12e2015-03-02 16:29:23 -0800115 nFinishBuilder(mNativePtr);
Raph Levien22ba7862015-07-29 12:34:13 -0700116 mText = null;
117 mPaint = null;
118 mLeftIndents = null;
119 mRightIndents = null;
Raph Leviend3ab6922015-03-02 14:30:53 -0800120 mMeasuredText.finish();
121 }
122
123 public Builder setText(CharSequence source) {
124 return setText(source, 0, source.length());
125 }
126
Raph Levien531c30c2015-04-30 16:29:59 -0700127 /**
128 * Set the text. Only useful when re-using the builder, which is done for
129 * the internal implementation of {@link DynamicLayout} but not as part
130 * of normal {@link StaticLayout} usage.
131 *
132 * @param source The text to be laid out, optionally with spans
133 * @param start The index of the start of the text
134 * @param end The index + 1 of the end of the text
135 * @return this builder, useful for chaining
136 *
137 * @hide
138 */
Raph Leviend3ab6922015-03-02 14:30:53 -0800139 public Builder setText(CharSequence source, int start, int end) {
140 mText = source;
141 mStart = start;
142 mEnd = end;
143 return this;
144 }
145
Raph Levien531c30c2015-04-30 16:29:59 -0700146 /**
147 * Set the paint. Internal for reuse cases only.
148 *
149 * @param paint The base paint used for layout
150 * @return this builder, useful for chaining
151 *
152 * @hide
153 */
Raph Leviend3ab6922015-03-02 14:30:53 -0800154 public Builder setPaint(TextPaint paint) {
155 mPaint = paint;
156 return this;
157 }
158
Raph Levien531c30c2015-04-30 16:29:59 -0700159 /**
160 * Set the width. Internal for reuse cases only.
161 *
162 * @param width The width in pixels
163 * @return this builder, useful for chaining
164 *
165 * @hide
166 */
Raph Leviend3ab6922015-03-02 14:30:53 -0800167 public Builder setWidth(int width) {
168 mWidth = width;
169 if (mEllipsize == null) {
170 mEllipsizedWidth = width;
171 }
172 return this;
173 }
174
Raph Levien531c30c2015-04-30 16:29:59 -0700175 /**
176 * Set the alignment. The default is {@link Layout.Alignment#ALIGN_NORMAL}.
177 *
178 * @param alignment Alignment for the resulting {@link StaticLayout}
179 * @return this builder, useful for chaining
180 */
Raph Levien39b4db72015-03-25 13:18:20 -0700181 public Builder setAlignment(Alignment alignment) {
182 mAlignment = alignment;
183 return this;
184 }
185
Raph Levien531c30c2015-04-30 16:29:59 -0700186 /**
187 * Set the text direction heuristic. The text direction heuristic is used to
188 * resolve text direction based per-paragraph based on the input text. The default is
189 * {@link TextDirectionHeuristics#FIRSTSTRONG_LTR}.
190 *
191 * @param textDir text direction heuristic for resolving BiDi behavior.
192 * @return this builder, useful for chaining
193 */
Raph Leviena6a08282015-06-03 13:20:45 -0700194 public Builder setTextDirection(TextDirectionHeuristic textDir) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800195 mTextDir = textDir;
196 return this;
197 }
198
Raph Levien531c30c2015-04-30 16:29:59 -0700199 /**
200 * Set line spacing parameters. The default is 0.0 for {@code spacingAdd}
201 * and 1.0 for {@code spacingMult}.
202 *
203 * @param spacingAdd line spacing add
204 * @param spacingMult line spacing multiplier
205 * @return this builder, useful for chaining
206 * @see android.widget.TextView#setLineSpacing
207 */
208 public Builder setLineSpacing(float spacingAdd, float spacingMult) {
209 mSpacingAdd = spacingAdd;
Raph Leviend3ab6922015-03-02 14:30:53 -0800210 mSpacingMult = spacingMult;
211 return this;
212 }
213
Raph Levien531c30c2015-04-30 16:29:59 -0700214 /**
215 * Set whether to include extra space beyond font ascent and descent (which is
216 * needed to avoid clipping in some languages, such as Arabic and Kannada). The
217 * default is {@code true}.
218 *
219 * @param includePad whether to include padding
220 * @return this builder, useful for chaining
221 * @see android.widget.TextView#setIncludeFontPadding
222 */
Raph Leviend3ab6922015-03-02 14:30:53 -0800223 public Builder setIncludePad(boolean includePad) {
224 mIncludePad = includePad;
225 return this;
226 }
227
Raph Levien531c30c2015-04-30 16:29:59 -0700228 /**
229 * Set the width as used for ellipsizing purposes, if it differs from the
230 * normal layout width. The default is the {@code width}
231 * passed to {@link #obtain}.
232 *
233 * @param ellipsizedWidth width used for ellipsizing, in pixels
234 * @return this builder, useful for chaining
235 * @see android.widget.TextView#setEllipsize
236 */
Raph Leviend3ab6922015-03-02 14:30:53 -0800237 public Builder setEllipsizedWidth(int ellipsizedWidth) {
238 mEllipsizedWidth = ellipsizedWidth;
239 return this;
240 }
241
Raph Levien531c30c2015-04-30 16:29:59 -0700242 /**
243 * Set ellipsizing on the layout. Causes words that are longer than the view
244 * is wide, or exceeding the number of lines (see #setMaxLines) in the case
245 * of {@link android.text.TextUtils.TruncateAt#END} or
246 * {@link android.text.TextUtils.TruncateAt#MARQUEE}, to be ellipsized instead
247 * of broken. The default is
248 * {@code null}, indicating no ellipsis is to be applied.
249 *
250 * @param ellipsize type of ellipsis behavior
251 * @return this builder, useful for chaining
252 * @see android.widget.TextView#setEllipsize
253 */
254 public Builder setEllipsize(@Nullable TextUtils.TruncateAt ellipsize) {
Raph Leviend3ab6922015-03-02 14:30:53 -0800255 mEllipsize = ellipsize;
256 return this;
257 }
258
Raph Levien531c30c2015-04-30 16:29:59 -0700259 /**
260 * Set maximum number of lines. This is particularly useful in the case of
261 * ellipsizing, where it changes the layout of the last line. The default is
262 * unlimited.
263 *
264 * @param maxLines maximum number of lines in the layout
265 * @return this builder, useful for chaining
266 * @see android.widget.TextView#setMaxLines
267 */
Raph Leviend3ab6922015-03-02 14:30:53 -0800268 public Builder setMaxLines(int maxLines) {
269 mMaxLines = maxLines;
270 return this;
271 }
272
Raph Levien531c30c2015-04-30 16:29:59 -0700273 /**
274 * Set break strategy, useful for selecting high quality or balanced paragraph
275 * layout options. The default is {@link Layout#BREAK_STRATEGY_SIMPLE}.
276 *
277 * @param breakStrategy break strategy for paragraph layout
278 * @return this builder, useful for chaining
279 * @see android.widget.TextView#setBreakStrategy
280 */
Raph Levien39b4db72015-03-25 13:18:20 -0700281 public Builder setBreakStrategy(@BreakStrategy int breakStrategy) {
282 mBreakStrategy = breakStrategy;
283 return this;
284 }
285
Raph Levien531c30c2015-04-30 16:29:59 -0700286 /**
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700287 * Set hyphenation frequency, to control the amount of automatic hyphenation used. The
288 * default is {@link Layout#HYPHENATION_FREQUENCY_NONE}.
289 *
290 * @param hyphenationFrequency hyphenation frequency for the paragraph
291 * @return this builder, useful for chaining
292 * @see android.widget.TextView#setHyphenationFrequency
293 */
294 public Builder setHyphenationFrequency(@HyphenationFrequency int hyphenationFrequency) {
295 mHyphenationFrequency = hyphenationFrequency;
296 return this;
297 }
298
299 /**
Raph Levien531c30c2015-04-30 16:29:59 -0700300 * Set indents. Arguments are arrays holding an indent amount, one per line, measured in
301 * pixels. For lines past the last element in the array, the last element repeats.
302 *
303 * @param leftIndents array of indent values for left margin, in pixels
304 * @param rightIndents array of indent values for right margin, in pixels
305 * @return this builder, useful for chaining
Raph Levien531c30c2015-04-30 16:29:59 -0700306 */
Raph Leviene319d5a2015-04-14 23:51:07 -0700307 public Builder setIndents(int[] leftIndents, int[] rightIndents) {
Raph Levien2ea52902015-07-01 14:39:31 -0700308 mLeftIndents = leftIndents;
309 mRightIndents = rightIndents;
Raph Leviene319d5a2015-04-14 23:51:07 -0700310 int leftLen = leftIndents == null ? 0 : leftIndents.length;
311 int rightLen = rightIndents == null ? 0 : rightIndents.length;
312 int[] indents = new int[Math.max(leftLen, rightLen)];
313 for (int i = 0; i < indents.length; i++) {
314 int leftMargin = i < leftLen ? leftIndents[i] : 0;
315 int rightMargin = i < rightLen ? rightIndents[i] : 0;
316 indents[i] = leftMargin + rightMargin;
317 }
318 nSetIndents(mNativePtr, indents);
319 return this;
320 }
321
Raph Levien70616ec2015-03-04 10:41:30 -0800322 /**
323 * Measurement and break iteration is done in native code. The protocol for using
324 * the native code is as follows.
325 *
Raph Levien26d443a2015-03-30 14:18:32 -0700326 * For each paragraph, do a nSetupParagraph, which sets paragraph text, line width, tab
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700327 * stops, break strategy, and hyphenation frequency (and possibly other parameters in the
328 * future).
Raph Levienc94f7422015-03-06 19:19:48 -0800329 *
330 * Then, for each run within the paragraph:
Raph Levien70616ec2015-03-04 10:41:30 -0800331 * - setLocale (this must be done at least for the first run, optional afterwards)
332 * - one of the following, depending on the type of run:
333 * + addStyleRun (a text run, to be measured in native code)
334 * + addMeasuredRun (a run already measured in Java, passed into native code)
335 * + addReplacementRun (a replacement run, width is given)
336 *
337 * After measurement, nGetWidths() is valid if the widths are needed (eg for ellipsis).
338 * Run nComputeLineBreaks() to obtain line breaks for the paragraph.
339 *
340 * After all paragraphs, call finish() to release expensive buffers.
341 */
342
343 private void setLocale(Locale locale) {
Raph Levien4c1f12e2015-03-02 16:29:23 -0800344 if (!locale.equals(mLocale)) {
Roozbeh Pournadere7eac6f2015-08-07 15:13:30 -0700345 nSetLocale(mNativePtr, locale.toLanguageTag(),
346 Hyphenator.get(locale).getNativePtr());
Raph Levien4c1f12e2015-03-02 16:29:23 -0800347 mLocale = locale;
348 }
349 }
350
Raph Levien70616ec2015-03-04 10:41:30 -0800351 /* package */ float addStyleRun(TextPaint paint, int start, int end, boolean isRtl) {
352 return nAddStyleRun(mNativePtr, paint.getNativeInstance(), paint.mNativeTypeface,
353 start, end, isRtl);
354 }
355
356 /* package */ void addMeasuredRun(int start, int end, float[] widths) {
357 nAddMeasuredRun(mNativePtr, start, end, widths);
358 }
359
360 /* package */ void addReplacementRun(int start, int end, float width) {
361 nAddReplacementRun(mNativePtr, start, end, width);
362 }
363
Raph Levien531c30c2015-04-30 16:29:59 -0700364 /**
365 * Build the {@link StaticLayout} after options have been set.
366 *
367 * <p>Note: the builder object must not be reused in any way after calling this
368 * method. Setting parameters after calling this method, or calling it a second
369 * time on the same builder object, will likely lead to unexpected results.
370 *
371 * @return the newly constructed {@link StaticLayout} object
372 */
Raph Leviend3ab6922015-03-02 14:30:53 -0800373 public StaticLayout build() {
Raph Levien39b4db72015-03-25 13:18:20 -0700374 StaticLayout result = new StaticLayout(this);
375 Builder.recycle(this);
Raph Leviend3ab6922015-03-02 14:30:53 -0800376 return result;
377 }
378
Raph Levien4c1f12e2015-03-02 16:29:23 -0800379 @Override
380 protected void finalize() throws Throwable {
381 try {
382 nFreeBuilder(mNativePtr);
383 } finally {
384 super.finalize();
385 }
386 }
387
388 /* package */ long mNativePtr;
389
Raph Leviend3ab6922015-03-02 14:30:53 -0800390 CharSequence mText;
391 int mStart;
392 int mEnd;
393 TextPaint mPaint;
394 int mWidth;
Raph Levien39b4db72015-03-25 13:18:20 -0700395 Alignment mAlignment;
Raph Leviend3ab6922015-03-02 14:30:53 -0800396 TextDirectionHeuristic mTextDir;
397 float mSpacingMult;
398 float mSpacingAdd;
399 boolean mIncludePad;
400 int mEllipsizedWidth;
401 TextUtils.TruncateAt mEllipsize;
402 int mMaxLines;
Raph Levien39b4db72015-03-25 13:18:20 -0700403 int mBreakStrategy;
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700404 int mHyphenationFrequency;
Raph Levien2ea52902015-07-01 14:39:31 -0700405 int[] mLeftIndents;
406 int[] mRightIndents;
Raph Leviend3ab6922015-03-02 14:30:53 -0800407
408 Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
409
410 // This will go away and be subsumed by native builder code
411 MeasuredText mMeasuredText;
412
Raph Levien4c1f12e2015-03-02 16:29:23 -0800413 Locale mLocale;
414
Raph Levien39b4db72015-03-25 13:18:20 -0700415 private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<Builder>(3);
Raph Leviend3ab6922015-03-02 14:30:53 -0800416 }
417
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800418 public StaticLayout(CharSequence source, TextPaint paint,
419 int width,
420 Alignment align, float spacingmult, float spacingadd,
421 boolean includepad) {
422 this(source, 0, source.length(), paint, width, align,
423 spacingmult, spacingadd, includepad);
424 }
425
Doug Feltcb3791202011-07-07 11:57:48 -0700426 /**
427 * @hide
428 */
429 public StaticLayout(CharSequence source, TextPaint paint,
430 int width, Alignment align, TextDirectionHeuristic textDir,
431 float spacingmult, float spacingadd,
432 boolean includepad) {
433 this(source, 0, source.length(), paint, width, align, textDir,
434 spacingmult, spacingadd, includepad);
435 }
436
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800437 public StaticLayout(CharSequence source, int bufstart, int bufend,
438 TextPaint paint, int outerwidth,
439 Alignment align,
440 float spacingmult, float spacingadd,
441 boolean includepad) {
442 this(source, bufstart, bufend, paint, outerwidth, align,
443 spacingmult, spacingadd, includepad, null, 0);
444 }
445
Doug Feltcb3791202011-07-07 11:57:48 -0700446 /**
447 * @hide
448 */
449 public StaticLayout(CharSequence source, int bufstart, int bufend,
450 TextPaint paint, int outerwidth,
451 Alignment align, TextDirectionHeuristic textDir,
452 float spacingmult, float spacingadd,
453 boolean includepad) {
454 this(source, bufstart, bufend, paint, outerwidth, align, textDir,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700455 spacingmult, spacingadd, includepad, null, 0, Integer.MAX_VALUE);
Doug Feltcb3791202011-07-07 11:57:48 -0700456}
457
458 public StaticLayout(CharSequence source, int bufstart, int bufend,
459 TextPaint paint, int outerwidth,
460 Alignment align,
461 float spacingmult, float spacingadd,
462 boolean includepad,
463 TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
464 this(source, bufstart, bufend, paint, outerwidth, align,
465 TextDirectionHeuristics.FIRSTSTRONG_LTR,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700466 spacingmult, spacingadd, includepad, ellipsize, ellipsizedWidth, Integer.MAX_VALUE);
Doug Feltcb3791202011-07-07 11:57:48 -0700467 }
468
469 /**
470 * @hide
471 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800472 public StaticLayout(CharSequence source, int bufstart, int bufend,
473 TextPaint paint, int outerwidth,
Doug Feltcb3791202011-07-07 11:57:48 -0700474 Alignment align, TextDirectionHeuristic textDir,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800475 float spacingmult, float spacingadd,
476 boolean includepad,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700477 TextUtils.TruncateAt ellipsize, int ellipsizedWidth, int maxLines) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800478 super((ellipsize == null)
Doug Felt4e0c5e52010-03-15 16:56:02 -0700479 ? source
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800480 : (source instanceof Spanned)
481 ? new SpannedEllipsizer(source)
482 : new Ellipsizer(source),
Doug Feltcb3791202011-07-07 11:57:48 -0700483 paint, outerwidth, align, textDir, spacingmult, spacingadd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800484
Raph Levienebd66ca2015-04-30 15:27:57 -0700485 Builder b = Builder.obtain(source, bufstart, bufend, paint, outerwidth)
Raph Levien39b4db72015-03-25 13:18:20 -0700486 .setAlignment(align)
Raph Leviena6a08282015-06-03 13:20:45 -0700487 .setTextDirection(textDir)
Raph Levien531c30c2015-04-30 16:29:59 -0700488 .setLineSpacing(spacingadd, spacingmult)
Raph Leviend3ab6922015-03-02 14:30:53 -0800489 .setIncludePad(includepad)
490 .setEllipsizedWidth(ellipsizedWidth)
491 .setEllipsize(ellipsize)
492 .setMaxLines(maxLines);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800493 /*
494 * This is annoying, but we can't refer to the layout until
495 * superclass construction is finished, and the superclass
496 * constructor wants the reference to the display text.
Doug Felt4e0c5e52010-03-15 16:56:02 -0700497 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800498 * This will break if the superclass constructor ever actually
499 * cares about the content instead of just holding the reference.
500 */
501 if (ellipsize != null) {
502 Ellipsizer e = (Ellipsizer) getText();
503
504 e.mLayout = this;
505 e.mWidth = ellipsizedWidth;
506 e.mMethod = ellipsize;
507 mEllipsizedWidth = ellipsizedWidth;
508
509 mColumns = COLUMNS_ELLIPSIZE;
510 } else {
511 mColumns = COLUMNS_NORMAL;
512 mEllipsizedWidth = outerwidth;
513 }
514
Adam Lesinski776abc22014-03-07 11:30:59 -0500515 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2 * mColumns);
516 mLines = new int[mLineDirections.length];
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700517 mMaximumVisibleLineCount = maxLines;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800518
Raph Levien70616ec2015-03-04 10:41:30 -0800519 generate(b, b.mIncludePad, b.mIncludePad);
Doug Felte8e45f22010-03-29 14:58:40 -0700520
Raph Leviend3ab6922015-03-02 14:30:53 -0800521 Builder.recycle(b);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800522 }
523
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700524 /* package */ StaticLayout(CharSequence text) {
525 super(text, null, 0, null, 0, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800526
527 mColumns = COLUMNS_ELLIPSIZE;
Adam Lesinski776abc22014-03-07 11:30:59 -0500528 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2 * mColumns);
529 mLines = new int[mLineDirections.length];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800530 }
531
Raph Levien39b4db72015-03-25 13:18:20 -0700532 private StaticLayout(Builder b) {
533 super((b.mEllipsize == null)
534 ? b.mText
535 : (b.mText instanceof Spanned)
536 ? new SpannedEllipsizer(b.mText)
537 : new Ellipsizer(b.mText),
538 b.mPaint, b.mWidth, b.mAlignment, b.mSpacingMult, b.mSpacingAdd);
539
540 if (b.mEllipsize != null) {
541 Ellipsizer e = (Ellipsizer) getText();
542
543 e.mLayout = this;
544 e.mWidth = b.mEllipsizedWidth;
545 e.mMethod = b.mEllipsize;
546 mEllipsizedWidth = b.mEllipsizedWidth;
547
548 mColumns = COLUMNS_ELLIPSIZE;
549 } else {
550 mColumns = COLUMNS_NORMAL;
551 mEllipsizedWidth = b.mWidth;
552 }
553
554 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2 * mColumns);
555 mLines = new int[mLineDirections.length];
556 mMaximumVisibleLineCount = b.mMaxLines;
557
Raph Levien2ea52902015-07-01 14:39:31 -0700558 mLeftIndents = b.mLeftIndents;
559 mRightIndents = b.mRightIndents;
560
Raph Levien39b4db72015-03-25 13:18:20 -0700561 generate(b, b.mIncludePad, b.mIncludePad);
562 }
563
Raph Leviend3ab6922015-03-02 14:30:53 -0800564 /* package */ void generate(Builder b, boolean includepad, boolean trackpad) {
565 CharSequence source = b.mText;
566 int bufStart = b.mStart;
567 int bufEnd = b.mEnd;
568 TextPaint paint = b.mPaint;
569 int outerWidth = b.mWidth;
570 TextDirectionHeuristic textDir = b.mTextDir;
571 float spacingmult = b.mSpacingMult;
572 float spacingadd = b.mSpacingAdd;
573 float ellipsizedWidth = b.mEllipsizedWidth;
574 TextUtils.TruncateAt ellipsize = b.mEllipsize;
Raph Levien4c1f12e2015-03-02 16:29:23 -0800575 LineBreaks lineBreaks = new LineBreaks(); // TODO: move to builder to avoid allocation costs
Anish Athalyec8f9e622014-07-21 15:26:34 -0700576 // store span end locations
577 int[] spanEndCache = new int[4];
578 // store fontMetrics per span range
579 // must be a multiple of 4 (and > 0) (store top, bottom, ascent, and descent per range)
580 int[] fmCache = new int[4 * 4];
Raph Levien4c1f12e2015-03-02 16:29:23 -0800581 b.setLocale(paint.getTextLocale()); // TODO: also respect LocaleSpan within the text
Anish Athalye88b5b0b2014-06-24 14:39:43 -0700582
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800583 mLineCount = 0;
584
585 int v = 0;
586 boolean needMultiply = (spacingmult != 1 || spacingadd != 0);
587
Raph Leviend3ab6922015-03-02 14:30:53 -0800588 Paint.FontMetricsInt fm = b.mFontMetricsInt;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800589 int[] chooseHtv = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800590
Raph Leviend3ab6922015-03-02 14:30:53 -0800591 MeasuredText measured = b.mMeasuredText;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800592
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800593 Spanned spanned = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800594 if (source instanceof Spanned)
595 spanned = (Spanned) source;
596
Doug Felte8e45f22010-03-29 14:58:40 -0700597 int paraEnd;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800598 for (int paraStart = bufStart; paraStart <= bufEnd; paraStart = paraEnd) {
599 paraEnd = TextUtils.indexOf(source, CHAR_NEW_LINE, paraStart, bufEnd);
Doug Felte8e45f22010-03-29 14:58:40 -0700600 if (paraEnd < 0)
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800601 paraEnd = bufEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800602 else
Doug Felte8e45f22010-03-29 14:58:40 -0700603 paraEnd++;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800604
Anish Athalyec8f9e622014-07-21 15:26:34 -0700605 int firstWidthLineCount = 1;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800606 int firstWidth = outerWidth;
607 int restWidth = outerWidth;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800608
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800609 LineHeightSpan[] chooseHt = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800610
611 if (spanned != null) {
Eric Fischer74d31ef2010-08-05 15:29:36 -0700612 LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
Doug Felte8e45f22010-03-29 14:58:40 -0700613 LeadingMarginSpan.class);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800614 for (int i = 0; i < sp.length; i++) {
Mark Wagner7b5676e2009-10-16 11:44:23 -0700615 LeadingMarginSpan lms = sp[i];
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800616 firstWidth -= sp[i].getLeadingMargin(true);
617 restWidth -= sp[i].getLeadingMargin(false);
Doug Feltcb3791202011-07-07 11:57:48 -0700618
Doug Feltc982f602010-05-25 11:51:40 -0700619 // LeadingMarginSpan2 is odd. The count affects all
Anish Athalyeab08c6d2014-08-08 12:09:58 -0700620 // leading margin spans, not just this particular one
Doug Feltc982f602010-05-25 11:51:40 -0700621 if (lms instanceof LeadingMarginSpan2) {
622 LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700623 firstWidthLineCount = Math.max(firstWidthLineCount,
624 lms2.getLeadingMarginLineCount());
Mark Wagner7b5676e2009-10-16 11:44:23 -0700625 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800626 }
627
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800628 chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800629
Roozbeh Pournader431e5062015-10-16 02:27:03 -0700630 if (chooseHt.length == 0) {
631 chooseHt = null; // So that out() would not assume it has any contents
632 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800633 if (chooseHtv == null ||
634 chooseHtv.length < chooseHt.length) {
Adam Lesinski776abc22014-03-07 11:30:59 -0500635 chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800636 }
637
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800638 for (int i = 0; i < chooseHt.length; i++) {
639 int o = spanned.getSpanStart(chooseHt[i]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800640
Doug Felte8e45f22010-03-29 14:58:40 -0700641 if (o < paraStart) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800642 // starts in this layout, before the
643 // current paragraph
644
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800645 chooseHtv[i] = getLineTop(getLineForOffset(o));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800646 } else {
647 // starts in this paragraph
648
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800649 chooseHtv[i] = v;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800650 }
651 }
652 }
653 }
654
Raph Levien70616ec2015-03-04 10:41:30 -0800655 measured.setPara(source, paraStart, paraEnd, textDir, b);
Doug Felte8e45f22010-03-29 14:58:40 -0700656 char[] chs = measured.mChars;
657 float[] widths = measured.mWidths;
658 byte[] chdirs = measured.mLevels;
659 int dir = measured.mDir;
660 boolean easy = measured.mEasy;
Raph Levienc94f7422015-03-06 19:19:48 -0800661
662 // tab stop locations
663 int[] variableTabStops = null;
664 if (spanned != null) {
665 TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
666 paraEnd, TabStopSpan.class);
667 if (spans.length > 0) {
668 int[] stops = new int[spans.length];
669 for (int i = 0; i < spans.length; i++) {
670 stops[i] = spans[i].getTabStop();
671 }
672 Arrays.sort(stops, 0, stops.length);
673 variableTabStops = stops;
674 }
675 }
676
Raph Levienc94f7422015-03-06 19:19:48 -0800677 nSetupParagraph(b.mNativePtr, chs, paraEnd - paraStart,
678 firstWidth, firstWidthLineCount, restWidth,
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700679 variableTabStops, TAB_INCREMENT, b.mBreakStrategy, b.mHyphenationFrequency);
Raph Levien2ea52902015-07-01 14:39:31 -0700680 if (mLeftIndents != null || mRightIndents != null) {
681 // TODO(raph) performance: it would be better to do this once per layout rather
682 // than once per paragraph, but that would require a change to the native
683 // interface.
684 int leftLen = mLeftIndents == null ? 0 : mLeftIndents.length;
685 int rightLen = mRightIndents == null ? 0 : mRightIndents.length;
686 int indentsLen = Math.max(1, Math.min(leftLen, rightLen) - mLineCount);
687 int[] indents = new int[indentsLen];
688 for (int i = 0; i < indentsLen; i++) {
689 int leftMargin = mLeftIndents == null ? 0 :
690 mLeftIndents[Math.min(i + mLineCount, leftLen - 1)];
691 int rightMargin = mRightIndents == null ? 0 :
692 mRightIndents[Math.min(i + mLineCount, rightLen - 1)];
693 indents[i] = leftMargin + rightMargin;
694 }
695 nSetIndents(b.mNativePtr, indents);
696 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800697
Anish Athalyec8f9e622014-07-21 15:26:34 -0700698 // measurement has to be done before performing line breaking
699 // but we don't want to recompute fontmetrics or span ranges the
700 // second time, so we cache those and then use those stored values
701 int fmCacheCount = 0;
702 int spanEndCacheCount = 0;
Gilles Debunnecd943a72012-06-07 17:54:47 -0700703 for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
Anish Athalyec8f9e622014-07-21 15:26:34 -0700704 if (fmCacheCount * 4 >= fmCache.length) {
705 int[] grow = new int[fmCacheCount * 4 * 2];
706 System.arraycopy(fmCache, 0, grow, 0, fmCacheCount * 4);
707 fmCache = grow;
708 }
709
710 if (spanEndCacheCount >= spanEndCache.length) {
711 int[] grow = new int[spanEndCacheCount * 2];
712 System.arraycopy(spanEndCache, 0, grow, 0, spanEndCacheCount);
713 spanEndCache = grow;
714 }
Doug Felte8e45f22010-03-29 14:58:40 -0700715
Gilles Debunnecd943a72012-06-07 17:54:47 -0700716 if (spanned == null) {
717 spanEnd = paraEnd;
Doug Felt23241882010-06-02 14:41:06 -0700718 int spanLen = spanEnd - spanStart;
Gilles Debunnecd943a72012-06-07 17:54:47 -0700719 measured.addStyleRun(paint, spanLen, fm);
720 } else {
721 spanEnd = spanned.nextSpanTransition(spanStart, paraEnd,
722 MetricAffectingSpan.class);
723 int spanLen = spanEnd - spanStart;
724 MetricAffectingSpan[] spans =
Doug Felt23241882010-06-02 14:41:06 -0700725 spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class);
Gilles Debunnecd943a72012-06-07 17:54:47 -0700726 spans = TextUtils.removeEmptySpans(spans, spanned, MetricAffectingSpan.class);
727 measured.addStyleRun(paint, spans, spanLen, fm);
Doug Felt23241882010-06-02 14:41:06 -0700728 }
729
Anish Athalyec8f9e622014-07-21 15:26:34 -0700730 // the order of storage here (top, bottom, ascent, descent) has to match the code below
731 // where these values are retrieved
732 fmCache[fmCacheCount * 4 + 0] = fm.top;
733 fmCache[fmCacheCount * 4 + 1] = fm.bottom;
734 fmCache[fmCacheCount * 4 + 2] = fm.ascent;
735 fmCache[fmCacheCount * 4 + 3] = fm.descent;
736 fmCacheCount++;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800737
Anish Athalyec8f9e622014-07-21 15:26:34 -0700738 spanEndCache[spanEndCacheCount] = spanEnd;
739 spanEndCacheCount++;
740 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800741
Raph Levien70616ec2015-03-04 10:41:30 -0800742 nGetWidths(b.mNativePtr, widths);
Raph Levienc94f7422015-03-06 19:19:48 -0800743 int breakCount = nComputeLineBreaks(b.mNativePtr, lineBreaks, lineBreaks.breaks,
744 lineBreaks.widths, lineBreaks.flags, lineBreaks.breaks.length);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800745
Anish Athalyec8f9e622014-07-21 15:26:34 -0700746 int[] breaks = lineBreaks.breaks;
747 float[] lineWidths = lineBreaks.widths;
Raph Levien26d443a2015-03-30 14:18:32 -0700748 int[] flags = lineBreaks.flags;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700749
Keisuke Kuroyanagif4a3f3a2015-06-10 09:37:32 +0900750 final int remainingLineCount = mMaximumVisibleLineCount - mLineCount;
751 final boolean ellipsisMayBeApplied = ellipsize != null
752 && (ellipsize == TextUtils.TruncateAt.END
753 || (mMaximumVisibleLineCount == 1
754 && ellipsize != TextUtils.TruncateAt.MARQUEE));
Raph Levien04a84552015-07-09 15:38:04 -0700755 if (remainingLineCount > 0 && remainingLineCount < breakCount &&
756 ellipsisMayBeApplied) {
Keisuke Kuroyanagif4a3f3a2015-06-10 09:37:32 +0900757 // Treat the last line and overflowed lines as a single line.
758 breaks[remainingLineCount - 1] = breaks[breakCount - 1];
759 // Calculate width and flag.
760 float width = 0;
761 int flag = 0;
762 for (int i = remainingLineCount - 1; i < breakCount; i++) {
763 width += lineWidths[i];
764 flag |= flags[i] & TAB_MASK;
765 }
766 lineWidths[remainingLineCount - 1] = width;
767 flags[remainingLineCount - 1] = flag;
768
769 breakCount = remainingLineCount;
770 }
771
Anish Athalyec8f9e622014-07-21 15:26:34 -0700772 // here is the offset of the starting character of the line we are currently measuring
773 int here = paraStart;
774
775 int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
776 int fmCacheIndex = 0;
777 int spanEndCacheIndex = 0;
778 int breakIndex = 0;
779 for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
780 // retrieve end of span
781 spanEnd = spanEndCache[spanEndCacheIndex++];
782
783 // retrieve cached metrics, order matches above
784 fm.top = fmCache[fmCacheIndex * 4 + 0];
785 fm.bottom = fmCache[fmCacheIndex * 4 + 1];
786 fm.ascent = fmCache[fmCacheIndex * 4 + 2];
787 fm.descent = fmCache[fmCacheIndex * 4 + 3];
788 fmCacheIndex++;
789
790 if (fm.top < fmTop) {
791 fmTop = fm.top;
792 }
793 if (fm.ascent < fmAscent) {
794 fmAscent = fm.ascent;
795 }
796 if (fm.descent > fmDescent) {
797 fmDescent = fm.descent;
798 }
799 if (fm.bottom > fmBottom) {
800 fmBottom = fm.bottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800801 }
802
Anish Athalyec8f9e622014-07-21 15:26:34 -0700803 // skip breaks ending before current span range
804 while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
805 breakIndex++;
806 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800807
Anish Athalyec8f9e622014-07-21 15:26:34 -0700808 while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
809 int endPos = paraStart + breaks[breakIndex];
810
Raph Levience4155a2015-03-11 11:02:33 -0700811 boolean moreChars = (endPos < bufEnd);
Raph Levien4c02e832014-12-12 11:17:01 -0800812
Anish Athalyec8f9e622014-07-21 15:26:34 -0700813 v = out(source, here, endPos,
814 fmAscent, fmDescent, fmTop, fmBottom,
Roozbeh Pournader431e5062015-10-16 02:27:03 -0700815 v, spacingmult, spacingadd, chooseHt, chooseHtv, fm, flags[breakIndex],
Anish Athalyec8f9e622014-07-21 15:26:34 -0700816 needMultiply, chdirs, dir, easy, bufEnd, includepad, trackpad,
817 chs, widths, paraStart, ellipsize, ellipsizedWidth,
Raph Levien4c02e832014-12-12 11:17:01 -0800818 lineWidths[breakIndex], paint, moreChars);
Anish Athalyec8f9e622014-07-21 15:26:34 -0700819
820 if (endPos < spanEnd) {
821 // preserve metrics for current span
822 fmTop = fm.top;
823 fmBottom = fm.bottom;
824 fmAscent = fm.ascent;
825 fmDescent = fm.descent;
826 } else {
827 fmTop = fmBottom = fmAscent = fmDescent = 0;
828 }
829
830 here = endPos;
831 breakIndex++;
832
833 if (mLineCount >= mMaximumVisibleLineCount) {
834 return;
835 }
836 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800837 }
838
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800839 if (paraEnd == bufEnd)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800840 break;
841 }
842
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700843 if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE) &&
Fabrice Di Meglioad0b0512011-10-04 17:21:26 -0700844 mLineCount < mMaximumVisibleLineCount) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800845 // Log.e("text", "output last " + bufEnd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800846
Raph Levien70616ec2015-03-04 10:41:30 -0800847 measured.setPara(source, bufEnd, bufEnd, textDir, b);
Fabrice Di Meglioe6318892013-06-18 20:03:41 -0700848
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800849 paint.getFontMetricsInt(fm);
850
851 v = out(source,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800852 bufEnd, bufEnd, fm.ascent, fm.descent,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800853 fm.top, fm.bottom,
854 v,
855 spacingmult, spacingadd, null,
Raph Levien26d443a2015-03-30 14:18:32 -0700856 null, fm, 0,
Fabrice Di Meglioe6318892013-06-18 20:03:41 -0700857 needMultiply, measured.mLevels, measured.mDir, measured.mEasy, bufEnd,
Gilles Debunned300e752011-10-17 13:37:36 -0700858 includepad, trackpad, null,
859 null, bufStart, ellipsize,
860 ellipsizedWidth, 0, paint, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800861 }
862 }
863
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800864 private int out(CharSequence text, int start, int end,
865 int above, int below, int top, int bottom, int v,
866 float spacingmult, float spacingadd,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800867 LineHeightSpan[] chooseHt, int[] chooseHtv,
Raph Levien26d443a2015-03-30 14:18:32 -0700868 Paint.FontMetricsInt fm, int flags,
Gilles Debunned300e752011-10-17 13:37:36 -0700869 boolean needMultiply, byte[] chdirs, int dir,
870 boolean easy, int bufEnd, boolean includePad,
871 boolean trackPad, char[] chs,
872 float[] widths, int widthStart, TextUtils.TruncateAt ellipsize,
873 float ellipsisWidth, float textWidth,
874 TextPaint paint, boolean moreChars) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800875 int j = mLineCount;
876 int off = j * mColumns;
877 int want = off + mColumns + TOP;
878 int[] lines = mLines;
879
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800880 if (want >= lines.length) {
Adam Lesinski776abc22014-03-07 11:30:59 -0500881 Directions[] grow2 = ArrayUtils.newUnpaddedArray(
882 Directions.class, GrowingArrayUtils.growSize(want));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800883 System.arraycopy(mLineDirections, 0, grow2, 0,
884 mLineDirections.length);
885 mLineDirections = grow2;
Adam Lesinski776abc22014-03-07 11:30:59 -0500886
887 int[] grow = new int[grow2.length];
888 System.arraycopy(lines, 0, grow, 0, lines.length);
889 mLines = grow;
890 lines = grow;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800891 }
892
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800893 if (chooseHt != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800894 fm.ascent = above;
895 fm.descent = below;
896 fm.top = top;
897 fm.bottom = bottom;
898
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800899 for (int i = 0; i < chooseHt.length; i++) {
900 if (chooseHt[i] instanceof LineHeightSpan.WithDensity) {
901 ((LineHeightSpan.WithDensity) chooseHt[i]).
902 chooseHeight(text, start, end, chooseHtv[i], v, fm, paint);
Eric Fischera9f1dd02009-08-12 15:00:10 -0700903
904 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800905 chooseHt[i].chooseHeight(text, start, end, chooseHtv[i], v, fm);
Eric Fischera9f1dd02009-08-12 15:00:10 -0700906 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800907 }
908
909 above = fm.ascent;
910 below = fm.descent;
911 top = fm.top;
912 bottom = fm.bottom;
913 }
914
Raph Leviend97b0972014-04-24 12:51:35 -0700915 boolean firstLine = (j == 0);
916 boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);
917 boolean lastLine = currentLineIsTheLastVisibleOne || (end == bufEnd);
918
919 if (firstLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800920 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800921 mTopPadding = top - above;
922 }
923
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800924 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800925 above = top;
926 }
927 }
Raph Leviend97b0972014-04-24 12:51:35 -0700928
929 int extra;
930
931 if (lastLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800932 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800933 mBottomPadding = bottom - below;
934 }
935
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800936 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800937 below = bottom;
938 }
939 }
940
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800941
Raph Leviend97b0972014-04-24 12:51:35 -0700942 if (needMultiply && !lastLine) {
Doug Felt10657582010-02-22 11:19:01 -0800943 double ex = (below - above) * (spacingmult - 1) + spacingadd;
944 if (ex >= 0) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800945 extra = (int)(ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -0800946 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800947 extra = -(int)(-ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -0800948 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800949 } else {
950 extra = 0;
951 }
952
953 lines[off + START] = start;
954 lines[off + TOP] = v;
955 lines[off + DESCENT] = below + extra;
956
957 v += (below - above) + extra;
958 lines[off + mColumns + START] = end;
959 lines[off + mColumns + TOP] = v;
960
Raph Levien26d443a2015-03-30 14:18:32 -0700961 // TODO: could move TAB to share same column as HYPHEN, simplifying this code and gaining
962 // one bit for start field
963 lines[off + TAB] |= flags & TAB_MASK;
964 lines[off + HYPHEN] = flags;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800965
Doug Felt9f7a4442010-03-01 12:45:56 -0800966 lines[off + DIR] |= dir << DIR_SHIFT;
967 Directions linedirs = DIRS_ALL_LEFT_TO_RIGHT;
968 // easy means all chars < the first RTL, so no emoji, no nothing
Doug Felt4e0c5e52010-03-15 16:56:02 -0700969 // XXX a run with no text or all spaces is easy but might be an empty
Doug Felt9f7a4442010-03-01 12:45:56 -0800970 // RTL paragraph. Make sure easy is false if this is the case.
971 if (easy) {
972 mLineDirections[j] = linedirs;
973 } else {
Gilles Debunnef3fa0cd2011-02-03 14:17:05 -0800974 mLineDirections[j] = AndroidBidi.directions(dir, chdirs, start - widthStart, chs,
975 start - widthStart, end - start);
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800976 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800977
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -0700978 if (ellipsize != null) {
979 // If there is only one line, then do any type of ellipsis except when it is MARQUEE
980 // if there are multiple lines, just allow END ellipsis on the last line
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700981 boolean forceEllipsis = moreChars && (mLineCount + 1 == mMaximumVisibleLineCount);
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -0700982
Fabrice Di Meglio34a126e2012-02-29 18:43:14 -0800983 boolean doEllipsis =
984 (((mMaximumVisibleLineCount == 1 && moreChars) || (firstLine && !moreChars)) &&
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -0700985 ellipsize != TextUtils.TruncateAt.MARQUEE) ||
986 (!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
987 ellipsize == TextUtils.TruncateAt.END);
988 if (doEllipsis) {
989 calculateEllipsis(start, end, widths, widthStart,
990 ellipsisWidth, ellipsize, j,
991 textWidth, paint, forceEllipsis);
992 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800993 }
994
995 mLineCount++;
996 return v;
997 }
998
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800999 private void calculateEllipsis(int lineStart, int lineEnd,
1000 float[] widths, int widthStart,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001001 float avail, TextUtils.TruncateAt where,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001002 int line, float textWidth, TextPaint paint,
1003 boolean forceEllipsis) {
1004 if (textWidth <= avail && !forceEllipsis) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001005 // Everything fits!
1006 mLines[mColumns * line + ELLIPSIS_START] = 0;
1007 mLines[mColumns * line + ELLIPSIS_COUNT] = 0;
1008 return;
1009 }
1010
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001011 float ellipsisWidth = paint.measureText(
Fabrice Di Meglio8d44fff2012-06-13 15:45:38 -07001012 (where == TextUtils.TruncateAt.END_SMALL) ?
Neil Fullerd29bdb22015-02-06 10:03:08 +00001013 TextUtils.ELLIPSIS_TWO_DOTS : TextUtils.ELLIPSIS_NORMAL, 0, 1);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001014 int ellipsisStart = 0;
1015 int ellipsisCount = 0;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001016 int len = lineEnd - lineStart;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001017
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001018 // We only support start ellipsis on a single line
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001019 if (where == TextUtils.TruncateAt.START) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001020 if (mMaximumVisibleLineCount == 1) {
1021 float sum = 0;
1022 int i;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001023
Keisuke Kuroyanagied2eea12015-04-14 18:18:35 +09001024 for (i = len; i > 0; i--) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001025 float w = widths[i - 1 + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001026
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001027 if (w + sum + ellipsisWidth > avail) {
1028 break;
1029 }
1030
1031 sum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001032 }
1033
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001034 ellipsisStart = 0;
1035 ellipsisCount = i;
1036 } else {
1037 if (Log.isLoggable(TAG, Log.WARN)) {
1038 Log.w(TAG, "Start Ellipsis only supported with one line");
1039 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001040 }
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001041 } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE ||
1042 where == TextUtils.TruncateAt.END_SMALL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001043 float sum = 0;
1044 int i;
1045
1046 for (i = 0; i < len; i++) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001047 float w = widths[i + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001048
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001049 if (w + sum + ellipsisWidth > avail) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001050 break;
1051 }
1052
1053 sum += w;
1054 }
1055
1056 ellipsisStart = i;
1057 ellipsisCount = len - i;
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -07001058 if (forceEllipsis && ellipsisCount == 0 && len > 0) {
1059 ellipsisStart = len - 1;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001060 ellipsisCount = 1;
1061 }
1062 } else {
1063 // where = TextUtils.TruncateAt.MIDDLE We only support middle ellipsis on a single line
1064 if (mMaximumVisibleLineCount == 1) {
1065 float lsum = 0, rsum = 0;
1066 int left = 0, right = len;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001067
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001068 float ravail = (avail - ellipsisWidth) / 2;
Raph Levien0e3c5e82014-12-04 13:26:07 -08001069 for (right = len; right > 0; right--) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001070 float w = widths[right - 1 + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001071
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001072 if (w + rsum > ravail) {
1073 break;
1074 }
1075
1076 rsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001077 }
1078
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001079 float lavail = avail - ellipsisWidth - rsum;
1080 for (left = 0; left < right; left++) {
1081 float w = widths[left + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001082
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001083 if (w + lsum > lavail) {
1084 break;
1085 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001086
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001087 lsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001088 }
1089
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001090 ellipsisStart = left;
1091 ellipsisCount = right - left;
1092 } else {
1093 if (Log.isLoggable(TAG, Log.WARN)) {
1094 Log.w(TAG, "Middle Ellipsis only supported with one line");
1095 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001096 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001097 }
1098
1099 mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
1100 mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
1101 }
1102
Doug Felte8e45f22010-03-29 14:58:40 -07001103 // Override the base class so we can directly access our members,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001104 // rather than relying on member functions.
1105 // The logic mirrors that of Layout.getLineForVertical
1106 // FIXME: It may be faster to do a linear search for layouts without many lines.
Gilles Debunne66111472010-11-19 11:04:37 -08001107 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001108 public int getLineForVertical(int vertical) {
1109 int high = mLineCount;
1110 int low = -1;
1111 int guess;
1112 int[] lines = mLines;
1113 while (high - low > 1) {
1114 guess = (high + low) >> 1;
1115 if (lines[mColumns * guess + TOP] > vertical){
1116 high = guess;
1117 } else {
1118 low = guess;
1119 }
1120 }
1121 if (low < 0) {
1122 return 0;
1123 } else {
1124 return low;
1125 }
1126 }
1127
Gilles Debunne66111472010-11-19 11:04:37 -08001128 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001129 public int getLineCount() {
1130 return mLineCount;
1131 }
1132
Gilles Debunne66111472010-11-19 11:04:37 -08001133 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001134 public int getLineTop(int line) {
Raph Levien07e6c232016-04-04 12:34:06 -07001135 return mLines[mColumns * line + TOP];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001136 }
1137
Gilles Debunne66111472010-11-19 11:04:37 -08001138 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001139 public int getLineDescent(int line) {
Raph Levien07e6c232016-04-04 12:34:06 -07001140 return mLines[mColumns * line + DESCENT];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001141 }
1142
Gilles Debunne66111472010-11-19 11:04:37 -08001143 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001144 public int getLineStart(int line) {
1145 return mLines[mColumns * line + START] & START_MASK;
1146 }
1147
Gilles Debunne66111472010-11-19 11:04:37 -08001148 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001149 public int getParagraphDirection(int line) {
1150 return mLines[mColumns * line + DIR] >> DIR_SHIFT;
1151 }
1152
Gilles Debunne66111472010-11-19 11:04:37 -08001153 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001154 public boolean getLineContainsTab(int line) {
1155 return (mLines[mColumns * line + TAB] & TAB_MASK) != 0;
1156 }
1157
Gilles Debunne66111472010-11-19 11:04:37 -08001158 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001159 public final Directions getLineDirections(int line) {
1160 return mLineDirections[line];
1161 }
1162
Gilles Debunne66111472010-11-19 11:04:37 -08001163 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001164 public int getTopPadding() {
1165 return mTopPadding;
1166 }
1167
Gilles Debunne66111472010-11-19 11:04:37 -08001168 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001169 public int getBottomPadding() {
1170 return mBottomPadding;
1171 }
1172
Raph Levien26d443a2015-03-30 14:18:32 -07001173 /**
1174 * @hide
1175 */
1176 @Override
1177 public int getHyphen(int line) {
1178 return mLines[mColumns * line + HYPHEN] & 0xff;
1179 }
1180
Raph Levien2ea52902015-07-01 14:39:31 -07001181 /**
1182 * @hide
1183 */
1184 @Override
1185 public int getIndentAdjust(int line, Alignment align) {
1186 if (align == Alignment.ALIGN_LEFT) {
1187 if (mLeftIndents == null) {
1188 return 0;
1189 } else {
1190 return mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1191 }
1192 } else if (align == Alignment.ALIGN_RIGHT) {
1193 if (mRightIndents == null) {
1194 return 0;
1195 } else {
1196 return -mRightIndents[Math.min(line, mRightIndents.length - 1)];
1197 }
1198 } else if (align == Alignment.ALIGN_CENTER) {
1199 int left = 0;
1200 if (mLeftIndents != null) {
1201 left = mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1202 }
1203 int right = 0;
1204 if (mRightIndents != null) {
1205 right = mRightIndents[Math.min(line, mRightIndents.length - 1)];
1206 }
1207 return (left - right) >> 1;
1208 } else {
1209 throw new AssertionError("unhandled alignment " + align);
1210 }
1211 }
1212
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001213 @Override
1214 public int getEllipsisCount(int line) {
1215 if (mColumns < COLUMNS_ELLIPSIZE) {
1216 return 0;
1217 }
1218
1219 return mLines[mColumns * line + ELLIPSIS_COUNT];
1220 }
1221
1222 @Override
1223 public int getEllipsisStart(int line) {
1224 if (mColumns < COLUMNS_ELLIPSIZE) {
1225 return 0;
1226 }
1227
1228 return mLines[mColumns * line + ELLIPSIS_START];
1229 }
1230
1231 @Override
1232 public int getEllipsizedWidth() {
1233 return mEllipsizedWidth;
1234 }
1235
Raph Levien70616ec2015-03-04 10:41:30 -08001236 private static native long nNewBuilder();
1237 private static native void nFreeBuilder(long nativePtr);
1238 private static native void nFinishBuilder(long nativePtr);
Raph Levien26d443a2015-03-30 14:18:32 -07001239
Raph Levien091dba22015-08-31 16:21:20 -07001240 /* package */ static native long nLoadHyphenator(ByteBuffer buf, int offset);
Raph Levien26d443a2015-03-30 14:18:32 -07001241
1242 private static native void nSetLocale(long nativePtr, String locale, long nativeHyphenator);
Raph Levien70616ec2015-03-04 10:41:30 -08001243
Raph Leviene319d5a2015-04-14 23:51:07 -07001244 private static native void nSetIndents(long nativePtr, int[] indents);
1245
Raph Levienc94f7422015-03-06 19:19:48 -08001246 // Set up paragraph text and settings; done as one big method to minimize jni crossings
1247 private static native void nSetupParagraph(long nativePtr, char[] text, int length,
1248 float firstWidth, int firstWidthLineCount, float restWidth,
Roozbeh Pournader95c7a132015-05-12 12:01:06 -07001249 int[] variableTabStops, int defaultTabStop, int breakStrategy, int hyphenationFrequency);
Raph Levien70616ec2015-03-04 10:41:30 -08001250
1251 private static native float nAddStyleRun(long nativePtr, long nativePaint,
1252 long nativeTypeface, int start, int end, boolean isRtl);
1253
1254 private static native void nAddMeasuredRun(long nativePtr,
1255 int start, int end, float[] widths);
1256
1257 private static native void nAddReplacementRun(long nativePtr, int start, int end, float width);
1258
1259 private static native void nGetWidths(long nativePtr, float[] widths);
1260
Anish Athalyec8f9e622014-07-21 15:26:34 -07001261 // populates LineBreaks and returns the number of breaks found
1262 //
1263 // the arrays inside the LineBreaks objects are passed in as well
1264 // to reduce the number of JNI calls in the common case where the
1265 // arrays do not have to be resized
Raph Levienc94f7422015-03-06 19:19:48 -08001266 private static native int nComputeLineBreaks(long nativePtr, LineBreaks recycle,
Raph Levien26d443a2015-03-30 14:18:32 -07001267 int[] recycleBreaks, float[] recycleWidths, int[] recycleFlags, int recycleLength);
Anish Athalye88b5b0b2014-06-24 14:39:43 -07001268
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001269 private int mLineCount;
1270 private int mTopPadding, mBottomPadding;
1271 private int mColumns;
1272 private int mEllipsizedWidth;
1273
Raph Levien26d443a2015-03-30 14:18:32 -07001274 private static final int COLUMNS_NORMAL = 4;
1275 private static final int COLUMNS_ELLIPSIZE = 6;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001276 private static final int START = 0;
1277 private static final int DIR = START;
1278 private static final int TAB = START;
1279 private static final int TOP = 1;
1280 private static final int DESCENT = 2;
Raph Levien26d443a2015-03-30 14:18:32 -07001281 private static final int HYPHEN = 3;
1282 private static final int ELLIPSIS_START = 4;
1283 private static final int ELLIPSIS_COUNT = 5;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001284
1285 private int[] mLines;
1286 private Directions[] mLineDirections;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001287 private int mMaximumVisibleLineCount = Integer.MAX_VALUE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001288
1289 private static final int START_MASK = 0x1FFFFFFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001290 private static final int DIR_SHIFT = 30;
1291 private static final int TAB_MASK = 0x20000000;
1292
Doug Feltc982f602010-05-25 11:51:40 -07001293 private static final int TAB_INCREMENT = 20; // same as Layout, but that's private
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001294
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001295 private static final char CHAR_NEW_LINE = '\n';
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001296
1297 private static final double EXTRA_ROUNDING = 0.5;
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001298
Anish Athalyec8f9e622014-07-21 15:26:34 -07001299 // This is used to return three arrays from a single JNI call when
1300 // performing line breaking
Deepanshu Gupta70539192014-10-15 15:57:40 -07001301 /*package*/ static class LineBreaks {
Anish Athalyec8f9e622014-07-21 15:26:34 -07001302 private static final int INITIAL_SIZE = 16;
1303 public int[] breaks = new int[INITIAL_SIZE];
1304 public float[] widths = new float[INITIAL_SIZE];
Roozbeh Pournader112d9c72015-08-07 12:44:41 -07001305 public int[] flags = new int[INITIAL_SIZE]; // hasTab
Anish Athalyec8f9e622014-07-21 15:26:34 -07001306 // breaks, widths, and flags should all have the same length
1307 }
1308
Raph Levien2ea52902015-07-01 14:39:31 -07001309 private int[] mLeftIndents;
1310 private int[] mRightIndents;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001311}