blob: 08fcd56c5a628acd359ff819fa4366ae99156c58 [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
19import android.graphics.Paint;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080020import android.text.style.LeadingMarginSpan;
Gilles Debunne66111472010-11-19 11:04:37 -080021import android.text.style.LeadingMarginSpan.LeadingMarginSpan2;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080022import android.text.style.LineHeightSpan;
23import android.text.style.MetricAffectingSpan;
Doug Feltc982f602010-05-25 11:51:40 -070024import android.text.style.TabStopSpan;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -070025import android.util.Log;
Raph Levien39b4db72015-03-25 13:18:20 -070026import android.util.Pools.SynchronizedPool;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080027
Doug Feltcb3791202011-07-07 11:57:48 -070028import com.android.internal.util.ArrayUtils;
Adam Lesinski776abc22014-03-07 11:30:59 -050029import com.android.internal.util.GrowingArrayUtils;
Doug Feltcb3791202011-07-07 11:57:48 -070030
Anish Athalyec8f9e622014-07-21 15:26:34 -070031import java.util.Arrays;
Raph Levien4c1f12e2015-03-02 16:29:23 -080032import java.util.Locale;
Anish Athalyec8f9e622014-07-21 15:26:34 -070033
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080034/**
35 * StaticLayout is a Layout for text that will not be edited after it
36 * is laid out. Use {@link DynamicLayout} for text that may change.
37 * <p>This is used by widgets to control text layout. You should not need
38 * to use this class directly unless you are implementing your own widget
39 * or custom display object, or would be tempted to call
Doug Felt4e0c5e52010-03-15 16:56:02 -070040 * {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int,
41 * float, float, android.graphics.Paint)
42 * Canvas.drawText()} directly.</p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080043 */
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -080044public class StaticLayout extends Layout {
45
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -070046 static final String TAG = "StaticLayout";
47
Raph Leviend3ab6922015-03-02 14:30:53 -080048 /**
49 * Builder for static layouts. It would be better if this were a public
50 * API (as it would offer much greater flexibility for adding new options)
51 * but for the time being it's just internal.
52 *
53 * @hide
54 */
55 public final static class Builder {
Raph Levien4c1f12e2015-03-02 16:29:23 -080056 private Builder() {
57 mNativePtr = nNewBuilder();
58 }
59
Raph Levienebd66ca2015-04-30 15:27:57 -070060 public static Builder obtain(CharSequence source, int start, int end, TextPaint paint,
61 int width) {
Raph Levien39b4db72015-03-25 13:18:20 -070062 Builder b = sPool.acquire();
Raph Leviend3ab6922015-03-02 14:30:53 -080063 if (b == null) {
64 b = new Builder();
65 }
66
67 // set default initial values
Raph Levien39b4db72015-03-25 13:18:20 -070068 b.mText = source;
69 b.mStart = start;
70 b.mEnd = end;
Raph Levienebd66ca2015-04-30 15:27:57 -070071 b.mPaint = paint;
Raph Levien39b4db72015-03-25 13:18:20 -070072 b.mWidth = width;
73 b.mAlignment = Alignment.ALIGN_NORMAL;
Raph Leviend3ab6922015-03-02 14:30:53 -080074 b.mTextDir = TextDirectionHeuristics.FIRSTSTRONG_LTR;
75 b.mSpacingMult = 1.0f;
76 b.mSpacingAdd = 0.0f;
77 b.mIncludePad = true;
Raph Levien39b4db72015-03-25 13:18:20 -070078 b.mEllipsizedWidth = width;
Raph Leviend3ab6922015-03-02 14:30:53 -080079 b.mEllipsize = null;
80 b.mMaxLines = Integer.MAX_VALUE;
81
82 b.mMeasuredText = MeasuredText.obtain();
83 return b;
84 }
85
Raph Levien39b4db72015-03-25 13:18:20 -070086 private static void recycle(Builder b) {
Raph Leviend3ab6922015-03-02 14:30:53 -080087 b.mPaint = null;
88 b.mText = null;
89 MeasuredText.recycle(b.mMeasuredText);
Raph Levien39b4db72015-03-25 13:18:20 -070090 sPool.release(b);
Raph Leviend3ab6922015-03-02 14:30:53 -080091 }
92
93 // release any expensive state
94 /* package */ void finish() {
Raph Levien4c1f12e2015-03-02 16:29:23 -080095 nFinishBuilder(mNativePtr);
Raph Leviend3ab6922015-03-02 14:30:53 -080096 mMeasuredText.finish();
97 }
98
99 public Builder setText(CharSequence source) {
100 return setText(source, 0, source.length());
101 }
102
103 public Builder setText(CharSequence source, int start, int end) {
104 mText = source;
105 mStart = start;
106 mEnd = end;
107 return this;
108 }
109
110 public Builder setPaint(TextPaint paint) {
111 mPaint = paint;
112 return this;
113 }
114
115 public Builder setWidth(int width) {
116 mWidth = width;
117 if (mEllipsize == null) {
118 mEllipsizedWidth = width;
119 }
120 return this;
121 }
122
Raph Levien39b4db72015-03-25 13:18:20 -0700123 public Builder setAlignment(Alignment alignment) {
124 mAlignment = alignment;
125 return this;
126 }
127
Raph Leviend3ab6922015-03-02 14:30:53 -0800128 public Builder setTextDir(TextDirectionHeuristic textDir) {
129 mTextDir = textDir;
130 return this;
131 }
132
133 // TODO: combine the following, as they're almost always set together?
134 public Builder setSpacingMult(float spacingMult) {
135 mSpacingMult = spacingMult;
136 return this;
137 }
138
139 public Builder setSpacingAdd(float spacingAdd) {
140 mSpacingAdd = spacingAdd;
141 return this;
142 }
143
144 public Builder setIncludePad(boolean includePad) {
145 mIncludePad = includePad;
146 return this;
147 }
148
149 // TODO: combine the following?
150 public Builder setEllipsizedWidth(int ellipsizedWidth) {
151 mEllipsizedWidth = ellipsizedWidth;
152 return this;
153 }
154
155 public Builder setEllipsize(TextUtils.TruncateAt ellipsize) {
156 mEllipsize = ellipsize;
157 return this;
158 }
159
160 public Builder setMaxLines(int maxLines) {
161 mMaxLines = maxLines;
162 return this;
163 }
164
Raph Levien39b4db72015-03-25 13:18:20 -0700165 public Builder setBreakStrategy(@BreakStrategy int breakStrategy) {
166 mBreakStrategy = breakStrategy;
167 return this;
168 }
169
Raph Leviene319d5a2015-04-14 23:51:07 -0700170 public Builder setIndents(int[] leftIndents, int[] rightIndents) {
171 int leftLen = leftIndents == null ? 0 : leftIndents.length;
172 int rightLen = rightIndents == null ? 0 : rightIndents.length;
173 int[] indents = new int[Math.max(leftLen, rightLen)];
174 for (int i = 0; i < indents.length; i++) {
175 int leftMargin = i < leftLen ? leftIndents[i] : 0;
176 int rightMargin = i < rightLen ? rightIndents[i] : 0;
177 indents[i] = leftMargin + rightMargin;
178 }
179 nSetIndents(mNativePtr, indents);
180 return this;
181 }
182
Raph Levien70616ec2015-03-04 10:41:30 -0800183 /**
184 * Measurement and break iteration is done in native code. The protocol for using
185 * the native code is as follows.
186 *
Raph Levien26d443a2015-03-30 14:18:32 -0700187 * For each paragraph, do a nSetupParagraph, which sets paragraph text, line width, tab
188 * stops, break strategy (and possibly other parameters in the future).
Raph Levienc94f7422015-03-06 19:19:48 -0800189 *
190 * Then, for each run within the paragraph:
Raph Levien70616ec2015-03-04 10:41:30 -0800191 * - setLocale (this must be done at least for the first run, optional afterwards)
192 * - one of the following, depending on the type of run:
193 * + addStyleRun (a text run, to be measured in native code)
194 * + addMeasuredRun (a run already measured in Java, passed into native code)
195 * + addReplacementRun (a replacement run, width is given)
196 *
197 * After measurement, nGetWidths() is valid if the widths are needed (eg for ellipsis).
198 * Run nComputeLineBreaks() to obtain line breaks for the paragraph.
199 *
200 * After all paragraphs, call finish() to release expensive buffers.
201 */
202
203 private void setLocale(Locale locale) {
Raph Levien4c1f12e2015-03-02 16:29:23 -0800204 if (!locale.equals(mLocale)) {
Raph Levien26d443a2015-03-30 14:18:32 -0700205 nSetLocale(mNativePtr, locale.toLanguageTag(), Hyphenator.get(locale));
Raph Levien4c1f12e2015-03-02 16:29:23 -0800206 mLocale = locale;
207 }
208 }
209
Raph Levien70616ec2015-03-04 10:41:30 -0800210 /* package */ float addStyleRun(TextPaint paint, int start, int end, boolean isRtl) {
211 return nAddStyleRun(mNativePtr, paint.getNativeInstance(), paint.mNativeTypeface,
212 start, end, isRtl);
213 }
214
215 /* package */ void addMeasuredRun(int start, int end, float[] widths) {
216 nAddMeasuredRun(mNativePtr, start, end, widths);
217 }
218
219 /* package */ void addReplacementRun(int start, int end, float width) {
220 nAddReplacementRun(mNativePtr, start, end, width);
221 }
222
Raph Leviend3ab6922015-03-02 14:30:53 -0800223 public StaticLayout build() {
Raph Levien39b4db72015-03-25 13:18:20 -0700224 StaticLayout result = new StaticLayout(this);
225 Builder.recycle(this);
Raph Leviend3ab6922015-03-02 14:30:53 -0800226 return result;
227 }
228
Raph Levien4c1f12e2015-03-02 16:29:23 -0800229 @Override
230 protected void finalize() throws Throwable {
231 try {
232 nFreeBuilder(mNativePtr);
233 } finally {
234 super.finalize();
235 }
236 }
237
238 /* package */ long mNativePtr;
239
Raph Leviend3ab6922015-03-02 14:30:53 -0800240 CharSequence mText;
241 int mStart;
242 int mEnd;
243 TextPaint mPaint;
244 int mWidth;
Raph Levien39b4db72015-03-25 13:18:20 -0700245 Alignment mAlignment;
Raph Leviend3ab6922015-03-02 14:30:53 -0800246 TextDirectionHeuristic mTextDir;
247 float mSpacingMult;
248 float mSpacingAdd;
249 boolean mIncludePad;
250 int mEllipsizedWidth;
251 TextUtils.TruncateAt mEllipsize;
252 int mMaxLines;
Raph Levien39b4db72015-03-25 13:18:20 -0700253 int mBreakStrategy;
Raph Leviend3ab6922015-03-02 14:30:53 -0800254
255 Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
256
257 // This will go away and be subsumed by native builder code
258 MeasuredText mMeasuredText;
259
Raph Levien4c1f12e2015-03-02 16:29:23 -0800260 Locale mLocale;
261
Raph Levien39b4db72015-03-25 13:18:20 -0700262 private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<Builder>(3);
Raph Leviend3ab6922015-03-02 14:30:53 -0800263 }
264
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800265 public StaticLayout(CharSequence source, TextPaint paint,
266 int width,
267 Alignment align, float spacingmult, float spacingadd,
268 boolean includepad) {
269 this(source, 0, source.length(), paint, width, align,
270 spacingmult, spacingadd, includepad);
271 }
272
Doug Feltcb3791202011-07-07 11:57:48 -0700273 /**
274 * @hide
275 */
276 public StaticLayout(CharSequence source, TextPaint paint,
277 int width, Alignment align, TextDirectionHeuristic textDir,
278 float spacingmult, float spacingadd,
279 boolean includepad) {
280 this(source, 0, source.length(), paint, width, align, textDir,
281 spacingmult, spacingadd, includepad);
282 }
283
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800284 public StaticLayout(CharSequence source, int bufstart, int bufend,
285 TextPaint paint, int outerwidth,
286 Alignment align,
287 float spacingmult, float spacingadd,
288 boolean includepad) {
289 this(source, bufstart, bufend, paint, outerwidth, align,
290 spacingmult, spacingadd, includepad, null, 0);
291 }
292
Doug Feltcb3791202011-07-07 11:57:48 -0700293 /**
294 * @hide
295 */
296 public StaticLayout(CharSequence source, int bufstart, int bufend,
297 TextPaint paint, int outerwidth,
298 Alignment align, TextDirectionHeuristic textDir,
299 float spacingmult, float spacingadd,
300 boolean includepad) {
301 this(source, bufstart, bufend, paint, outerwidth, align, textDir,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700302 spacingmult, spacingadd, includepad, null, 0, Integer.MAX_VALUE);
Doug Feltcb3791202011-07-07 11:57:48 -0700303}
304
305 public StaticLayout(CharSequence source, int bufstart, int bufend,
306 TextPaint paint, int outerwidth,
307 Alignment align,
308 float spacingmult, float spacingadd,
309 boolean includepad,
310 TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
311 this(source, bufstart, bufend, paint, outerwidth, align,
312 TextDirectionHeuristics.FIRSTSTRONG_LTR,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700313 spacingmult, spacingadd, includepad, ellipsize, ellipsizedWidth, Integer.MAX_VALUE);
Doug Feltcb3791202011-07-07 11:57:48 -0700314 }
315
316 /**
317 * @hide
318 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800319 public StaticLayout(CharSequence source, int bufstart, int bufend,
320 TextPaint paint, int outerwidth,
Doug Feltcb3791202011-07-07 11:57:48 -0700321 Alignment align, TextDirectionHeuristic textDir,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800322 float spacingmult, float spacingadd,
323 boolean includepad,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700324 TextUtils.TruncateAt ellipsize, int ellipsizedWidth, int maxLines) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800325 super((ellipsize == null)
Doug Felt4e0c5e52010-03-15 16:56:02 -0700326 ? source
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800327 : (source instanceof Spanned)
328 ? new SpannedEllipsizer(source)
329 : new Ellipsizer(source),
Doug Feltcb3791202011-07-07 11:57:48 -0700330 paint, outerwidth, align, textDir, spacingmult, spacingadd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800331
Raph Levienebd66ca2015-04-30 15:27:57 -0700332 Builder b = Builder.obtain(source, bufstart, bufend, paint, outerwidth)
Raph Levien39b4db72015-03-25 13:18:20 -0700333 .setAlignment(align)
Raph Leviend3ab6922015-03-02 14:30:53 -0800334 .setTextDir(textDir)
335 .setSpacingMult(spacingmult)
336 .setSpacingAdd(spacingadd)
337 .setIncludePad(includepad)
338 .setEllipsizedWidth(ellipsizedWidth)
339 .setEllipsize(ellipsize)
340 .setMaxLines(maxLines);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800341 /*
342 * This is annoying, but we can't refer to the layout until
343 * superclass construction is finished, and the superclass
344 * constructor wants the reference to the display text.
Doug Felt4e0c5e52010-03-15 16:56:02 -0700345 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800346 * This will break if the superclass constructor ever actually
347 * cares about the content instead of just holding the reference.
348 */
349 if (ellipsize != null) {
350 Ellipsizer e = (Ellipsizer) getText();
351
352 e.mLayout = this;
353 e.mWidth = ellipsizedWidth;
354 e.mMethod = ellipsize;
355 mEllipsizedWidth = ellipsizedWidth;
356
357 mColumns = COLUMNS_ELLIPSIZE;
358 } else {
359 mColumns = COLUMNS_NORMAL;
360 mEllipsizedWidth = outerwidth;
361 }
362
Adam Lesinski776abc22014-03-07 11:30:59 -0500363 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2 * mColumns);
364 mLines = new int[mLineDirections.length];
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700365 mMaximumVisibleLineCount = maxLines;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800366
Raph Levien70616ec2015-03-04 10:41:30 -0800367 generate(b, b.mIncludePad, b.mIncludePad);
Doug Felte8e45f22010-03-29 14:58:40 -0700368
Raph Leviend3ab6922015-03-02 14:30:53 -0800369 Builder.recycle(b);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800370 }
371
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700372 /* package */ StaticLayout(CharSequence text) {
373 super(text, null, 0, null, 0, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800374
375 mColumns = COLUMNS_ELLIPSIZE;
Adam Lesinski776abc22014-03-07 11:30:59 -0500376 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2 * mColumns);
377 mLines = new int[mLineDirections.length];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800378 }
379
Raph Levien39b4db72015-03-25 13:18:20 -0700380 private StaticLayout(Builder b) {
381 super((b.mEllipsize == null)
382 ? b.mText
383 : (b.mText instanceof Spanned)
384 ? new SpannedEllipsizer(b.mText)
385 : new Ellipsizer(b.mText),
386 b.mPaint, b.mWidth, b.mAlignment, b.mSpacingMult, b.mSpacingAdd);
387
388 if (b.mEllipsize != null) {
389 Ellipsizer e = (Ellipsizer) getText();
390
391 e.mLayout = this;
392 e.mWidth = b.mEllipsizedWidth;
393 e.mMethod = b.mEllipsize;
394 mEllipsizedWidth = b.mEllipsizedWidth;
395
396 mColumns = COLUMNS_ELLIPSIZE;
397 } else {
398 mColumns = COLUMNS_NORMAL;
399 mEllipsizedWidth = b.mWidth;
400 }
401
402 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2 * mColumns);
403 mLines = new int[mLineDirections.length];
404 mMaximumVisibleLineCount = b.mMaxLines;
405
406 generate(b, b.mIncludePad, b.mIncludePad);
407 }
408
Raph Leviend3ab6922015-03-02 14:30:53 -0800409 /* package */ void generate(Builder b, boolean includepad, boolean trackpad) {
410 CharSequence source = b.mText;
411 int bufStart = b.mStart;
412 int bufEnd = b.mEnd;
413 TextPaint paint = b.mPaint;
414 int outerWidth = b.mWidth;
415 TextDirectionHeuristic textDir = b.mTextDir;
416 float spacingmult = b.mSpacingMult;
417 float spacingadd = b.mSpacingAdd;
418 float ellipsizedWidth = b.mEllipsizedWidth;
419 TextUtils.TruncateAt ellipsize = b.mEllipsize;
Raph Levien4c1f12e2015-03-02 16:29:23 -0800420 LineBreaks lineBreaks = new LineBreaks(); // TODO: move to builder to avoid allocation costs
Anish Athalyec8f9e622014-07-21 15:26:34 -0700421 // store span end locations
422 int[] spanEndCache = new int[4];
423 // store fontMetrics per span range
424 // must be a multiple of 4 (and > 0) (store top, bottom, ascent, and descent per range)
425 int[] fmCache = new int[4 * 4];
Raph Levien4c1f12e2015-03-02 16:29:23 -0800426 b.setLocale(paint.getTextLocale()); // TODO: also respect LocaleSpan within the text
Anish Athalye88b5b0b2014-06-24 14:39:43 -0700427
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800428 mLineCount = 0;
429
430 int v = 0;
431 boolean needMultiply = (spacingmult != 1 || spacingadd != 0);
432
Raph Leviend3ab6922015-03-02 14:30:53 -0800433 Paint.FontMetricsInt fm = b.mFontMetricsInt;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800434 int[] chooseHtv = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800435
Raph Leviend3ab6922015-03-02 14:30:53 -0800436 MeasuredText measured = b.mMeasuredText;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800437
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800438 Spanned spanned = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800439 if (source instanceof Spanned)
440 spanned = (Spanned) source;
441
Doug Felte8e45f22010-03-29 14:58:40 -0700442 int paraEnd;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800443 for (int paraStart = bufStart; paraStart <= bufEnd; paraStart = paraEnd) {
444 paraEnd = TextUtils.indexOf(source, CHAR_NEW_LINE, paraStart, bufEnd);
Doug Felte8e45f22010-03-29 14:58:40 -0700445 if (paraEnd < 0)
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800446 paraEnd = bufEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800447 else
Doug Felte8e45f22010-03-29 14:58:40 -0700448 paraEnd++;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800449
Anish Athalyec8f9e622014-07-21 15:26:34 -0700450 int firstWidthLineCount = 1;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800451 int firstWidth = outerWidth;
452 int restWidth = outerWidth;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800453
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800454 LineHeightSpan[] chooseHt = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800455
456 if (spanned != null) {
Eric Fischer74d31ef2010-08-05 15:29:36 -0700457 LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
Doug Felte8e45f22010-03-29 14:58:40 -0700458 LeadingMarginSpan.class);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800459 for (int i = 0; i < sp.length; i++) {
Mark Wagner7b5676e2009-10-16 11:44:23 -0700460 LeadingMarginSpan lms = sp[i];
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800461 firstWidth -= sp[i].getLeadingMargin(true);
462 restWidth -= sp[i].getLeadingMargin(false);
Doug Feltcb3791202011-07-07 11:57:48 -0700463
Doug Feltc982f602010-05-25 11:51:40 -0700464 // LeadingMarginSpan2 is odd. The count affects all
Anish Athalyeab08c6d2014-08-08 12:09:58 -0700465 // leading margin spans, not just this particular one
Doug Feltc982f602010-05-25 11:51:40 -0700466 if (lms instanceof LeadingMarginSpan2) {
467 LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700468 firstWidthLineCount = Math.max(firstWidthLineCount,
469 lms2.getLeadingMarginLineCount());
Mark Wagner7b5676e2009-10-16 11:44:23 -0700470 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800471 }
472
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800473 chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800474
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800475 if (chooseHt.length != 0) {
476 if (chooseHtv == null ||
477 chooseHtv.length < chooseHt.length) {
Adam Lesinski776abc22014-03-07 11:30:59 -0500478 chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800479 }
480
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800481 for (int i = 0; i < chooseHt.length; i++) {
482 int o = spanned.getSpanStart(chooseHt[i]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800483
Doug Felte8e45f22010-03-29 14:58:40 -0700484 if (o < paraStart) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800485 // starts in this layout, before the
486 // current paragraph
487
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800488 chooseHtv[i] = getLineTop(getLineForOffset(o));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800489 } else {
490 // starts in this paragraph
491
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800492 chooseHtv[i] = v;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800493 }
494 }
495 }
496 }
497
Raph Levien70616ec2015-03-04 10:41:30 -0800498 measured.setPara(source, paraStart, paraEnd, textDir, b);
Doug Felte8e45f22010-03-29 14:58:40 -0700499 char[] chs = measured.mChars;
500 float[] widths = measured.mWidths;
501 byte[] chdirs = measured.mLevels;
502 int dir = measured.mDir;
503 boolean easy = measured.mEasy;
Raph Levienc94f7422015-03-06 19:19:48 -0800504
505 // tab stop locations
506 int[] variableTabStops = null;
507 if (spanned != null) {
508 TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
509 paraEnd, TabStopSpan.class);
510 if (spans.length > 0) {
511 int[] stops = new int[spans.length];
512 for (int i = 0; i < spans.length; i++) {
513 stops[i] = spans[i].getTabStop();
514 }
515 Arrays.sort(stops, 0, stops.length);
516 variableTabStops = stops;
517 }
518 }
519
Raph Levienc94f7422015-03-06 19:19:48 -0800520 nSetupParagraph(b.mNativePtr, chs, paraEnd - paraStart,
521 firstWidth, firstWidthLineCount, restWidth,
Raph Levien39b4db72015-03-25 13:18:20 -0700522 variableTabStops, TAB_INCREMENT, b.mBreakStrategy);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800523
Anish Athalyec8f9e622014-07-21 15:26:34 -0700524 // measurement has to be done before performing line breaking
525 // but we don't want to recompute fontmetrics or span ranges the
526 // second time, so we cache those and then use those stored values
527 int fmCacheCount = 0;
528 int spanEndCacheCount = 0;
Gilles Debunnecd943a72012-06-07 17:54:47 -0700529 for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
Anish Athalyec8f9e622014-07-21 15:26:34 -0700530 if (fmCacheCount * 4 >= fmCache.length) {
531 int[] grow = new int[fmCacheCount * 4 * 2];
532 System.arraycopy(fmCache, 0, grow, 0, fmCacheCount * 4);
533 fmCache = grow;
534 }
535
536 if (spanEndCacheCount >= spanEndCache.length) {
537 int[] grow = new int[spanEndCacheCount * 2];
538 System.arraycopy(spanEndCache, 0, grow, 0, spanEndCacheCount);
539 spanEndCache = grow;
540 }
Doug Felte8e45f22010-03-29 14:58:40 -0700541
Gilles Debunnecd943a72012-06-07 17:54:47 -0700542 if (spanned == null) {
543 spanEnd = paraEnd;
Doug Felt23241882010-06-02 14:41:06 -0700544 int spanLen = spanEnd - spanStart;
Gilles Debunnecd943a72012-06-07 17:54:47 -0700545 measured.addStyleRun(paint, spanLen, fm);
546 } else {
547 spanEnd = spanned.nextSpanTransition(spanStart, paraEnd,
548 MetricAffectingSpan.class);
549 int spanLen = spanEnd - spanStart;
550 MetricAffectingSpan[] spans =
Doug Felt23241882010-06-02 14:41:06 -0700551 spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class);
Gilles Debunnecd943a72012-06-07 17:54:47 -0700552 spans = TextUtils.removeEmptySpans(spans, spanned, MetricAffectingSpan.class);
553 measured.addStyleRun(paint, spans, spanLen, fm);
Doug Felt23241882010-06-02 14:41:06 -0700554 }
555
Anish Athalyec8f9e622014-07-21 15:26:34 -0700556 // the order of storage here (top, bottom, ascent, descent) has to match the code below
557 // where these values are retrieved
558 fmCache[fmCacheCount * 4 + 0] = fm.top;
559 fmCache[fmCacheCount * 4 + 1] = fm.bottom;
560 fmCache[fmCacheCount * 4 + 2] = fm.ascent;
561 fmCache[fmCacheCount * 4 + 3] = fm.descent;
562 fmCacheCount++;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800563
Anish Athalyec8f9e622014-07-21 15:26:34 -0700564 spanEndCache[spanEndCacheCount] = spanEnd;
565 spanEndCacheCount++;
566 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800567
Raph Levien70616ec2015-03-04 10:41:30 -0800568 nGetWidths(b.mNativePtr, widths);
Raph Levienc94f7422015-03-06 19:19:48 -0800569 int breakCount = nComputeLineBreaks(b.mNativePtr, lineBreaks, lineBreaks.breaks,
570 lineBreaks.widths, lineBreaks.flags, lineBreaks.breaks.length);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800571
Anish Athalyec8f9e622014-07-21 15:26:34 -0700572 int[] breaks = lineBreaks.breaks;
573 float[] lineWidths = lineBreaks.widths;
Raph Levien26d443a2015-03-30 14:18:32 -0700574 int[] flags = lineBreaks.flags;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700575
Anish Athalyec8f9e622014-07-21 15:26:34 -0700576 // here is the offset of the starting character of the line we are currently measuring
577 int here = paraStart;
578
579 int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
580 int fmCacheIndex = 0;
581 int spanEndCacheIndex = 0;
582 int breakIndex = 0;
583 for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
584 // retrieve end of span
585 spanEnd = spanEndCache[spanEndCacheIndex++];
586
587 // retrieve cached metrics, order matches above
588 fm.top = fmCache[fmCacheIndex * 4 + 0];
589 fm.bottom = fmCache[fmCacheIndex * 4 + 1];
590 fm.ascent = fmCache[fmCacheIndex * 4 + 2];
591 fm.descent = fmCache[fmCacheIndex * 4 + 3];
592 fmCacheIndex++;
593
594 if (fm.top < fmTop) {
595 fmTop = fm.top;
596 }
597 if (fm.ascent < fmAscent) {
598 fmAscent = fm.ascent;
599 }
600 if (fm.descent > fmDescent) {
601 fmDescent = fm.descent;
602 }
603 if (fm.bottom > fmBottom) {
604 fmBottom = fm.bottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800605 }
606
Anish Athalyec8f9e622014-07-21 15:26:34 -0700607 // skip breaks ending before current span range
608 while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
609 breakIndex++;
610 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800611
Anish Athalyec8f9e622014-07-21 15:26:34 -0700612 while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
613 int endPos = paraStart + breaks[breakIndex];
614
Raph Levience4155a2015-03-11 11:02:33 -0700615 boolean moreChars = (endPos < bufEnd);
Raph Levien4c02e832014-12-12 11:17:01 -0800616
Anish Athalyec8f9e622014-07-21 15:26:34 -0700617 v = out(source, here, endPos,
618 fmAscent, fmDescent, fmTop, fmBottom,
619 v, spacingmult, spacingadd, chooseHt,chooseHtv, fm, flags[breakIndex],
620 needMultiply, chdirs, dir, easy, bufEnd, includepad, trackpad,
621 chs, widths, paraStart, ellipsize, ellipsizedWidth,
Raph Levien4c02e832014-12-12 11:17:01 -0800622 lineWidths[breakIndex], paint, moreChars);
Anish Athalyec8f9e622014-07-21 15:26:34 -0700623
624 if (endPos < spanEnd) {
625 // preserve metrics for current span
626 fmTop = fm.top;
627 fmBottom = fm.bottom;
628 fmAscent = fm.ascent;
629 fmDescent = fm.descent;
630 } else {
631 fmTop = fmBottom = fmAscent = fmDescent = 0;
632 }
633
634 here = endPos;
635 breakIndex++;
636
637 if (mLineCount >= mMaximumVisibleLineCount) {
638 return;
639 }
640 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800641 }
642
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800643 if (paraEnd == bufEnd)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800644 break;
645 }
646
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700647 if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE) &&
Fabrice Di Meglioad0b0512011-10-04 17:21:26 -0700648 mLineCount < mMaximumVisibleLineCount) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800649 // Log.e("text", "output last " + bufEnd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800650
Raph Levien70616ec2015-03-04 10:41:30 -0800651 measured.setPara(source, bufEnd, bufEnd, textDir, b);
Fabrice Di Meglioe6318892013-06-18 20:03:41 -0700652
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800653 paint.getFontMetricsInt(fm);
654
655 v = out(source,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800656 bufEnd, bufEnd, fm.ascent, fm.descent,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800657 fm.top, fm.bottom,
658 v,
659 spacingmult, spacingadd, null,
Raph Levien26d443a2015-03-30 14:18:32 -0700660 null, fm, 0,
Fabrice Di Meglioe6318892013-06-18 20:03:41 -0700661 needMultiply, measured.mLevels, measured.mDir, measured.mEasy, bufEnd,
Gilles Debunned300e752011-10-17 13:37:36 -0700662 includepad, trackpad, null,
663 null, bufStart, ellipsize,
664 ellipsizedWidth, 0, paint, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800665 }
666 }
667
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800668 private int out(CharSequence text, int start, int end,
669 int above, int below, int top, int bottom, int v,
670 float spacingmult, float spacingadd,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800671 LineHeightSpan[] chooseHt, int[] chooseHtv,
Raph Levien26d443a2015-03-30 14:18:32 -0700672 Paint.FontMetricsInt fm, int flags,
Gilles Debunned300e752011-10-17 13:37:36 -0700673 boolean needMultiply, byte[] chdirs, int dir,
674 boolean easy, int bufEnd, boolean includePad,
675 boolean trackPad, char[] chs,
676 float[] widths, int widthStart, TextUtils.TruncateAt ellipsize,
677 float ellipsisWidth, float textWidth,
678 TextPaint paint, boolean moreChars) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800679 int j = mLineCount;
680 int off = j * mColumns;
681 int want = off + mColumns + TOP;
682 int[] lines = mLines;
683
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800684 if (want >= lines.length) {
Adam Lesinski776abc22014-03-07 11:30:59 -0500685 Directions[] grow2 = ArrayUtils.newUnpaddedArray(
686 Directions.class, GrowingArrayUtils.growSize(want));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800687 System.arraycopy(mLineDirections, 0, grow2, 0,
688 mLineDirections.length);
689 mLineDirections = grow2;
Adam Lesinski776abc22014-03-07 11:30:59 -0500690
691 int[] grow = new int[grow2.length];
692 System.arraycopy(lines, 0, grow, 0, lines.length);
693 mLines = grow;
694 lines = grow;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800695 }
696
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800697 if (chooseHt != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800698 fm.ascent = above;
699 fm.descent = below;
700 fm.top = top;
701 fm.bottom = bottom;
702
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800703 for (int i = 0; i < chooseHt.length; i++) {
704 if (chooseHt[i] instanceof LineHeightSpan.WithDensity) {
705 ((LineHeightSpan.WithDensity) chooseHt[i]).
706 chooseHeight(text, start, end, chooseHtv[i], v, fm, paint);
Eric Fischera9f1dd02009-08-12 15:00:10 -0700707
708 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800709 chooseHt[i].chooseHeight(text, start, end, chooseHtv[i], v, fm);
Eric Fischera9f1dd02009-08-12 15:00:10 -0700710 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800711 }
712
713 above = fm.ascent;
714 below = fm.descent;
715 top = fm.top;
716 bottom = fm.bottom;
717 }
718
Raph Leviend97b0972014-04-24 12:51:35 -0700719 boolean firstLine = (j == 0);
720 boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);
721 boolean lastLine = currentLineIsTheLastVisibleOne || (end == bufEnd);
722
723 if (firstLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800724 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800725 mTopPadding = top - above;
726 }
727
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800728 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800729 above = top;
730 }
731 }
Raph Leviend97b0972014-04-24 12:51:35 -0700732
733 int extra;
734
735 if (lastLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800736 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800737 mBottomPadding = bottom - below;
738 }
739
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800740 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800741 below = bottom;
742 }
743 }
744
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800745
Raph Leviend97b0972014-04-24 12:51:35 -0700746 if (needMultiply && !lastLine) {
Doug Felt10657582010-02-22 11:19:01 -0800747 double ex = (below - above) * (spacingmult - 1) + spacingadd;
748 if (ex >= 0) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800749 extra = (int)(ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -0800750 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800751 extra = -(int)(-ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -0800752 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800753 } else {
754 extra = 0;
755 }
756
757 lines[off + START] = start;
758 lines[off + TOP] = v;
759 lines[off + DESCENT] = below + extra;
760
761 v += (below - above) + extra;
762 lines[off + mColumns + START] = end;
763 lines[off + mColumns + TOP] = v;
764
Raph Levien26d443a2015-03-30 14:18:32 -0700765 // TODO: could move TAB to share same column as HYPHEN, simplifying this code and gaining
766 // one bit for start field
767 lines[off + TAB] |= flags & TAB_MASK;
768 lines[off + HYPHEN] = flags;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800769
Doug Felt9f7a4442010-03-01 12:45:56 -0800770 lines[off + DIR] |= dir << DIR_SHIFT;
771 Directions linedirs = DIRS_ALL_LEFT_TO_RIGHT;
772 // easy means all chars < the first RTL, so no emoji, no nothing
Doug Felt4e0c5e52010-03-15 16:56:02 -0700773 // XXX a run with no text or all spaces is easy but might be an empty
Doug Felt9f7a4442010-03-01 12:45:56 -0800774 // RTL paragraph. Make sure easy is false if this is the case.
775 if (easy) {
776 mLineDirections[j] = linedirs;
777 } else {
Gilles Debunnef3fa0cd2011-02-03 14:17:05 -0800778 mLineDirections[j] = AndroidBidi.directions(dir, chdirs, start - widthStart, chs,
779 start - widthStart, end - start);
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800780 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800781
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -0700782 if (ellipsize != null) {
783 // If there is only one line, then do any type of ellipsis except when it is MARQUEE
784 // if there are multiple lines, just allow END ellipsis on the last line
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700785 boolean forceEllipsis = moreChars && (mLineCount + 1 == mMaximumVisibleLineCount);
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -0700786
Fabrice Di Meglio34a126e2012-02-29 18:43:14 -0800787 boolean doEllipsis =
788 (((mMaximumVisibleLineCount == 1 && moreChars) || (firstLine && !moreChars)) &&
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -0700789 ellipsize != TextUtils.TruncateAt.MARQUEE) ||
790 (!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
791 ellipsize == TextUtils.TruncateAt.END);
792 if (doEllipsis) {
793 calculateEllipsis(start, end, widths, widthStart,
794 ellipsisWidth, ellipsize, j,
795 textWidth, paint, forceEllipsis);
796 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800797 }
798
799 mLineCount++;
800 return v;
801 }
802
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800803 private void calculateEllipsis(int lineStart, int lineEnd,
804 float[] widths, int widthStart,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800805 float avail, TextUtils.TruncateAt where,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700806 int line, float textWidth, TextPaint paint,
807 boolean forceEllipsis) {
808 if (textWidth <= avail && !forceEllipsis) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800809 // Everything fits!
810 mLines[mColumns * line + ELLIPSIS_START] = 0;
811 mLines[mColumns * line + ELLIPSIS_COUNT] = 0;
812 return;
813 }
814
Fabrice Di Megliocb332642011-09-23 19:08:04 -0700815 float ellipsisWidth = paint.measureText(
Fabrice Di Meglio8d44fff2012-06-13 15:45:38 -0700816 (where == TextUtils.TruncateAt.END_SMALL) ?
Neil Fullerd29bdb22015-02-06 10:03:08 +0000817 TextUtils.ELLIPSIS_TWO_DOTS : TextUtils.ELLIPSIS_NORMAL, 0, 1);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700818 int ellipsisStart = 0;
819 int ellipsisCount = 0;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800820 int len = lineEnd - lineStart;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800821
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700822 // We only support start ellipsis on a single line
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800823 if (where == TextUtils.TruncateAt.START) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700824 if (mMaximumVisibleLineCount == 1) {
825 float sum = 0;
826 int i;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800827
Keisuke Kuroyanagied2eea12015-04-14 18:18:35 +0900828 for (i = len; i > 0; i--) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700829 float w = widths[i - 1 + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800830
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700831 if (w + sum + ellipsisWidth > avail) {
832 break;
833 }
834
835 sum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800836 }
837
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700838 ellipsisStart = 0;
839 ellipsisCount = i;
840 } else {
841 if (Log.isLoggable(TAG, Log.WARN)) {
842 Log.w(TAG, "Start Ellipsis only supported with one line");
843 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800844 }
Fabrice Di Megliocb332642011-09-23 19:08:04 -0700845 } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE ||
846 where == TextUtils.TruncateAt.END_SMALL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800847 float sum = 0;
848 int i;
849
850 for (i = 0; i < len; i++) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800851 float w = widths[i + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800852
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800853 if (w + sum + ellipsisWidth > avail) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800854 break;
855 }
856
857 sum += w;
858 }
859
860 ellipsisStart = i;
861 ellipsisCount = len - i;
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -0700862 if (forceEllipsis && ellipsisCount == 0 && len > 0) {
863 ellipsisStart = len - 1;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700864 ellipsisCount = 1;
865 }
866 } else {
867 // where = TextUtils.TruncateAt.MIDDLE We only support middle ellipsis on a single line
868 if (mMaximumVisibleLineCount == 1) {
869 float lsum = 0, rsum = 0;
870 int left = 0, right = len;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800871
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700872 float ravail = (avail - ellipsisWidth) / 2;
Raph Levien0e3c5e82014-12-04 13:26:07 -0800873 for (right = len; right > 0; right--) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700874 float w = widths[right - 1 + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800875
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700876 if (w + rsum > ravail) {
877 break;
878 }
879
880 rsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800881 }
882
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700883 float lavail = avail - ellipsisWidth - rsum;
884 for (left = 0; left < right; left++) {
885 float w = widths[left + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800886
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700887 if (w + lsum > lavail) {
888 break;
889 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800890
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700891 lsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800892 }
893
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700894 ellipsisStart = left;
895 ellipsisCount = right - left;
896 } else {
897 if (Log.isLoggable(TAG, Log.WARN)) {
898 Log.w(TAG, "Middle Ellipsis only supported with one line");
899 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800900 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800901 }
902
903 mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
904 mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
905 }
906
Doug Felte8e45f22010-03-29 14:58:40 -0700907 // Override the base class so we can directly access our members,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800908 // rather than relying on member functions.
909 // The logic mirrors that of Layout.getLineForVertical
910 // FIXME: It may be faster to do a linear search for layouts without many lines.
Gilles Debunne66111472010-11-19 11:04:37 -0800911 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800912 public int getLineForVertical(int vertical) {
913 int high = mLineCount;
914 int low = -1;
915 int guess;
916 int[] lines = mLines;
917 while (high - low > 1) {
918 guess = (high + low) >> 1;
919 if (lines[mColumns * guess + TOP] > vertical){
920 high = guess;
921 } else {
922 low = guess;
923 }
924 }
925 if (low < 0) {
926 return 0;
927 } else {
928 return low;
929 }
930 }
931
Gilles Debunne66111472010-11-19 11:04:37 -0800932 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800933 public int getLineCount() {
934 return mLineCount;
935 }
936
Gilles Debunne66111472010-11-19 11:04:37 -0800937 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800938 public int getLineTop(int line) {
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800939 int top = mLines[mColumns * line + TOP];
940 if (mMaximumVisibleLineCount > 0 && line >= mMaximumVisibleLineCount &&
941 line != mLineCount) {
942 top += getBottomPadding();
943 }
944 return top;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800945 }
946
Gilles Debunne66111472010-11-19 11:04:37 -0800947 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800948 public int getLineDescent(int line) {
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800949 int descent = mLines[mColumns * line + DESCENT];
Gilles Debunnef3fa0cd2011-02-03 14:17:05 -0800950 if (mMaximumVisibleLineCount > 0 && line >= mMaximumVisibleLineCount - 1 && // -1 intended
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800951 line != mLineCount) {
952 descent += getBottomPadding();
953 }
954 return descent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800955 }
956
Gilles Debunne66111472010-11-19 11:04:37 -0800957 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800958 public int getLineStart(int line) {
959 return mLines[mColumns * line + START] & START_MASK;
960 }
961
Gilles Debunne66111472010-11-19 11:04:37 -0800962 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800963 public int getParagraphDirection(int line) {
964 return mLines[mColumns * line + DIR] >> DIR_SHIFT;
965 }
966
Gilles Debunne66111472010-11-19 11:04:37 -0800967 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800968 public boolean getLineContainsTab(int line) {
969 return (mLines[mColumns * line + TAB] & TAB_MASK) != 0;
970 }
971
Gilles Debunne66111472010-11-19 11:04:37 -0800972 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800973 public final Directions getLineDirections(int line) {
974 return mLineDirections[line];
975 }
976
Gilles Debunne66111472010-11-19 11:04:37 -0800977 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800978 public int getTopPadding() {
979 return mTopPadding;
980 }
981
Gilles Debunne66111472010-11-19 11:04:37 -0800982 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800983 public int getBottomPadding() {
984 return mBottomPadding;
985 }
986
Raph Levien26d443a2015-03-30 14:18:32 -0700987 /**
988 * @hide
989 */
990 @Override
991 public int getHyphen(int line) {
992 return mLines[mColumns * line + HYPHEN] & 0xff;
993 }
994
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800995 @Override
996 public int getEllipsisCount(int line) {
997 if (mColumns < COLUMNS_ELLIPSIZE) {
998 return 0;
999 }
1000
1001 return mLines[mColumns * line + ELLIPSIS_COUNT];
1002 }
1003
1004 @Override
1005 public int getEllipsisStart(int line) {
1006 if (mColumns < COLUMNS_ELLIPSIZE) {
1007 return 0;
1008 }
1009
1010 return mLines[mColumns * line + ELLIPSIS_START];
1011 }
1012
1013 @Override
1014 public int getEllipsizedWidth() {
1015 return mEllipsizedWidth;
1016 }
1017
Raph Levien70616ec2015-03-04 10:41:30 -08001018 private static native long nNewBuilder();
1019 private static native void nFreeBuilder(long nativePtr);
1020 private static native void nFinishBuilder(long nativePtr);
Raph Levien26d443a2015-03-30 14:18:32 -07001021
1022 /* package */ static native long nLoadHyphenator(String patternData);
1023
1024 private static native void nSetLocale(long nativePtr, String locale, long nativeHyphenator);
Raph Levien70616ec2015-03-04 10:41:30 -08001025
Raph Leviene319d5a2015-04-14 23:51:07 -07001026 private static native void nSetIndents(long nativePtr, int[] indents);
1027
Raph Levienc94f7422015-03-06 19:19:48 -08001028 // Set up paragraph text and settings; done as one big method to minimize jni crossings
1029 private static native void nSetupParagraph(long nativePtr, char[] text, int length,
1030 float firstWidth, int firstWidthLineCount, float restWidth,
1031 int[] variableTabStops, int defaultTabStop, int breakStrategy);
Raph Levien70616ec2015-03-04 10:41:30 -08001032
1033 private static native float nAddStyleRun(long nativePtr, long nativePaint,
1034 long nativeTypeface, int start, int end, boolean isRtl);
1035
1036 private static native void nAddMeasuredRun(long nativePtr,
1037 int start, int end, float[] widths);
1038
1039 private static native void nAddReplacementRun(long nativePtr, int start, int end, float width);
1040
1041 private static native void nGetWidths(long nativePtr, float[] widths);
1042
Anish Athalyec8f9e622014-07-21 15:26:34 -07001043 // populates LineBreaks and returns the number of breaks found
1044 //
1045 // the arrays inside the LineBreaks objects are passed in as well
1046 // to reduce the number of JNI calls in the common case where the
1047 // arrays do not have to be resized
Raph Levienc94f7422015-03-06 19:19:48 -08001048 private static native int nComputeLineBreaks(long nativePtr, LineBreaks recycle,
Raph Levien26d443a2015-03-30 14:18:32 -07001049 int[] recycleBreaks, float[] recycleWidths, int[] recycleFlags, int recycleLength);
Anish Athalye88b5b0b2014-06-24 14:39:43 -07001050
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001051 private int mLineCount;
1052 private int mTopPadding, mBottomPadding;
1053 private int mColumns;
1054 private int mEllipsizedWidth;
1055
Raph Levien26d443a2015-03-30 14:18:32 -07001056 private static final int COLUMNS_NORMAL = 4;
1057 private static final int COLUMNS_ELLIPSIZE = 6;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001058 private static final int START = 0;
1059 private static final int DIR = START;
1060 private static final int TAB = START;
1061 private static final int TOP = 1;
1062 private static final int DESCENT = 2;
Raph Levien26d443a2015-03-30 14:18:32 -07001063 private static final int HYPHEN = 3;
1064 private static final int ELLIPSIS_START = 4;
1065 private static final int ELLIPSIS_COUNT = 5;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001066
1067 private int[] mLines;
1068 private Directions[] mLineDirections;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07001069 private int mMaximumVisibleLineCount = Integer.MAX_VALUE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001070
1071 private static final int START_MASK = 0x1FFFFFFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001072 private static final int DIR_SHIFT = 30;
1073 private static final int TAB_MASK = 0x20000000;
1074
Doug Feltc982f602010-05-25 11:51:40 -07001075 private static final int TAB_INCREMENT = 20; // same as Layout, but that's private
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001076
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001077 private static final char CHAR_NEW_LINE = '\n';
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001078
1079 private static final double EXTRA_ROUNDING = 0.5;
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001080
Anish Athalyec8f9e622014-07-21 15:26:34 -07001081 // This is used to return three arrays from a single JNI call when
1082 // performing line breaking
Deepanshu Gupta70539192014-10-15 15:57:40 -07001083 /*package*/ static class LineBreaks {
Anish Athalyec8f9e622014-07-21 15:26:34 -07001084 private static final int INITIAL_SIZE = 16;
1085 public int[] breaks = new int[INITIAL_SIZE];
1086 public float[] widths = new float[INITIAL_SIZE];
Raph Levien26d443a2015-03-30 14:18:32 -07001087 public int[] flags = new int[INITIAL_SIZE]; // hasTabOrEmoji
Anish Athalyec8f9e622014-07-21 15:26:34 -07001088 // breaks, widths, and flags should all have the same length
1089 }
1090
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001091}