blob: 967e80c4d7b8fe11a8dc984844cc7bc87f042f69 [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;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080026
Doug Feltcb3791202011-07-07 11:57:48 -070027import com.android.internal.util.ArrayUtils;
Adam Lesinski776abc22014-03-07 11:30:59 -050028import com.android.internal.util.GrowingArrayUtils;
Doug Feltcb3791202011-07-07 11:57:48 -070029
Anish Athalyec8f9e622014-07-21 15:26:34 -070030import java.util.Arrays;
Raph Levien4c1f12e2015-03-02 16:29:23 -080031import java.util.Locale;
Anish Athalyec8f9e622014-07-21 15:26:34 -070032
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080033/**
34 * StaticLayout is a Layout for text that will not be edited after it
35 * is laid out. Use {@link DynamicLayout} for text that may change.
36 * <p>This is used by widgets to control text layout. You should not need
37 * to use this class directly unless you are implementing your own widget
38 * or custom display object, or would be tempted to call
Doug Felt4e0c5e52010-03-15 16:56:02 -070039 * {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int,
40 * float, float, android.graphics.Paint)
41 * Canvas.drawText()} directly.</p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080042 */
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -080043public class StaticLayout extends Layout {
44
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -070045 static final String TAG = "StaticLayout";
46
Raph Leviend3ab6922015-03-02 14:30:53 -080047 /**
48 * Builder for static layouts. It would be better if this were a public
49 * API (as it would offer much greater flexibility for adding new options)
50 * but for the time being it's just internal.
51 *
52 * @hide
53 */
54 public final static class Builder {
Raph Levien4c1f12e2015-03-02 16:29:23 -080055 private Builder() {
56 mNativePtr = nNewBuilder();
57 }
58
Raph Leviend3ab6922015-03-02 14:30:53 -080059 static Builder obtain() {
60 Builder b = null;
61 synchronized (sLock) {
62 for (int i = 0; i < sCached.length; i++) {
63 if (sCached[i] != null) {
64 b = sCached[i];
65 sCached[i] = null;
66 break;
67 }
68 }
69 }
70 if (b == null) {
71 b = new Builder();
72 }
73
74 // set default initial values
75 b.mWidth = 0;
76 b.mTextDir = TextDirectionHeuristics.FIRSTSTRONG_LTR;
77 b.mSpacingMult = 1.0f;
78 b.mSpacingAdd = 0.0f;
79 b.mIncludePad = true;
80 b.mEllipsizedWidth = 0;
81 b.mEllipsize = null;
82 b.mMaxLines = Integer.MAX_VALUE;
83
84 b.mMeasuredText = MeasuredText.obtain();
85 return b;
86 }
87
88 static void recycle(Builder b) {
89 b.mPaint = null;
90 b.mText = null;
91 MeasuredText.recycle(b.mMeasuredText);
92 synchronized (sLock) {
93 for (int i = 0; i < sCached.length; i++) {
94 if (sCached[i] == null) {
95 sCached[i] = b;
96 break;
97 }
98 }
99 }
100 }
101
102 // release any expensive state
103 /* package */ void finish() {
Raph Levien4c1f12e2015-03-02 16:29:23 -0800104 nFinishBuilder(mNativePtr);
Raph Leviend3ab6922015-03-02 14:30:53 -0800105 mMeasuredText.finish();
106 }
107
108 public Builder setText(CharSequence source) {
109 return setText(source, 0, source.length());
110 }
111
112 public Builder setText(CharSequence source, int start, int end) {
113 mText = source;
114 mStart = start;
115 mEnd = end;
116 return this;
117 }
118
119 public Builder setPaint(TextPaint paint) {
120 mPaint = paint;
121 return this;
122 }
123
124 public Builder setWidth(int width) {
125 mWidth = width;
126 if (mEllipsize == null) {
127 mEllipsizedWidth = width;
128 }
129 return this;
130 }
131
132 public Builder setTextDir(TextDirectionHeuristic textDir) {
133 mTextDir = textDir;
134 return this;
135 }
136
137 // TODO: combine the following, as they're almost always set together?
138 public Builder setSpacingMult(float spacingMult) {
139 mSpacingMult = spacingMult;
140 return this;
141 }
142
143 public Builder setSpacingAdd(float spacingAdd) {
144 mSpacingAdd = spacingAdd;
145 return this;
146 }
147
148 public Builder setIncludePad(boolean includePad) {
149 mIncludePad = includePad;
150 return this;
151 }
152
153 // TODO: combine the following?
154 public Builder setEllipsizedWidth(int ellipsizedWidth) {
155 mEllipsizedWidth = ellipsizedWidth;
156 return this;
157 }
158
159 public Builder setEllipsize(TextUtils.TruncateAt ellipsize) {
160 mEllipsize = ellipsize;
161 return this;
162 }
163
164 public Builder setMaxLines(int maxLines) {
165 mMaxLines = maxLines;
166 return this;
167 }
168
Raph Levien4c1f12e2015-03-02 16:29:23 -0800169 /* @hide */
170 public void setLocale(Locale locale) {
171 if (!locale.equals(mLocale)) {
172 nBuilderSetLocale(mNativePtr, locale.toLanguageTag());
173 mLocale = locale;
174 }
175 }
176
Raph Leviend3ab6922015-03-02 14:30:53 -0800177 public StaticLayout build() {
178 // TODO: can optimize based on whether ellipsis is needed
179 StaticLayout result = new StaticLayout(mText);
180 result.initFromBuilder(this);
181 recycle(this);
182 return result;
183 }
184
Raph Levien4c1f12e2015-03-02 16:29:23 -0800185 @Override
186 protected void finalize() throws Throwable {
187 try {
188 nFreeBuilder(mNativePtr);
189 } finally {
190 super.finalize();
191 }
192 }
193
194 /* package */ long mNativePtr;
195
Raph Leviend3ab6922015-03-02 14:30:53 -0800196 CharSequence mText;
197 int mStart;
198 int mEnd;
199 TextPaint mPaint;
200 int mWidth;
201 TextDirectionHeuristic mTextDir;
202 float mSpacingMult;
203 float mSpacingAdd;
204 boolean mIncludePad;
205 int mEllipsizedWidth;
206 TextUtils.TruncateAt mEllipsize;
207 int mMaxLines;
208
209 Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
210
211 // This will go away and be subsumed by native builder code
212 MeasuredText mMeasuredText;
213
Raph Levien4c1f12e2015-03-02 16:29:23 -0800214 Locale mLocale;
215
Raph Leviend3ab6922015-03-02 14:30:53 -0800216 private static final Object sLock = new Object();
217 private static final Builder[] sCached = new Builder[3];
218 }
219
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800220 public StaticLayout(CharSequence source, TextPaint paint,
221 int width,
222 Alignment align, float spacingmult, float spacingadd,
223 boolean includepad) {
224 this(source, 0, source.length(), paint, width, align,
225 spacingmult, spacingadd, includepad);
226 }
227
Doug Feltcb3791202011-07-07 11:57:48 -0700228 /**
229 * @hide
230 */
231 public StaticLayout(CharSequence source, TextPaint paint,
232 int width, Alignment align, TextDirectionHeuristic textDir,
233 float spacingmult, float spacingadd,
234 boolean includepad) {
235 this(source, 0, source.length(), paint, width, align, textDir,
236 spacingmult, spacingadd, includepad);
237 }
238
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800239 public StaticLayout(CharSequence source, int bufstart, int bufend,
240 TextPaint paint, int outerwidth,
241 Alignment align,
242 float spacingmult, float spacingadd,
243 boolean includepad) {
244 this(source, bufstart, bufend, paint, outerwidth, align,
245 spacingmult, spacingadd, includepad, null, 0);
246 }
247
Doug Feltcb3791202011-07-07 11:57:48 -0700248 /**
249 * @hide
250 */
251 public StaticLayout(CharSequence source, int bufstart, int bufend,
252 TextPaint paint, int outerwidth,
253 Alignment align, TextDirectionHeuristic textDir,
254 float spacingmult, float spacingadd,
255 boolean includepad) {
256 this(source, bufstart, bufend, paint, outerwidth, align, textDir,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700257 spacingmult, spacingadd, includepad, null, 0, Integer.MAX_VALUE);
Doug Feltcb3791202011-07-07 11:57:48 -0700258}
259
260 public StaticLayout(CharSequence source, int bufstart, int bufend,
261 TextPaint paint, int outerwidth,
262 Alignment align,
263 float spacingmult, float spacingadd,
264 boolean includepad,
265 TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
266 this(source, bufstart, bufend, paint, outerwidth, align,
267 TextDirectionHeuristics.FIRSTSTRONG_LTR,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700268 spacingmult, spacingadd, includepad, ellipsize, ellipsizedWidth, Integer.MAX_VALUE);
Doug Feltcb3791202011-07-07 11:57:48 -0700269 }
270
271 /**
272 * @hide
273 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800274 public StaticLayout(CharSequence source, int bufstart, int bufend,
275 TextPaint paint, int outerwidth,
Doug Feltcb3791202011-07-07 11:57:48 -0700276 Alignment align, TextDirectionHeuristic textDir,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800277 float spacingmult, float spacingadd,
278 boolean includepad,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700279 TextUtils.TruncateAt ellipsize, int ellipsizedWidth, int maxLines) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800280 super((ellipsize == null)
Doug Felt4e0c5e52010-03-15 16:56:02 -0700281 ? source
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800282 : (source instanceof Spanned)
283 ? new SpannedEllipsizer(source)
284 : new Ellipsizer(source),
Doug Feltcb3791202011-07-07 11:57:48 -0700285 paint, outerwidth, align, textDir, spacingmult, spacingadd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800286
Raph Leviend3ab6922015-03-02 14:30:53 -0800287 Builder b = Builder.obtain();
288 b.setText(source, bufstart, bufend)
289 .setPaint(paint)
290 .setWidth(outerwidth)
291 .setTextDir(textDir)
292 .setSpacingMult(spacingmult)
293 .setSpacingAdd(spacingadd)
294 .setIncludePad(includepad)
295 .setEllipsizedWidth(ellipsizedWidth)
296 .setEllipsize(ellipsize)
297 .setMaxLines(maxLines);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800298 /*
299 * This is annoying, but we can't refer to the layout until
300 * superclass construction is finished, and the superclass
301 * constructor wants the reference to the display text.
Doug Felt4e0c5e52010-03-15 16:56:02 -0700302 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800303 * This will break if the superclass constructor ever actually
304 * cares about the content instead of just holding the reference.
305 */
306 if (ellipsize != null) {
307 Ellipsizer e = (Ellipsizer) getText();
308
309 e.mLayout = this;
310 e.mWidth = ellipsizedWidth;
311 e.mMethod = ellipsize;
312 mEllipsizedWidth = ellipsizedWidth;
313
314 mColumns = COLUMNS_ELLIPSIZE;
315 } else {
316 mColumns = COLUMNS_NORMAL;
317 mEllipsizedWidth = outerwidth;
318 }
319
Adam Lesinski776abc22014-03-07 11:30:59 -0500320 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2 * mColumns);
321 mLines = new int[mLineDirections.length];
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700322 mMaximumVisibleLineCount = maxLines;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800323
Raph Leviend3ab6922015-03-02 14:30:53 -0800324 initFromBuilder(b);
Doug Felte8e45f22010-03-29 14:58:40 -0700325
Raph Leviend3ab6922015-03-02 14:30:53 -0800326 Builder.recycle(b);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800327 }
328
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700329 /* package */ StaticLayout(CharSequence text) {
330 super(text, null, 0, null, 0, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800331
332 mColumns = COLUMNS_ELLIPSIZE;
Adam Lesinski776abc22014-03-07 11:30:59 -0500333 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2 * mColumns);
334 mLines = new int[mLineDirections.length];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800335 }
336
Raph Leviend3ab6922015-03-02 14:30:53 -0800337 private void initFromBuilder(Builder b) {
338 generate(b, b.mIncludePad, b.mIncludePad);
339 }
340
341 /* package */ void generate(Builder b, boolean includepad, boolean trackpad) {
342 CharSequence source = b.mText;
343 int bufStart = b.mStart;
344 int bufEnd = b.mEnd;
345 TextPaint paint = b.mPaint;
346 int outerWidth = b.mWidth;
347 TextDirectionHeuristic textDir = b.mTextDir;
348 float spacingmult = b.mSpacingMult;
349 float spacingadd = b.mSpacingAdd;
350 float ellipsizedWidth = b.mEllipsizedWidth;
351 TextUtils.TruncateAt ellipsize = b.mEllipsize;
Raph Levien4c1f12e2015-03-02 16:29:23 -0800352 LineBreaks lineBreaks = new LineBreaks(); // TODO: move to builder to avoid allocation costs
Anish Athalyec8f9e622014-07-21 15:26:34 -0700353 // store span end locations
354 int[] spanEndCache = new int[4];
355 // store fontMetrics per span range
356 // must be a multiple of 4 (and > 0) (store top, bottom, ascent, and descent per range)
357 int[] fmCache = new int[4 * 4];
Raph Levien4c1f12e2015-03-02 16:29:23 -0800358 b.setLocale(paint.getTextLocale()); // TODO: also respect LocaleSpan within the text
Anish Athalye88b5b0b2014-06-24 14:39:43 -0700359
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800360 mLineCount = 0;
361
362 int v = 0;
363 boolean needMultiply = (spacingmult != 1 || spacingadd != 0);
364
Raph Leviend3ab6922015-03-02 14:30:53 -0800365 Paint.FontMetricsInt fm = b.mFontMetricsInt;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800366 int[] chooseHtv = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800367
Raph Leviend3ab6922015-03-02 14:30:53 -0800368 MeasuredText measured = b.mMeasuredText;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800369
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800370 Spanned spanned = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800371 if (source instanceof Spanned)
372 spanned = (Spanned) source;
373
Doug Felte8e45f22010-03-29 14:58:40 -0700374 int paraEnd;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800375 for (int paraStart = bufStart; paraStart <= bufEnd; paraStart = paraEnd) {
376 paraEnd = TextUtils.indexOf(source, CHAR_NEW_LINE, paraStart, bufEnd);
Doug Felte8e45f22010-03-29 14:58:40 -0700377 if (paraEnd < 0)
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800378 paraEnd = bufEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800379 else
Doug Felte8e45f22010-03-29 14:58:40 -0700380 paraEnd++;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800381
Anish Athalyec8f9e622014-07-21 15:26:34 -0700382 int firstWidthLineCount = 1;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800383 int firstWidth = outerWidth;
384 int restWidth = outerWidth;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800385
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800386 LineHeightSpan[] chooseHt = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800387
388 if (spanned != null) {
Eric Fischer74d31ef2010-08-05 15:29:36 -0700389 LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
Doug Felte8e45f22010-03-29 14:58:40 -0700390 LeadingMarginSpan.class);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800391 for (int i = 0; i < sp.length; i++) {
Mark Wagner7b5676e2009-10-16 11:44:23 -0700392 LeadingMarginSpan lms = sp[i];
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800393 firstWidth -= sp[i].getLeadingMargin(true);
394 restWidth -= sp[i].getLeadingMargin(false);
Doug Feltcb3791202011-07-07 11:57:48 -0700395
Doug Feltc982f602010-05-25 11:51:40 -0700396 // LeadingMarginSpan2 is odd. The count affects all
Anish Athalyeab08c6d2014-08-08 12:09:58 -0700397 // leading margin spans, not just this particular one
Doug Feltc982f602010-05-25 11:51:40 -0700398 if (lms instanceof LeadingMarginSpan2) {
399 LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700400 firstWidthLineCount = Math.max(firstWidthLineCount,
401 lms2.getLeadingMarginLineCount());
Mark Wagner7b5676e2009-10-16 11:44:23 -0700402 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800403 }
404
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800405 chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800406
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800407 if (chooseHt.length != 0) {
408 if (chooseHtv == null ||
409 chooseHtv.length < chooseHt.length) {
Adam Lesinski776abc22014-03-07 11:30:59 -0500410 chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800411 }
412
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800413 for (int i = 0; i < chooseHt.length; i++) {
414 int o = spanned.getSpanStart(chooseHt[i]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800415
Doug Felte8e45f22010-03-29 14:58:40 -0700416 if (o < paraStart) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800417 // starts in this layout, before the
418 // current paragraph
419
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800420 chooseHtv[i] = getLineTop(getLineForOffset(o));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800421 } else {
422 // starts in this paragraph
423
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800424 chooseHtv[i] = v;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800425 }
426 }
427 }
428 }
429
Doug Feltcb3791202011-07-07 11:57:48 -0700430 measured.setPara(source, paraStart, paraEnd, textDir);
Doug Felte8e45f22010-03-29 14:58:40 -0700431 char[] chs = measured.mChars;
432 float[] widths = measured.mWidths;
433 byte[] chdirs = measured.mLevels;
434 int dir = measured.mDir;
435 boolean easy = measured.mEasy;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800436
Anish Athalyec8f9e622014-07-21 15:26:34 -0700437 // measurement has to be done before performing line breaking
438 // but we don't want to recompute fontmetrics or span ranges the
439 // second time, so we cache those and then use those stored values
440 int fmCacheCount = 0;
441 int spanEndCacheCount = 0;
Gilles Debunnecd943a72012-06-07 17:54:47 -0700442 for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
Anish Athalyec8f9e622014-07-21 15:26:34 -0700443 if (fmCacheCount * 4 >= fmCache.length) {
444 int[] grow = new int[fmCacheCount * 4 * 2];
445 System.arraycopy(fmCache, 0, grow, 0, fmCacheCount * 4);
446 fmCache = grow;
447 }
448
449 if (spanEndCacheCount >= spanEndCache.length) {
450 int[] grow = new int[spanEndCacheCount * 2];
451 System.arraycopy(spanEndCache, 0, grow, 0, spanEndCacheCount);
452 spanEndCache = grow;
453 }
Doug Felte8e45f22010-03-29 14:58:40 -0700454
Gilles Debunnecd943a72012-06-07 17:54:47 -0700455 if (spanned == null) {
456 spanEnd = paraEnd;
Doug Felt23241882010-06-02 14:41:06 -0700457 int spanLen = spanEnd - spanStart;
Gilles Debunnecd943a72012-06-07 17:54:47 -0700458 measured.addStyleRun(paint, spanLen, fm);
459 } else {
460 spanEnd = spanned.nextSpanTransition(spanStart, paraEnd,
461 MetricAffectingSpan.class);
462 int spanLen = spanEnd - spanStart;
463 MetricAffectingSpan[] spans =
Doug Felt23241882010-06-02 14:41:06 -0700464 spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class);
Gilles Debunnecd943a72012-06-07 17:54:47 -0700465 spans = TextUtils.removeEmptySpans(spans, spanned, MetricAffectingSpan.class);
466 measured.addStyleRun(paint, spans, spanLen, fm);
Doug Felt23241882010-06-02 14:41:06 -0700467 }
468
Anish Athalyec8f9e622014-07-21 15:26:34 -0700469 // the order of storage here (top, bottom, ascent, descent) has to match the code below
470 // where these values are retrieved
471 fmCache[fmCacheCount * 4 + 0] = fm.top;
472 fmCache[fmCacheCount * 4 + 1] = fm.bottom;
473 fmCache[fmCacheCount * 4 + 2] = fm.ascent;
474 fmCache[fmCacheCount * 4 + 3] = fm.descent;
475 fmCacheCount++;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800476
Anish Athalyec8f9e622014-07-21 15:26:34 -0700477 spanEndCache[spanEndCacheCount] = spanEnd;
478 spanEndCacheCount++;
479 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800480
Anish Athalyec8f9e622014-07-21 15:26:34 -0700481 // tab stop locations
482 int[] variableTabStops = null;
483 if (spanned != null) {
484 TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
485 paraEnd, TabStopSpan.class);
486 if (spans.length > 0) {
487 int[] stops = new int[spans.length];
488 for (int i = 0; i < spans.length; i++) {
489 stops[i] = spans[i].getTabStop();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800490 }
Anish Athalyec8f9e622014-07-21 15:26:34 -0700491 Arrays.sort(stops, 0, stops.length);
492 variableTabStops = stops;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800493 }
494 }
495
Raph Levien4c1f12e2015-03-02 16:29:23 -0800496 int breakCount = nComputeLineBreaks(b.mNativePtr, chs, widths, paraEnd - paraStart, firstWidth,
Anish Athalye969e7b92014-07-21 15:34:20 -0700497 firstWidthLineCount, restWidth, variableTabStops, TAB_INCREMENT, false, lineBreaks,
Anish Athalyec8f9e622014-07-21 15:26:34 -0700498 lineBreaks.breaks, lineBreaks.widths, lineBreaks.flags, lineBreaks.breaks.length);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800499
Anish Athalyec8f9e622014-07-21 15:26:34 -0700500 int[] breaks = lineBreaks.breaks;
501 float[] lineWidths = lineBreaks.widths;
502 boolean[] flags = lineBreaks.flags;
503
Anish Athalyec8f9e622014-07-21 15:26:34 -0700504 // here is the offset of the starting character of the line we are currently measuring
505 int here = paraStart;
506
507 int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
508 int fmCacheIndex = 0;
509 int spanEndCacheIndex = 0;
510 int breakIndex = 0;
511 for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
512 // retrieve end of span
513 spanEnd = spanEndCache[spanEndCacheIndex++];
514
515 // retrieve cached metrics, order matches above
516 fm.top = fmCache[fmCacheIndex * 4 + 0];
517 fm.bottom = fmCache[fmCacheIndex * 4 + 1];
518 fm.ascent = fmCache[fmCacheIndex * 4 + 2];
519 fm.descent = fmCache[fmCacheIndex * 4 + 3];
520 fmCacheIndex++;
521
522 if (fm.top < fmTop) {
523 fmTop = fm.top;
524 }
525 if (fm.ascent < fmAscent) {
526 fmAscent = fm.ascent;
527 }
528 if (fm.descent > fmDescent) {
529 fmDescent = fm.descent;
530 }
531 if (fm.bottom > fmBottom) {
532 fmBottom = fm.bottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800533 }
534
Anish Athalyec8f9e622014-07-21 15:26:34 -0700535 // skip breaks ending before current span range
536 while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
537 breakIndex++;
538 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800539
Anish Athalyec8f9e622014-07-21 15:26:34 -0700540 while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
541 int endPos = paraStart + breaks[breakIndex];
542
Raph Levien4c02e832014-12-12 11:17:01 -0800543 boolean moreChars = (endPos < paraEnd); // XXX is this the right way to calculate this?
544
Anish Athalyec8f9e622014-07-21 15:26:34 -0700545 v = out(source, here, endPos,
546 fmAscent, fmDescent, fmTop, fmBottom,
547 v, spacingmult, spacingadd, chooseHt,chooseHtv, fm, flags[breakIndex],
548 needMultiply, chdirs, dir, easy, bufEnd, includepad, trackpad,
549 chs, widths, paraStart, ellipsize, ellipsizedWidth,
Raph Levien4c02e832014-12-12 11:17:01 -0800550 lineWidths[breakIndex], paint, moreChars);
Anish Athalyec8f9e622014-07-21 15:26:34 -0700551
552 if (endPos < spanEnd) {
553 // preserve metrics for current span
554 fmTop = fm.top;
555 fmBottom = fm.bottom;
556 fmAscent = fm.ascent;
557 fmDescent = fm.descent;
558 } else {
559 fmTop = fmBottom = fmAscent = fmDescent = 0;
560 }
561
562 here = endPos;
563 breakIndex++;
564
565 if (mLineCount >= mMaximumVisibleLineCount) {
566 return;
567 }
568 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800569 }
570
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800571 if (paraEnd == bufEnd)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800572 break;
573 }
574
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700575 if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE) &&
Fabrice Di Meglioad0b0512011-10-04 17:21:26 -0700576 mLineCount < mMaximumVisibleLineCount) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800577 // Log.e("text", "output last " + bufEnd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800578
Anish Athalye46514a72014-08-06 18:00:09 -0700579 measured.setPara(source, bufEnd, bufEnd, textDir);
Fabrice Di Meglioe6318892013-06-18 20:03:41 -0700580
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800581 paint.getFontMetricsInt(fm);
582
583 v = out(source,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800584 bufEnd, bufEnd, fm.ascent, fm.descent,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800585 fm.top, fm.bottom,
586 v,
587 spacingmult, spacingadd, null,
588 null, fm, false,
Fabrice Di Meglioe6318892013-06-18 20:03:41 -0700589 needMultiply, measured.mLevels, measured.mDir, measured.mEasy, bufEnd,
Gilles Debunned300e752011-10-17 13:37:36 -0700590 includepad, trackpad, null,
591 null, bufStart, ellipsize,
592 ellipsizedWidth, 0, paint, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800593 }
594 }
595
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800596 private int out(CharSequence text, int start, int end,
597 int above, int below, int top, int bottom, int v,
598 float spacingmult, float spacingadd,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800599 LineHeightSpan[] chooseHt, int[] chooseHtv,
Doug Feltc982f602010-05-25 11:51:40 -0700600 Paint.FontMetricsInt fm, boolean hasTabOrEmoji,
Gilles Debunned300e752011-10-17 13:37:36 -0700601 boolean needMultiply, byte[] chdirs, int dir,
602 boolean easy, int bufEnd, boolean includePad,
603 boolean trackPad, char[] chs,
604 float[] widths, int widthStart, TextUtils.TruncateAt ellipsize,
605 float ellipsisWidth, float textWidth,
606 TextPaint paint, boolean moreChars) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800607 int j = mLineCount;
608 int off = j * mColumns;
609 int want = off + mColumns + TOP;
610 int[] lines = mLines;
611
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800612 if (want >= lines.length) {
Adam Lesinski776abc22014-03-07 11:30:59 -0500613 Directions[] grow2 = ArrayUtils.newUnpaddedArray(
614 Directions.class, GrowingArrayUtils.growSize(want));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800615 System.arraycopy(mLineDirections, 0, grow2, 0,
616 mLineDirections.length);
617 mLineDirections = grow2;
Adam Lesinski776abc22014-03-07 11:30:59 -0500618
619 int[] grow = new int[grow2.length];
620 System.arraycopy(lines, 0, grow, 0, lines.length);
621 mLines = grow;
622 lines = grow;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800623 }
624
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800625 if (chooseHt != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800626 fm.ascent = above;
627 fm.descent = below;
628 fm.top = top;
629 fm.bottom = bottom;
630
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800631 for (int i = 0; i < chooseHt.length; i++) {
632 if (chooseHt[i] instanceof LineHeightSpan.WithDensity) {
633 ((LineHeightSpan.WithDensity) chooseHt[i]).
634 chooseHeight(text, start, end, chooseHtv[i], v, fm, paint);
Eric Fischera9f1dd02009-08-12 15:00:10 -0700635
636 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800637 chooseHt[i].chooseHeight(text, start, end, chooseHtv[i], v, fm);
Eric Fischera9f1dd02009-08-12 15:00:10 -0700638 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800639 }
640
641 above = fm.ascent;
642 below = fm.descent;
643 top = fm.top;
644 bottom = fm.bottom;
645 }
646
Raph Leviend97b0972014-04-24 12:51:35 -0700647 boolean firstLine = (j == 0);
648 boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);
649 boolean lastLine = currentLineIsTheLastVisibleOne || (end == bufEnd);
650
651 if (firstLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800652 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800653 mTopPadding = top - above;
654 }
655
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800656 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800657 above = top;
658 }
659 }
Raph Leviend97b0972014-04-24 12:51:35 -0700660
661 int extra;
662
663 if (lastLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800664 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800665 mBottomPadding = bottom - below;
666 }
667
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800668 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800669 below = bottom;
670 }
671 }
672
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800673
Raph Leviend97b0972014-04-24 12:51:35 -0700674 if (needMultiply && !lastLine) {
Doug Felt10657582010-02-22 11:19:01 -0800675 double ex = (below - above) * (spacingmult - 1) + spacingadd;
676 if (ex >= 0) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800677 extra = (int)(ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -0800678 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800679 extra = -(int)(-ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -0800680 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800681 } else {
682 extra = 0;
683 }
684
685 lines[off + START] = start;
686 lines[off + TOP] = v;
687 lines[off + DESCENT] = below + extra;
688
689 v += (below - above) + extra;
690 lines[off + mColumns + START] = end;
691 lines[off + mColumns + TOP] = v;
692
Doug Feltc982f602010-05-25 11:51:40 -0700693 if (hasTabOrEmoji)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800694 lines[off + TAB] |= TAB_MASK;
695
Doug Felt9f7a4442010-03-01 12:45:56 -0800696 lines[off + DIR] |= dir << DIR_SHIFT;
697 Directions linedirs = DIRS_ALL_LEFT_TO_RIGHT;
698 // easy means all chars < the first RTL, so no emoji, no nothing
Doug Felt4e0c5e52010-03-15 16:56:02 -0700699 // XXX a run with no text or all spaces is easy but might be an empty
Doug Felt9f7a4442010-03-01 12:45:56 -0800700 // RTL paragraph. Make sure easy is false if this is the case.
701 if (easy) {
702 mLineDirections[j] = linedirs;
703 } else {
Gilles Debunnef3fa0cd2011-02-03 14:17:05 -0800704 mLineDirections[j] = AndroidBidi.directions(dir, chdirs, start - widthStart, chs,
705 start - widthStart, end - start);
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800706 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800707
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -0700708 if (ellipsize != null) {
709 // If there is only one line, then do any type of ellipsis except when it is MARQUEE
710 // if there are multiple lines, just allow END ellipsis on the last line
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700711 boolean forceEllipsis = moreChars && (mLineCount + 1 == mMaximumVisibleLineCount);
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -0700712
Fabrice Di Meglio34a126e2012-02-29 18:43:14 -0800713 boolean doEllipsis =
714 (((mMaximumVisibleLineCount == 1 && moreChars) || (firstLine && !moreChars)) &&
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -0700715 ellipsize != TextUtils.TruncateAt.MARQUEE) ||
716 (!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
717 ellipsize == TextUtils.TruncateAt.END);
718 if (doEllipsis) {
719 calculateEllipsis(start, end, widths, widthStart,
720 ellipsisWidth, ellipsize, j,
721 textWidth, paint, forceEllipsis);
722 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800723 }
724
725 mLineCount++;
726 return v;
727 }
728
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800729 private void calculateEllipsis(int lineStart, int lineEnd,
730 float[] widths, int widthStart,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800731 float avail, TextUtils.TruncateAt where,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700732 int line, float textWidth, TextPaint paint,
733 boolean forceEllipsis) {
734 if (textWidth <= avail && !forceEllipsis) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800735 // Everything fits!
736 mLines[mColumns * line + ELLIPSIS_START] = 0;
737 mLines[mColumns * line + ELLIPSIS_COUNT] = 0;
738 return;
739 }
740
Fabrice Di Megliocb332642011-09-23 19:08:04 -0700741 float ellipsisWidth = paint.measureText(
Fabrice Di Meglio8d44fff2012-06-13 15:45:38 -0700742 (where == TextUtils.TruncateAt.END_SMALL) ?
Neil Fullerd29bdb22015-02-06 10:03:08 +0000743 TextUtils.ELLIPSIS_TWO_DOTS : TextUtils.ELLIPSIS_NORMAL, 0, 1);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700744 int ellipsisStart = 0;
745 int ellipsisCount = 0;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800746 int len = lineEnd - lineStart;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800747
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700748 // We only support start ellipsis on a single line
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800749 if (where == TextUtils.TruncateAt.START) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700750 if (mMaximumVisibleLineCount == 1) {
751 float sum = 0;
752 int i;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800753
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700754 for (i = len; i >= 0; i--) {
755 float w = widths[i - 1 + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800756
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700757 if (w + sum + ellipsisWidth > avail) {
758 break;
759 }
760
761 sum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800762 }
763
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700764 ellipsisStart = 0;
765 ellipsisCount = i;
766 } else {
767 if (Log.isLoggable(TAG, Log.WARN)) {
768 Log.w(TAG, "Start Ellipsis only supported with one line");
769 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800770 }
Fabrice Di Megliocb332642011-09-23 19:08:04 -0700771 } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE ||
772 where == TextUtils.TruncateAt.END_SMALL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800773 float sum = 0;
774 int i;
775
776 for (i = 0; i < len; i++) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800777 float w = widths[i + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800778
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800779 if (w + sum + ellipsisWidth > avail) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800780 break;
781 }
782
783 sum += w;
784 }
785
786 ellipsisStart = i;
787 ellipsisCount = len - i;
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -0700788 if (forceEllipsis && ellipsisCount == 0 && len > 0) {
789 ellipsisStart = len - 1;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700790 ellipsisCount = 1;
791 }
792 } else {
793 // where = TextUtils.TruncateAt.MIDDLE We only support middle ellipsis on a single line
794 if (mMaximumVisibleLineCount == 1) {
795 float lsum = 0, rsum = 0;
796 int left = 0, right = len;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800797
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700798 float ravail = (avail - ellipsisWidth) / 2;
Raph Levien0e3c5e82014-12-04 13:26:07 -0800799 for (right = len; right > 0; right--) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700800 float w = widths[right - 1 + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800801
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700802 if (w + rsum > ravail) {
803 break;
804 }
805
806 rsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800807 }
808
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700809 float lavail = avail - ellipsisWidth - rsum;
810 for (left = 0; left < right; left++) {
811 float w = widths[left + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800812
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700813 if (w + lsum > lavail) {
814 break;
815 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800816
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700817 lsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800818 }
819
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700820 ellipsisStart = left;
821 ellipsisCount = right - left;
822 } else {
823 if (Log.isLoggable(TAG, Log.WARN)) {
824 Log.w(TAG, "Middle Ellipsis only supported with one line");
825 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800826 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800827 }
828
829 mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
830 mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
831 }
832
Doug Felte8e45f22010-03-29 14:58:40 -0700833 // Override the base class so we can directly access our members,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800834 // rather than relying on member functions.
835 // The logic mirrors that of Layout.getLineForVertical
836 // FIXME: It may be faster to do a linear search for layouts without many lines.
Gilles Debunne66111472010-11-19 11:04:37 -0800837 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800838 public int getLineForVertical(int vertical) {
839 int high = mLineCount;
840 int low = -1;
841 int guess;
842 int[] lines = mLines;
843 while (high - low > 1) {
844 guess = (high + low) >> 1;
845 if (lines[mColumns * guess + TOP] > vertical){
846 high = guess;
847 } else {
848 low = guess;
849 }
850 }
851 if (low < 0) {
852 return 0;
853 } else {
854 return low;
855 }
856 }
857
Gilles Debunne66111472010-11-19 11:04:37 -0800858 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800859 public int getLineCount() {
860 return mLineCount;
861 }
862
Gilles Debunne66111472010-11-19 11:04:37 -0800863 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800864 public int getLineTop(int line) {
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800865 int top = mLines[mColumns * line + TOP];
866 if (mMaximumVisibleLineCount > 0 && line >= mMaximumVisibleLineCount &&
867 line != mLineCount) {
868 top += getBottomPadding();
869 }
870 return top;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800871 }
872
Gilles Debunne66111472010-11-19 11:04:37 -0800873 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800874 public int getLineDescent(int line) {
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800875 int descent = mLines[mColumns * line + DESCENT];
Gilles Debunnef3fa0cd2011-02-03 14:17:05 -0800876 if (mMaximumVisibleLineCount > 0 && line >= mMaximumVisibleLineCount - 1 && // -1 intended
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800877 line != mLineCount) {
878 descent += getBottomPadding();
879 }
880 return descent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800881 }
882
Gilles Debunne66111472010-11-19 11:04:37 -0800883 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800884 public int getLineStart(int line) {
885 return mLines[mColumns * line + START] & START_MASK;
886 }
887
Gilles Debunne66111472010-11-19 11:04:37 -0800888 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800889 public int getParagraphDirection(int line) {
890 return mLines[mColumns * line + DIR] >> DIR_SHIFT;
891 }
892
Gilles Debunne66111472010-11-19 11:04:37 -0800893 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800894 public boolean getLineContainsTab(int line) {
895 return (mLines[mColumns * line + TAB] & TAB_MASK) != 0;
896 }
897
Gilles Debunne66111472010-11-19 11:04:37 -0800898 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800899 public final Directions getLineDirections(int line) {
900 return mLineDirections[line];
901 }
902
Gilles Debunne66111472010-11-19 11:04:37 -0800903 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800904 public int getTopPadding() {
905 return mTopPadding;
906 }
907
Gilles Debunne66111472010-11-19 11:04:37 -0800908 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800909 public int getBottomPadding() {
910 return mBottomPadding;
911 }
912
913 @Override
914 public int getEllipsisCount(int line) {
915 if (mColumns < COLUMNS_ELLIPSIZE) {
916 return 0;
917 }
918
919 return mLines[mColumns * line + ELLIPSIS_COUNT];
920 }
921
922 @Override
923 public int getEllipsisStart(int line) {
924 if (mColumns < COLUMNS_ELLIPSIZE) {
925 return 0;
926 }
927
928 return mLines[mColumns * line + ELLIPSIS_START];
929 }
930
931 @Override
932 public int getEllipsizedWidth() {
933 return mEllipsizedWidth;
934 }
935
Anish Athalyec8f9e622014-07-21 15:26:34 -0700936 // populates LineBreaks and returns the number of breaks found
937 //
938 // the arrays inside the LineBreaks objects are passed in as well
939 // to reduce the number of JNI calls in the common case where the
940 // arrays do not have to be resized
Raph Levien4c1f12e2015-03-02 16:29:23 -0800941 private static native int nComputeLineBreaks(long nativePtr, char[] text, float[] widths,
Anish Athalyec8f9e622014-07-21 15:26:34 -0700942 int length, float firstWidth, int firstWidthLineCount, float restWidth,
Anish Athalye969e7b92014-07-21 15:34:20 -0700943 int[] variableTabStops, int defaultTabStop, boolean optimize, LineBreaks recycle,
Anish Athalyec8f9e622014-07-21 15:26:34 -0700944 int[] recycleBreaks, float[] recycleWidths, boolean[] recycleFlags, int recycleLength);
Anish Athalye88b5b0b2014-06-24 14:39:43 -0700945
Raph Levien4c1f12e2015-03-02 16:29:23 -0800946 private static native long nNewBuilder();
947 private static native void nFreeBuilder(long nativePtr);
948 private static native void nFinishBuilder(long nativePtr);
949 private static native void nBuilderSetLocale(long nativePtr, String locale);
950
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800951 private int mLineCount;
952 private int mTopPadding, mBottomPadding;
953 private int mColumns;
954 private int mEllipsizedWidth;
955
956 private static final int COLUMNS_NORMAL = 3;
957 private static final int COLUMNS_ELLIPSIZE = 5;
958 private static final int START = 0;
959 private static final int DIR = START;
960 private static final int TAB = START;
961 private static final int TOP = 1;
962 private static final int DESCENT = 2;
963 private static final int ELLIPSIS_START = 3;
964 private static final int ELLIPSIS_COUNT = 4;
965
966 private int[] mLines;
967 private Directions[] mLineDirections;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700968 private int mMaximumVisibleLineCount = Integer.MAX_VALUE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800969
970 private static final int START_MASK = 0x1FFFFFFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800971 private static final int DIR_SHIFT = 30;
972 private static final int TAB_MASK = 0x20000000;
973
Doug Feltc982f602010-05-25 11:51:40 -0700974 private static final int TAB_INCREMENT = 20; // same as Layout, but that's private
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800975
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800976 private static final char CHAR_NEW_LINE = '\n';
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800977
978 private static final double EXTRA_ROUNDING = 0.5;
Fabrice Di Megliocb332642011-09-23 19:08:04 -0700979
Anish Athalyec8f9e622014-07-21 15:26:34 -0700980 // This is used to return three arrays from a single JNI call when
981 // performing line breaking
Deepanshu Gupta70539192014-10-15 15:57:40 -0700982 /*package*/ static class LineBreaks {
Anish Athalyec8f9e622014-07-21 15:26:34 -0700983 private static final int INITIAL_SIZE = 16;
984 public int[] breaks = new int[INITIAL_SIZE];
985 public float[] widths = new float[INITIAL_SIZE];
986 public boolean[] flags = new boolean[INITIAL_SIZE]; // hasTabOrEmoji
987 // breaks, widths, and flags should all have the same length
988 }
989
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800990}