blob: d1caa43b330154be31d26198a834ddb87cb8d1ff [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;
31
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080032/**
33 * StaticLayout is a Layout for text that will not be edited after it
34 * is laid out. Use {@link DynamicLayout} for text that may change.
35 * <p>This is used by widgets to control text layout. You should not need
36 * to use this class directly unless you are implementing your own widget
37 * or custom display object, or would be tempted to call
Doug Felt4e0c5e52010-03-15 16:56:02 -070038 * {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int,
39 * float, float, android.graphics.Paint)
40 * Canvas.drawText()} directly.</p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080041 */
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -080042public class StaticLayout extends Layout {
43
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -070044 static final String TAG = "StaticLayout";
45
Raph Leviend3ab6922015-03-02 14:30:53 -080046 /**
47 * Builder for static layouts. It would be better if this were a public
48 * API (as it would offer much greater flexibility for adding new options)
49 * but for the time being it's just internal.
50 *
51 * @hide
52 */
53 public final static class Builder {
54 static Builder obtain() {
55 Builder b = null;
56 synchronized (sLock) {
57 for (int i = 0; i < sCached.length; i++) {
58 if (sCached[i] != null) {
59 b = sCached[i];
60 sCached[i] = null;
61 break;
62 }
63 }
64 }
65 if (b == null) {
66 b = new Builder();
67 }
68
69 // set default initial values
70 b.mWidth = 0;
71 b.mTextDir = TextDirectionHeuristics.FIRSTSTRONG_LTR;
72 b.mSpacingMult = 1.0f;
73 b.mSpacingAdd = 0.0f;
74 b.mIncludePad = true;
75 b.mEllipsizedWidth = 0;
76 b.mEllipsize = null;
77 b.mMaxLines = Integer.MAX_VALUE;
78
79 b.mMeasuredText = MeasuredText.obtain();
80 return b;
81 }
82
83 static void recycle(Builder b) {
84 b.mPaint = null;
85 b.mText = null;
86 MeasuredText.recycle(b.mMeasuredText);
87 synchronized (sLock) {
88 for (int i = 0; i < sCached.length; i++) {
89 if (sCached[i] == null) {
90 sCached[i] = b;
91 break;
92 }
93 }
94 }
95 }
96
97 // release any expensive state
98 /* package */ void finish() {
99 mMeasuredText.finish();
100 }
101
102 public Builder setText(CharSequence source) {
103 return setText(source, 0, source.length());
104 }
105
106 public Builder setText(CharSequence source, int start, int end) {
107 mText = source;
108 mStart = start;
109 mEnd = end;
110 return this;
111 }
112
113 public Builder setPaint(TextPaint paint) {
114 mPaint = paint;
115 return this;
116 }
117
118 public Builder setWidth(int width) {
119 mWidth = width;
120 if (mEllipsize == null) {
121 mEllipsizedWidth = width;
122 }
123 return this;
124 }
125
126 public Builder setTextDir(TextDirectionHeuristic textDir) {
127 mTextDir = textDir;
128 return this;
129 }
130
131 // TODO: combine the following, as they're almost always set together?
132 public Builder setSpacingMult(float spacingMult) {
133 mSpacingMult = spacingMult;
134 return this;
135 }
136
137 public Builder setSpacingAdd(float spacingAdd) {
138 mSpacingAdd = spacingAdd;
139 return this;
140 }
141
142 public Builder setIncludePad(boolean includePad) {
143 mIncludePad = includePad;
144 return this;
145 }
146
147 // TODO: combine the following?
148 public Builder setEllipsizedWidth(int ellipsizedWidth) {
149 mEllipsizedWidth = ellipsizedWidth;
150 return this;
151 }
152
153 public Builder setEllipsize(TextUtils.TruncateAt ellipsize) {
154 mEllipsize = ellipsize;
155 return this;
156 }
157
158 public Builder setMaxLines(int maxLines) {
159 mMaxLines = maxLines;
160 return this;
161 }
162
163 public StaticLayout build() {
164 // TODO: can optimize based on whether ellipsis is needed
165 StaticLayout result = new StaticLayout(mText);
166 result.initFromBuilder(this);
167 recycle(this);
168 return result;
169 }
170
171 CharSequence mText;
172 int mStart;
173 int mEnd;
174 TextPaint mPaint;
175 int mWidth;
176 TextDirectionHeuristic mTextDir;
177 float mSpacingMult;
178 float mSpacingAdd;
179 boolean mIncludePad;
180 int mEllipsizedWidth;
181 TextUtils.TruncateAt mEllipsize;
182 int mMaxLines;
183
184 Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
185
186 // This will go away and be subsumed by native builder code
187 MeasuredText mMeasuredText;
188
189 private static final Object sLock = new Object();
190 private static final Builder[] sCached = new Builder[3];
191 }
192
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800193 public StaticLayout(CharSequence source, TextPaint paint,
194 int width,
195 Alignment align, float spacingmult, float spacingadd,
196 boolean includepad) {
197 this(source, 0, source.length(), paint, width, align,
198 spacingmult, spacingadd, includepad);
199 }
200
Doug Feltcb3791202011-07-07 11:57:48 -0700201 /**
202 * @hide
203 */
204 public StaticLayout(CharSequence source, TextPaint paint,
205 int width, Alignment align, TextDirectionHeuristic textDir,
206 float spacingmult, float spacingadd,
207 boolean includepad) {
208 this(source, 0, source.length(), paint, width, align, textDir,
209 spacingmult, spacingadd, includepad);
210 }
211
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800212 public StaticLayout(CharSequence source, int bufstart, int bufend,
213 TextPaint paint, int outerwidth,
214 Alignment align,
215 float spacingmult, float spacingadd,
216 boolean includepad) {
217 this(source, bufstart, bufend, paint, outerwidth, align,
218 spacingmult, spacingadd, includepad, null, 0);
219 }
220
Doug Feltcb3791202011-07-07 11:57:48 -0700221 /**
222 * @hide
223 */
224 public StaticLayout(CharSequence source, int bufstart, int bufend,
225 TextPaint paint, int outerwidth,
226 Alignment align, TextDirectionHeuristic textDir,
227 float spacingmult, float spacingadd,
228 boolean includepad) {
229 this(source, bufstart, bufend, paint, outerwidth, align, textDir,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700230 spacingmult, spacingadd, includepad, null, 0, Integer.MAX_VALUE);
Doug Feltcb3791202011-07-07 11:57:48 -0700231}
232
233 public StaticLayout(CharSequence source, int bufstart, int bufend,
234 TextPaint paint, int outerwidth,
235 Alignment align,
236 float spacingmult, float spacingadd,
237 boolean includepad,
238 TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
239 this(source, bufstart, bufend, paint, outerwidth, align,
240 TextDirectionHeuristics.FIRSTSTRONG_LTR,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700241 spacingmult, spacingadd, includepad, ellipsize, ellipsizedWidth, Integer.MAX_VALUE);
Doug Feltcb3791202011-07-07 11:57:48 -0700242 }
243
244 /**
245 * @hide
246 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800247 public StaticLayout(CharSequence source, int bufstart, int bufend,
248 TextPaint paint, int outerwidth,
Doug Feltcb3791202011-07-07 11:57:48 -0700249 Alignment align, TextDirectionHeuristic textDir,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800250 float spacingmult, float spacingadd,
251 boolean includepad,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700252 TextUtils.TruncateAt ellipsize, int ellipsizedWidth, int maxLines) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800253 super((ellipsize == null)
Doug Felt4e0c5e52010-03-15 16:56:02 -0700254 ? source
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800255 : (source instanceof Spanned)
256 ? new SpannedEllipsizer(source)
257 : new Ellipsizer(source),
Doug Feltcb3791202011-07-07 11:57:48 -0700258 paint, outerwidth, align, textDir, spacingmult, spacingadd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800259
Raph Leviend3ab6922015-03-02 14:30:53 -0800260 Builder b = Builder.obtain();
261 b.setText(source, bufstart, bufend)
262 .setPaint(paint)
263 .setWidth(outerwidth)
264 .setTextDir(textDir)
265 .setSpacingMult(spacingmult)
266 .setSpacingAdd(spacingadd)
267 .setIncludePad(includepad)
268 .setEllipsizedWidth(ellipsizedWidth)
269 .setEllipsize(ellipsize)
270 .setMaxLines(maxLines);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800271 /*
272 * This is annoying, but we can't refer to the layout until
273 * superclass construction is finished, and the superclass
274 * constructor wants the reference to the display text.
Doug Felt4e0c5e52010-03-15 16:56:02 -0700275 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800276 * This will break if the superclass constructor ever actually
277 * cares about the content instead of just holding the reference.
278 */
279 if (ellipsize != null) {
280 Ellipsizer e = (Ellipsizer) getText();
281
282 e.mLayout = this;
283 e.mWidth = ellipsizedWidth;
284 e.mMethod = ellipsize;
285 mEllipsizedWidth = ellipsizedWidth;
286
287 mColumns = COLUMNS_ELLIPSIZE;
288 } else {
289 mColumns = COLUMNS_NORMAL;
290 mEllipsizedWidth = outerwidth;
291 }
292
Adam Lesinski776abc22014-03-07 11:30:59 -0500293 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2 * mColumns);
294 mLines = new int[mLineDirections.length];
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700295 mMaximumVisibleLineCount = maxLines;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800296
Raph Leviend3ab6922015-03-02 14:30:53 -0800297 initFromBuilder(b);
Doug Felte8e45f22010-03-29 14:58:40 -0700298
Raph Leviend3ab6922015-03-02 14:30:53 -0800299 Builder.recycle(b);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800300 }
301
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700302 /* package */ StaticLayout(CharSequence text) {
303 super(text, null, 0, null, 0, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800304
305 mColumns = COLUMNS_ELLIPSIZE;
Adam Lesinski776abc22014-03-07 11:30:59 -0500306 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2 * mColumns);
307 mLines = new int[mLineDirections.length];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800308 }
309
Raph Leviend3ab6922015-03-02 14:30:53 -0800310 private void initFromBuilder(Builder b) {
311 generate(b, b.mIncludePad, b.mIncludePad);
312 }
313
314 /* package */ void generate(Builder b, boolean includepad, boolean trackpad) {
315 CharSequence source = b.mText;
316 int bufStart = b.mStart;
317 int bufEnd = b.mEnd;
318 TextPaint paint = b.mPaint;
319 int outerWidth = b.mWidth;
320 TextDirectionHeuristic textDir = b.mTextDir;
321 float spacingmult = b.mSpacingMult;
322 float spacingadd = b.mSpacingAdd;
323 float ellipsizedWidth = b.mEllipsizedWidth;
324 TextUtils.TruncateAt ellipsize = b.mEllipsize;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700325 LineBreaks lineBreaks = new LineBreaks();
326 // store span end locations
327 int[] spanEndCache = new int[4];
328 // store fontMetrics per span range
329 // must be a multiple of 4 (and > 0) (store top, bottom, ascent, and descent per range)
330 int[] fmCache = new int[4 * 4];
Anish Athalye88b5b0b2014-06-24 14:39:43 -0700331 final String localeLanguageTag = paint.getTextLocale().toLanguageTag();
332
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800333 mLineCount = 0;
334
335 int v = 0;
336 boolean needMultiply = (spacingmult != 1 || spacingadd != 0);
337
Raph Leviend3ab6922015-03-02 14:30:53 -0800338 Paint.FontMetricsInt fm = b.mFontMetricsInt;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800339 int[] chooseHtv = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800340
Raph Leviend3ab6922015-03-02 14:30:53 -0800341 MeasuredText measured = b.mMeasuredText;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800342
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800343 Spanned spanned = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800344 if (source instanceof Spanned)
345 spanned = (Spanned) source;
346
Doug Felte8e45f22010-03-29 14:58:40 -0700347 int paraEnd;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800348 for (int paraStart = bufStart; paraStart <= bufEnd; paraStart = paraEnd) {
349 paraEnd = TextUtils.indexOf(source, CHAR_NEW_LINE, paraStart, bufEnd);
Doug Felte8e45f22010-03-29 14:58:40 -0700350 if (paraEnd < 0)
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800351 paraEnd = bufEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800352 else
Doug Felte8e45f22010-03-29 14:58:40 -0700353 paraEnd++;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800354
Anish Athalyec8f9e622014-07-21 15:26:34 -0700355 int firstWidthLineCount = 1;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800356 int firstWidth = outerWidth;
357 int restWidth = outerWidth;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800358
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800359 LineHeightSpan[] chooseHt = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800360
361 if (spanned != null) {
Eric Fischer74d31ef2010-08-05 15:29:36 -0700362 LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
Doug Felte8e45f22010-03-29 14:58:40 -0700363 LeadingMarginSpan.class);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800364 for (int i = 0; i < sp.length; i++) {
Mark Wagner7b5676e2009-10-16 11:44:23 -0700365 LeadingMarginSpan lms = sp[i];
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800366 firstWidth -= sp[i].getLeadingMargin(true);
367 restWidth -= sp[i].getLeadingMargin(false);
Doug Feltcb3791202011-07-07 11:57:48 -0700368
Doug Feltc982f602010-05-25 11:51:40 -0700369 // LeadingMarginSpan2 is odd. The count affects all
Anish Athalyeab08c6d2014-08-08 12:09:58 -0700370 // leading margin spans, not just this particular one
Doug Feltc982f602010-05-25 11:51:40 -0700371 if (lms instanceof LeadingMarginSpan2) {
372 LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700373 firstWidthLineCount = Math.max(firstWidthLineCount,
374 lms2.getLeadingMarginLineCount());
Mark Wagner7b5676e2009-10-16 11:44:23 -0700375 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800376 }
377
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800378 chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800379
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800380 if (chooseHt.length != 0) {
381 if (chooseHtv == null ||
382 chooseHtv.length < chooseHt.length) {
Adam Lesinski776abc22014-03-07 11:30:59 -0500383 chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800384 }
385
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800386 for (int i = 0; i < chooseHt.length; i++) {
387 int o = spanned.getSpanStart(chooseHt[i]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800388
Doug Felte8e45f22010-03-29 14:58:40 -0700389 if (o < paraStart) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800390 // starts in this layout, before the
391 // current paragraph
392
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800393 chooseHtv[i] = getLineTop(getLineForOffset(o));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800394 } else {
395 // starts in this paragraph
396
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800397 chooseHtv[i] = v;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800398 }
399 }
400 }
401 }
402
Doug Feltcb3791202011-07-07 11:57:48 -0700403 measured.setPara(source, paraStart, paraEnd, textDir);
Doug Felte8e45f22010-03-29 14:58:40 -0700404 char[] chs = measured.mChars;
405 float[] widths = measured.mWidths;
406 byte[] chdirs = measured.mLevels;
407 int dir = measured.mDir;
408 boolean easy = measured.mEasy;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800409
Anish Athalyec8f9e622014-07-21 15:26:34 -0700410 // measurement has to be done before performing line breaking
411 // but we don't want to recompute fontmetrics or span ranges the
412 // second time, so we cache those and then use those stored values
413 int fmCacheCount = 0;
414 int spanEndCacheCount = 0;
Gilles Debunnecd943a72012-06-07 17:54:47 -0700415 for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
Anish Athalyec8f9e622014-07-21 15:26:34 -0700416 if (fmCacheCount * 4 >= fmCache.length) {
417 int[] grow = new int[fmCacheCount * 4 * 2];
418 System.arraycopy(fmCache, 0, grow, 0, fmCacheCount * 4);
419 fmCache = grow;
420 }
421
422 if (spanEndCacheCount >= spanEndCache.length) {
423 int[] grow = new int[spanEndCacheCount * 2];
424 System.arraycopy(spanEndCache, 0, grow, 0, spanEndCacheCount);
425 spanEndCache = grow;
426 }
Doug Felte8e45f22010-03-29 14:58:40 -0700427
Gilles Debunnecd943a72012-06-07 17:54:47 -0700428 if (spanned == null) {
429 spanEnd = paraEnd;
Doug Felt23241882010-06-02 14:41:06 -0700430 int spanLen = spanEnd - spanStart;
Gilles Debunnecd943a72012-06-07 17:54:47 -0700431 measured.addStyleRun(paint, spanLen, fm);
432 } else {
433 spanEnd = spanned.nextSpanTransition(spanStart, paraEnd,
434 MetricAffectingSpan.class);
435 int spanLen = spanEnd - spanStart;
436 MetricAffectingSpan[] spans =
Doug Felt23241882010-06-02 14:41:06 -0700437 spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class);
Gilles Debunnecd943a72012-06-07 17:54:47 -0700438 spans = TextUtils.removeEmptySpans(spans, spanned, MetricAffectingSpan.class);
439 measured.addStyleRun(paint, spans, spanLen, fm);
Doug Felt23241882010-06-02 14:41:06 -0700440 }
441
Anish Athalyec8f9e622014-07-21 15:26:34 -0700442 // the order of storage here (top, bottom, ascent, descent) has to match the code below
443 // where these values are retrieved
444 fmCache[fmCacheCount * 4 + 0] = fm.top;
445 fmCache[fmCacheCount * 4 + 1] = fm.bottom;
446 fmCache[fmCacheCount * 4 + 2] = fm.ascent;
447 fmCache[fmCacheCount * 4 + 3] = fm.descent;
448 fmCacheCount++;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800449
Anish Athalyec8f9e622014-07-21 15:26:34 -0700450 spanEndCache[spanEndCacheCount] = spanEnd;
451 spanEndCacheCount++;
452 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800453
Anish Athalyec8f9e622014-07-21 15:26:34 -0700454 // tab stop locations
455 int[] variableTabStops = null;
456 if (spanned != null) {
457 TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
458 paraEnd, TabStopSpan.class);
459 if (spans.length > 0) {
460 int[] stops = new int[spans.length];
461 for (int i = 0; i < spans.length; i++) {
462 stops[i] = spans[i].getTabStop();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800463 }
Anish Athalyec8f9e622014-07-21 15:26:34 -0700464 Arrays.sort(stops, 0, stops.length);
465 variableTabStops = stops;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800466 }
467 }
468
Anish Athalyec8f9e622014-07-21 15:26:34 -0700469 int breakCount = nComputeLineBreaks(localeLanguageTag, chs, widths, paraEnd - paraStart, firstWidth,
Anish Athalye969e7b92014-07-21 15:34:20 -0700470 firstWidthLineCount, restWidth, variableTabStops, TAB_INCREMENT, false, lineBreaks,
Anish Athalyec8f9e622014-07-21 15:26:34 -0700471 lineBreaks.breaks, lineBreaks.widths, lineBreaks.flags, lineBreaks.breaks.length);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800472
Anish Athalyec8f9e622014-07-21 15:26:34 -0700473 int[] breaks = lineBreaks.breaks;
474 float[] lineWidths = lineBreaks.widths;
475 boolean[] flags = lineBreaks.flags;
476
Anish Athalyec8f9e622014-07-21 15:26:34 -0700477 // here is the offset of the starting character of the line we are currently measuring
478 int here = paraStart;
479
480 int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
481 int fmCacheIndex = 0;
482 int spanEndCacheIndex = 0;
483 int breakIndex = 0;
484 for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
485 // retrieve end of span
486 spanEnd = spanEndCache[spanEndCacheIndex++];
487
488 // retrieve cached metrics, order matches above
489 fm.top = fmCache[fmCacheIndex * 4 + 0];
490 fm.bottom = fmCache[fmCacheIndex * 4 + 1];
491 fm.ascent = fmCache[fmCacheIndex * 4 + 2];
492 fm.descent = fmCache[fmCacheIndex * 4 + 3];
493 fmCacheIndex++;
494
495 if (fm.top < fmTop) {
496 fmTop = fm.top;
497 }
498 if (fm.ascent < fmAscent) {
499 fmAscent = fm.ascent;
500 }
501 if (fm.descent > fmDescent) {
502 fmDescent = fm.descent;
503 }
504 if (fm.bottom > fmBottom) {
505 fmBottom = fm.bottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800506 }
507
Anish Athalyec8f9e622014-07-21 15:26:34 -0700508 // skip breaks ending before current span range
509 while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
510 breakIndex++;
511 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800512
Anish Athalyec8f9e622014-07-21 15:26:34 -0700513 while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
514 int endPos = paraStart + breaks[breakIndex];
515
Raph Levien4c02e832014-12-12 11:17:01 -0800516 boolean moreChars = (endPos < paraEnd); // XXX is this the right way to calculate this?
517
Anish Athalyec8f9e622014-07-21 15:26:34 -0700518 v = out(source, here, endPos,
519 fmAscent, fmDescent, fmTop, fmBottom,
520 v, spacingmult, spacingadd, chooseHt,chooseHtv, fm, flags[breakIndex],
521 needMultiply, chdirs, dir, easy, bufEnd, includepad, trackpad,
522 chs, widths, paraStart, ellipsize, ellipsizedWidth,
Raph Levien4c02e832014-12-12 11:17:01 -0800523 lineWidths[breakIndex], paint, moreChars);
Anish Athalyec8f9e622014-07-21 15:26:34 -0700524
525 if (endPos < spanEnd) {
526 // preserve metrics for current span
527 fmTop = fm.top;
528 fmBottom = fm.bottom;
529 fmAscent = fm.ascent;
530 fmDescent = fm.descent;
531 } else {
532 fmTop = fmBottom = fmAscent = fmDescent = 0;
533 }
534
535 here = endPos;
536 breakIndex++;
537
538 if (mLineCount >= mMaximumVisibleLineCount) {
539 return;
540 }
541 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800542 }
543
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800544 if (paraEnd == bufEnd)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800545 break;
546 }
547
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700548 if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE) &&
Fabrice Di Meglioad0b0512011-10-04 17:21:26 -0700549 mLineCount < mMaximumVisibleLineCount) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800550 // Log.e("text", "output last " + bufEnd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800551
Anish Athalye46514a72014-08-06 18:00:09 -0700552 measured.setPara(source, bufEnd, bufEnd, textDir);
Fabrice Di Meglioe6318892013-06-18 20:03:41 -0700553
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800554 paint.getFontMetricsInt(fm);
555
556 v = out(source,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800557 bufEnd, bufEnd, fm.ascent, fm.descent,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800558 fm.top, fm.bottom,
559 v,
560 spacingmult, spacingadd, null,
561 null, fm, false,
Fabrice Di Meglioe6318892013-06-18 20:03:41 -0700562 needMultiply, measured.mLevels, measured.mDir, measured.mEasy, bufEnd,
Gilles Debunned300e752011-10-17 13:37:36 -0700563 includepad, trackpad, null,
564 null, bufStart, ellipsize,
565 ellipsizedWidth, 0, paint, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800566 }
567 }
568
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800569 private int out(CharSequence text, int start, int end,
570 int above, int below, int top, int bottom, int v,
571 float spacingmult, float spacingadd,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800572 LineHeightSpan[] chooseHt, int[] chooseHtv,
Doug Feltc982f602010-05-25 11:51:40 -0700573 Paint.FontMetricsInt fm, boolean hasTabOrEmoji,
Gilles Debunned300e752011-10-17 13:37:36 -0700574 boolean needMultiply, byte[] chdirs, int dir,
575 boolean easy, int bufEnd, boolean includePad,
576 boolean trackPad, char[] chs,
577 float[] widths, int widthStart, TextUtils.TruncateAt ellipsize,
578 float ellipsisWidth, float textWidth,
579 TextPaint paint, boolean moreChars) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800580 int j = mLineCount;
581 int off = j * mColumns;
582 int want = off + mColumns + TOP;
583 int[] lines = mLines;
584
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800585 if (want >= lines.length) {
Adam Lesinski776abc22014-03-07 11:30:59 -0500586 Directions[] grow2 = ArrayUtils.newUnpaddedArray(
587 Directions.class, GrowingArrayUtils.growSize(want));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800588 System.arraycopy(mLineDirections, 0, grow2, 0,
589 mLineDirections.length);
590 mLineDirections = grow2;
Adam Lesinski776abc22014-03-07 11:30:59 -0500591
592 int[] grow = new int[grow2.length];
593 System.arraycopy(lines, 0, grow, 0, lines.length);
594 mLines = grow;
595 lines = grow;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800596 }
597
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800598 if (chooseHt != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800599 fm.ascent = above;
600 fm.descent = below;
601 fm.top = top;
602 fm.bottom = bottom;
603
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800604 for (int i = 0; i < chooseHt.length; i++) {
605 if (chooseHt[i] instanceof LineHeightSpan.WithDensity) {
606 ((LineHeightSpan.WithDensity) chooseHt[i]).
607 chooseHeight(text, start, end, chooseHtv[i], v, fm, paint);
Eric Fischera9f1dd02009-08-12 15:00:10 -0700608
609 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800610 chooseHt[i].chooseHeight(text, start, end, chooseHtv[i], v, fm);
Eric Fischera9f1dd02009-08-12 15:00:10 -0700611 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800612 }
613
614 above = fm.ascent;
615 below = fm.descent;
616 top = fm.top;
617 bottom = fm.bottom;
618 }
619
Raph Leviend97b0972014-04-24 12:51:35 -0700620 boolean firstLine = (j == 0);
621 boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);
622 boolean lastLine = currentLineIsTheLastVisibleOne || (end == bufEnd);
623
624 if (firstLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800625 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800626 mTopPadding = top - above;
627 }
628
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800629 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800630 above = top;
631 }
632 }
Raph Leviend97b0972014-04-24 12:51:35 -0700633
634 int extra;
635
636 if (lastLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800637 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800638 mBottomPadding = bottom - below;
639 }
640
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800641 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800642 below = bottom;
643 }
644 }
645
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800646
Raph Leviend97b0972014-04-24 12:51:35 -0700647 if (needMultiply && !lastLine) {
Doug Felt10657582010-02-22 11:19:01 -0800648 double ex = (below - above) * (spacingmult - 1) + spacingadd;
649 if (ex >= 0) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800650 extra = (int)(ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -0800651 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800652 extra = -(int)(-ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -0800653 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800654 } else {
655 extra = 0;
656 }
657
658 lines[off + START] = start;
659 lines[off + TOP] = v;
660 lines[off + DESCENT] = below + extra;
661
662 v += (below - above) + extra;
663 lines[off + mColumns + START] = end;
664 lines[off + mColumns + TOP] = v;
665
Doug Feltc982f602010-05-25 11:51:40 -0700666 if (hasTabOrEmoji)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800667 lines[off + TAB] |= TAB_MASK;
668
Doug Felt9f7a4442010-03-01 12:45:56 -0800669 lines[off + DIR] |= dir << DIR_SHIFT;
670 Directions linedirs = DIRS_ALL_LEFT_TO_RIGHT;
671 // easy means all chars < the first RTL, so no emoji, no nothing
Doug Felt4e0c5e52010-03-15 16:56:02 -0700672 // XXX a run with no text or all spaces is easy but might be an empty
Doug Felt9f7a4442010-03-01 12:45:56 -0800673 // RTL paragraph. Make sure easy is false if this is the case.
674 if (easy) {
675 mLineDirections[j] = linedirs;
676 } else {
Gilles Debunnef3fa0cd2011-02-03 14:17:05 -0800677 mLineDirections[j] = AndroidBidi.directions(dir, chdirs, start - widthStart, chs,
678 start - widthStart, end - start);
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800679 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800680
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -0700681 if (ellipsize != null) {
682 // If there is only one line, then do any type of ellipsis except when it is MARQUEE
683 // if there are multiple lines, just allow END ellipsis on the last line
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700684 boolean forceEllipsis = moreChars && (mLineCount + 1 == mMaximumVisibleLineCount);
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -0700685
Fabrice Di Meglio34a126e2012-02-29 18:43:14 -0800686 boolean doEllipsis =
687 (((mMaximumVisibleLineCount == 1 && moreChars) || (firstLine && !moreChars)) &&
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -0700688 ellipsize != TextUtils.TruncateAt.MARQUEE) ||
689 (!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
690 ellipsize == TextUtils.TruncateAt.END);
691 if (doEllipsis) {
692 calculateEllipsis(start, end, widths, widthStart,
693 ellipsisWidth, ellipsize, j,
694 textWidth, paint, forceEllipsis);
695 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800696 }
697
698 mLineCount++;
699 return v;
700 }
701
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800702 private void calculateEllipsis(int lineStart, int lineEnd,
703 float[] widths, int widthStart,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800704 float avail, TextUtils.TruncateAt where,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700705 int line, float textWidth, TextPaint paint,
706 boolean forceEllipsis) {
707 if (textWidth <= avail && !forceEllipsis) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800708 // Everything fits!
709 mLines[mColumns * line + ELLIPSIS_START] = 0;
710 mLines[mColumns * line + ELLIPSIS_COUNT] = 0;
711 return;
712 }
713
Fabrice Di Megliocb332642011-09-23 19:08:04 -0700714 float ellipsisWidth = paint.measureText(
Fabrice Di Meglio8d44fff2012-06-13 15:45:38 -0700715 (where == TextUtils.TruncateAt.END_SMALL) ?
Neil Fullerd29bdb22015-02-06 10:03:08 +0000716 TextUtils.ELLIPSIS_TWO_DOTS : TextUtils.ELLIPSIS_NORMAL, 0, 1);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700717 int ellipsisStart = 0;
718 int ellipsisCount = 0;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800719 int len = lineEnd - lineStart;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800720
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700721 // We only support start ellipsis on a single line
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800722 if (where == TextUtils.TruncateAt.START) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700723 if (mMaximumVisibleLineCount == 1) {
724 float sum = 0;
725 int i;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800726
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700727 for (i = len; i >= 0; i--) {
728 float w = widths[i - 1 + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800729
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700730 if (w + sum + ellipsisWidth > avail) {
731 break;
732 }
733
734 sum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800735 }
736
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700737 ellipsisStart = 0;
738 ellipsisCount = i;
739 } else {
740 if (Log.isLoggable(TAG, Log.WARN)) {
741 Log.w(TAG, "Start Ellipsis only supported with one line");
742 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800743 }
Fabrice Di Megliocb332642011-09-23 19:08:04 -0700744 } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE ||
745 where == TextUtils.TruncateAt.END_SMALL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800746 float sum = 0;
747 int i;
748
749 for (i = 0; i < len; i++) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800750 float w = widths[i + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800751
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800752 if (w + sum + ellipsisWidth > avail) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800753 break;
754 }
755
756 sum += w;
757 }
758
759 ellipsisStart = i;
760 ellipsisCount = len - i;
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -0700761 if (forceEllipsis && ellipsisCount == 0 && len > 0) {
762 ellipsisStart = len - 1;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700763 ellipsisCount = 1;
764 }
765 } else {
766 // where = TextUtils.TruncateAt.MIDDLE We only support middle ellipsis on a single line
767 if (mMaximumVisibleLineCount == 1) {
768 float lsum = 0, rsum = 0;
769 int left = 0, right = len;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800770
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700771 float ravail = (avail - ellipsisWidth) / 2;
Raph Levien0e3c5e82014-12-04 13:26:07 -0800772 for (right = len; right > 0; right--) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700773 float w = widths[right - 1 + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800774
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700775 if (w + rsum > ravail) {
776 break;
777 }
778
779 rsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800780 }
781
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700782 float lavail = avail - ellipsisWidth - rsum;
783 for (left = 0; left < right; left++) {
784 float w = widths[left + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800785
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700786 if (w + lsum > lavail) {
787 break;
788 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800789
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700790 lsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800791 }
792
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700793 ellipsisStart = left;
794 ellipsisCount = right - left;
795 } else {
796 if (Log.isLoggable(TAG, Log.WARN)) {
797 Log.w(TAG, "Middle Ellipsis only supported with one line");
798 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800799 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800800 }
801
802 mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
803 mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
804 }
805
Doug Felte8e45f22010-03-29 14:58:40 -0700806 // Override the base class so we can directly access our members,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800807 // rather than relying on member functions.
808 // The logic mirrors that of Layout.getLineForVertical
809 // FIXME: It may be faster to do a linear search for layouts without many lines.
Gilles Debunne66111472010-11-19 11:04:37 -0800810 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800811 public int getLineForVertical(int vertical) {
812 int high = mLineCount;
813 int low = -1;
814 int guess;
815 int[] lines = mLines;
816 while (high - low > 1) {
817 guess = (high + low) >> 1;
818 if (lines[mColumns * guess + TOP] > vertical){
819 high = guess;
820 } else {
821 low = guess;
822 }
823 }
824 if (low < 0) {
825 return 0;
826 } else {
827 return low;
828 }
829 }
830
Gilles Debunne66111472010-11-19 11:04:37 -0800831 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800832 public int getLineCount() {
833 return mLineCount;
834 }
835
Gilles Debunne66111472010-11-19 11:04:37 -0800836 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800837 public int getLineTop(int line) {
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800838 int top = mLines[mColumns * line + TOP];
839 if (mMaximumVisibleLineCount > 0 && line >= mMaximumVisibleLineCount &&
840 line != mLineCount) {
841 top += getBottomPadding();
842 }
843 return top;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800844 }
845
Gilles Debunne66111472010-11-19 11:04:37 -0800846 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800847 public int getLineDescent(int line) {
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800848 int descent = mLines[mColumns * line + DESCENT];
Gilles Debunnef3fa0cd2011-02-03 14:17:05 -0800849 if (mMaximumVisibleLineCount > 0 && line >= mMaximumVisibleLineCount - 1 && // -1 intended
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800850 line != mLineCount) {
851 descent += getBottomPadding();
852 }
853 return descent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800854 }
855
Gilles Debunne66111472010-11-19 11:04:37 -0800856 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800857 public int getLineStart(int line) {
858 return mLines[mColumns * line + START] & START_MASK;
859 }
860
Gilles Debunne66111472010-11-19 11:04:37 -0800861 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800862 public int getParagraphDirection(int line) {
863 return mLines[mColumns * line + DIR] >> DIR_SHIFT;
864 }
865
Gilles Debunne66111472010-11-19 11:04:37 -0800866 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800867 public boolean getLineContainsTab(int line) {
868 return (mLines[mColumns * line + TAB] & TAB_MASK) != 0;
869 }
870
Gilles Debunne66111472010-11-19 11:04:37 -0800871 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800872 public final Directions getLineDirections(int line) {
873 return mLineDirections[line];
874 }
875
Gilles Debunne66111472010-11-19 11:04:37 -0800876 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800877 public int getTopPadding() {
878 return mTopPadding;
879 }
880
Gilles Debunne66111472010-11-19 11:04:37 -0800881 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800882 public int getBottomPadding() {
883 return mBottomPadding;
884 }
885
886 @Override
887 public int getEllipsisCount(int line) {
888 if (mColumns < COLUMNS_ELLIPSIZE) {
889 return 0;
890 }
891
892 return mLines[mColumns * line + ELLIPSIS_COUNT];
893 }
894
895 @Override
896 public int getEllipsisStart(int line) {
897 if (mColumns < COLUMNS_ELLIPSIZE) {
898 return 0;
899 }
900
901 return mLines[mColumns * line + ELLIPSIS_START];
902 }
903
904 @Override
905 public int getEllipsizedWidth() {
906 return mEllipsizedWidth;
907 }
908
Anish Athalyec8f9e622014-07-21 15:26:34 -0700909 // populates LineBreaks and returns the number of breaks found
910 //
911 // the arrays inside the LineBreaks objects are passed in as well
912 // to reduce the number of JNI calls in the common case where the
913 // arrays do not have to be resized
914 private static native int nComputeLineBreaks(String locale, char[] text, float[] widths,
915 int length, float firstWidth, int firstWidthLineCount, float restWidth,
Anish Athalye969e7b92014-07-21 15:34:20 -0700916 int[] variableTabStops, int defaultTabStop, boolean optimize, LineBreaks recycle,
Anish Athalyec8f9e622014-07-21 15:26:34 -0700917 int[] recycleBreaks, float[] recycleWidths, boolean[] recycleFlags, int recycleLength);
Anish Athalye88b5b0b2014-06-24 14:39:43 -0700918
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800919 private int mLineCount;
920 private int mTopPadding, mBottomPadding;
921 private int mColumns;
922 private int mEllipsizedWidth;
923
924 private static final int COLUMNS_NORMAL = 3;
925 private static final int COLUMNS_ELLIPSIZE = 5;
926 private static final int START = 0;
927 private static final int DIR = START;
928 private static final int TAB = START;
929 private static final int TOP = 1;
930 private static final int DESCENT = 2;
931 private static final int ELLIPSIS_START = 3;
932 private static final int ELLIPSIS_COUNT = 4;
933
934 private int[] mLines;
935 private Directions[] mLineDirections;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700936 private int mMaximumVisibleLineCount = Integer.MAX_VALUE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800937
938 private static final int START_MASK = 0x1FFFFFFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800939 private static final int DIR_SHIFT = 30;
940 private static final int TAB_MASK = 0x20000000;
941
Doug Feltc982f602010-05-25 11:51:40 -0700942 private static final int TAB_INCREMENT = 20; // same as Layout, but that's private
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800943
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800944 private static final char CHAR_NEW_LINE = '\n';
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800945
946 private static final double EXTRA_ROUNDING = 0.5;
Fabrice Di Megliocb332642011-09-23 19:08:04 -0700947
Anish Athalyec8f9e622014-07-21 15:26:34 -0700948 // This is used to return three arrays from a single JNI call when
949 // performing line breaking
Deepanshu Gupta70539192014-10-15 15:57:40 -0700950 /*package*/ static class LineBreaks {
Anish Athalyec8f9e622014-07-21 15:26:34 -0700951 private static final int INITIAL_SIZE = 16;
952 public int[] breaks = new int[INITIAL_SIZE];
953 public float[] widths = new float[INITIAL_SIZE];
954 public boolean[] flags = new boolean[INITIAL_SIZE]; // hasTabOrEmoji
955 // breaks, widths, and flags should all have the same length
956 }
957
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800958}