blob: 74b7b6928fd0730afc85506170d796b5f4fda1ba [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
The Android Open Source Project10592532009-03-18 17:39:46 -070019import android.graphics.Bitmap;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080020import android.graphics.Paint;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080021import android.text.style.LeadingMarginSpan;
Gilles Debunne66111472010-11-19 11:04:37 -080022import android.text.style.LeadingMarginSpan.LeadingMarginSpan2;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080023import android.text.style.LineHeightSpan;
24import android.text.style.MetricAffectingSpan;
Doug Feltc982f602010-05-25 11:51:40 -070025import android.text.style.TabStopSpan;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -070026import android.util.Log;
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
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080031/**
32 * StaticLayout is a Layout for text that will not be edited after it
33 * is laid out. Use {@link DynamicLayout} for text that may change.
34 * <p>This is used by widgets to control text layout. You should not need
35 * to use this class directly unless you are implementing your own widget
36 * or custom display object, or would be tempted to call
Doug Felt4e0c5e52010-03-15 16:56:02 -070037 * {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int,
38 * float, float, android.graphics.Paint)
39 * Canvas.drawText()} directly.</p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080040 */
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -080041public class StaticLayout extends Layout {
42
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -070043 static final String TAG = "StaticLayout";
44
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080045 public StaticLayout(CharSequence source, TextPaint paint,
46 int width,
47 Alignment align, float spacingmult, float spacingadd,
48 boolean includepad) {
49 this(source, 0, source.length(), paint, width, align,
50 spacingmult, spacingadd, includepad);
51 }
52
Doug Feltcb3791202011-07-07 11:57:48 -070053 /**
54 * @hide
55 */
56 public StaticLayout(CharSequence source, TextPaint paint,
57 int width, Alignment align, TextDirectionHeuristic textDir,
58 float spacingmult, float spacingadd,
59 boolean includepad) {
60 this(source, 0, source.length(), paint, width, align, textDir,
61 spacingmult, spacingadd, includepad);
62 }
63
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080064 public StaticLayout(CharSequence source, int bufstart, int bufend,
65 TextPaint paint, int outerwidth,
66 Alignment align,
67 float spacingmult, float spacingadd,
68 boolean includepad) {
69 this(source, bufstart, bufend, paint, outerwidth, align,
70 spacingmult, spacingadd, includepad, null, 0);
71 }
72
Doug Feltcb3791202011-07-07 11:57:48 -070073 /**
74 * @hide
75 */
76 public StaticLayout(CharSequence source, int bufstart, int bufend,
77 TextPaint paint, int outerwidth,
78 Alignment align, TextDirectionHeuristic textDir,
79 float spacingmult, float spacingadd,
80 boolean includepad) {
81 this(source, bufstart, bufend, paint, outerwidth, align, textDir,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -070082 spacingmult, spacingadd, includepad, null, 0, Integer.MAX_VALUE);
Doug Feltcb3791202011-07-07 11:57:48 -070083}
84
85 public StaticLayout(CharSequence source, int bufstart, int bufend,
86 TextPaint paint, int outerwidth,
87 Alignment align,
88 float spacingmult, float spacingadd,
89 boolean includepad,
90 TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
91 this(source, bufstart, bufend, paint, outerwidth, align,
92 TextDirectionHeuristics.FIRSTSTRONG_LTR,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -070093 spacingmult, spacingadd, includepad, ellipsize, ellipsizedWidth, Integer.MAX_VALUE);
Doug Feltcb3791202011-07-07 11:57:48 -070094 }
95
96 /**
97 * @hide
98 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080099 public StaticLayout(CharSequence source, int bufstart, int bufend,
100 TextPaint paint, int outerwidth,
Doug Feltcb3791202011-07-07 11:57:48 -0700101 Alignment align, TextDirectionHeuristic textDir,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800102 float spacingmult, float spacingadd,
103 boolean includepad,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700104 TextUtils.TruncateAt ellipsize, int ellipsizedWidth, int maxLines) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800105 super((ellipsize == null)
Doug Felt4e0c5e52010-03-15 16:56:02 -0700106 ? source
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800107 : (source instanceof Spanned)
108 ? new SpannedEllipsizer(source)
109 : new Ellipsizer(source),
Doug Feltcb3791202011-07-07 11:57:48 -0700110 paint, outerwidth, align, textDir, spacingmult, spacingadd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800111
112 /*
113 * This is annoying, but we can't refer to the layout until
114 * superclass construction is finished, and the superclass
115 * constructor wants the reference to the display text.
Doug Felt4e0c5e52010-03-15 16:56:02 -0700116 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800117 * This will break if the superclass constructor ever actually
118 * cares about the content instead of just holding the reference.
119 */
120 if (ellipsize != null) {
121 Ellipsizer e = (Ellipsizer) getText();
122
123 e.mLayout = this;
124 e.mWidth = ellipsizedWidth;
125 e.mMethod = ellipsize;
126 mEllipsizedWidth = ellipsizedWidth;
127
128 mColumns = COLUMNS_ELLIPSIZE;
129 } else {
130 mColumns = COLUMNS_NORMAL;
131 mEllipsizedWidth = outerwidth;
132 }
133
Adam Lesinski776abc22014-03-07 11:30:59 -0500134 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2 * mColumns);
135 mLines = new int[mLineDirections.length];
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700136 mMaximumVisibleLineCount = maxLines;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800137
Doug Felte8e45f22010-03-29 14:58:40 -0700138 mMeasured = MeasuredText.obtain();
139
Gilles Debunned300e752011-10-17 13:37:36 -0700140 generate(source, bufstart, bufend, paint, outerwidth, textDir, spacingmult,
141 spacingadd, includepad, includepad, ellipsizedWidth,
142 ellipsize);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800143
Doug Felte8e45f22010-03-29 14:58:40 -0700144 mMeasured = MeasuredText.recycle(mMeasured);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800145 mFontMetricsInt = null;
146 }
147
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700148 /* package */ StaticLayout(CharSequence text) {
149 super(text, null, 0, null, 0, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800150
151 mColumns = COLUMNS_ELLIPSIZE;
Adam Lesinski776abc22014-03-07 11:30:59 -0500152 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2 * mColumns);
153 mLines = new int[mLineDirections.length];
Gilles Debunne81541492012-06-26 16:05:15 -0700154 // FIXME This is never recycled
Doug Felte8e45f22010-03-29 14:58:40 -0700155 mMeasured = MeasuredText.obtain();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800156 }
157
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800158 /* package */ void generate(CharSequence source, int bufStart, int bufEnd,
159 TextPaint paint, int outerWidth,
Gilles Debunned300e752011-10-17 13:37:36 -0700160 TextDirectionHeuristic textDir, float spacingmult,
161 float spacingadd, boolean includepad,
162 boolean trackpad, float ellipsizedWidth,
163 TextUtils.TruncateAt ellipsize) {
Anish Athalye88b5b0b2014-06-24 14:39:43 -0700164 int[] breakOpp = null;
165 final String localeLanguageTag = paint.getTextLocale().toLanguageTag();
166
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800167 mLineCount = 0;
168
169 int v = 0;
170 boolean needMultiply = (spacingmult != 1 || spacingadd != 0);
171
172 Paint.FontMetricsInt fm = mFontMetricsInt;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800173 int[] chooseHtv = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800174
Doug Felte8e45f22010-03-29 14:58:40 -0700175 MeasuredText measured = mMeasured;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800176
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800177 Spanned spanned = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800178 if (source instanceof Spanned)
179 spanned = (Spanned) source;
180
Doug Felte8e45f22010-03-29 14:58:40 -0700181 int paraEnd;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800182 for (int paraStart = bufStart; paraStart <= bufEnd; paraStart = paraEnd) {
183 paraEnd = TextUtils.indexOf(source, CHAR_NEW_LINE, paraStart, bufEnd);
Doug Felte8e45f22010-03-29 14:58:40 -0700184 if (paraEnd < 0)
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800185 paraEnd = bufEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800186 else
Doug Felte8e45f22010-03-29 14:58:40 -0700187 paraEnd++;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800188
Doug Feltc982f602010-05-25 11:51:40 -0700189 int firstWidthLineLimit = mLineCount + 1;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800190 int firstWidth = outerWidth;
191 int restWidth = outerWidth;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800192
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800193 LineHeightSpan[] chooseHt = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800194
195 if (spanned != null) {
Eric Fischer74d31ef2010-08-05 15:29:36 -0700196 LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
Doug Felte8e45f22010-03-29 14:58:40 -0700197 LeadingMarginSpan.class);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800198 for (int i = 0; i < sp.length; i++) {
Mark Wagner7b5676e2009-10-16 11:44:23 -0700199 LeadingMarginSpan lms = sp[i];
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800200 firstWidth -= sp[i].getLeadingMargin(true);
201 restWidth -= sp[i].getLeadingMargin(false);
Doug Feltcb3791202011-07-07 11:57:48 -0700202
Doug Feltc982f602010-05-25 11:51:40 -0700203 // LeadingMarginSpan2 is odd. The count affects all
Anish Athalyeab08c6d2014-08-08 12:09:58 -0700204 // leading margin spans, not just this particular one
Doug Feltc982f602010-05-25 11:51:40 -0700205 if (lms instanceof LeadingMarginSpan2) {
206 LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
207 int lmsFirstLine = getLineForOffset(spanned.getSpanStart(lms2));
Anish Athalyeab08c6d2014-08-08 12:09:58 -0700208 firstWidthLineLimit = Math.max(firstWidthLineLimit,
209 lmsFirstLine + lms2.getLeadingMarginLineCount());
Mark Wagner7b5676e2009-10-16 11:44:23 -0700210 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800211 }
212
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800213 chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800214
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800215 if (chooseHt.length != 0) {
216 if (chooseHtv == null ||
217 chooseHtv.length < chooseHt.length) {
Adam Lesinski776abc22014-03-07 11:30:59 -0500218 chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800219 }
220
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800221 for (int i = 0; i < chooseHt.length; i++) {
222 int o = spanned.getSpanStart(chooseHt[i]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800223
Doug Felte8e45f22010-03-29 14:58:40 -0700224 if (o < paraStart) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800225 // starts in this layout, before the
226 // current paragraph
227
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800228 chooseHtv[i] = getLineTop(getLineForOffset(o));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800229 } else {
230 // starts in this paragraph
231
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800232 chooseHtv[i] = v;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800233 }
234 }
235 }
236 }
237
Doug Feltcb3791202011-07-07 11:57:48 -0700238 measured.setPara(source, paraStart, paraEnd, textDir);
Doug Felte8e45f22010-03-29 14:58:40 -0700239 char[] chs = measured.mChars;
240 float[] widths = measured.mWidths;
241 byte[] chdirs = measured.mLevels;
242 int dir = measured.mDir;
243 boolean easy = measured.mEasy;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800244
Anish Athalye88b5b0b2014-06-24 14:39:43 -0700245 breakOpp = nLineBreakOpportunities(localeLanguageTag, chs, paraEnd - paraStart, breakOpp);
246 int breakOppIndex = 0;
247
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800248 int width = firstWidth;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800249
250 float w = 0;
Gilles Debunnecd943a72012-06-07 17:54:47 -0700251 // here is the offset of the starting character of the line we are currently measuring
Doug Felte8e45f22010-03-29 14:58:40 -0700252 int here = paraStart;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800253
Gilles Debunnecd943a72012-06-07 17:54:47 -0700254 // ok is a character offset located after a word separator (space, tab, number...) where
255 // we would prefer to cut the current line. Equals to here when no such break was found.
Doug Felte8e45f22010-03-29 14:58:40 -0700256 int ok = paraStart;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800257 float okWidth = w;
258 int okAscent = 0, okDescent = 0, okTop = 0, okBottom = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800259
Gilles Debunnecd943a72012-06-07 17:54:47 -0700260 // fit is a character offset such that the [here, fit[ range fits in the allowed width.
261 // We will cut the line there if no ok position is found.
Doug Felte8e45f22010-03-29 14:58:40 -0700262 int fit = paraStart;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800263 float fitWidth = w;
264 int fitAscent = 0, fitDescent = 0, fitTop = 0, fitBottom = 0;
Raph Levien26c87cf2014-08-28 12:53:10 -0700265 // same as fitWidth but not including any trailing whitespace
266 float fitWidthGraphing = w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800267
Doug Feltc982f602010-05-25 11:51:40 -0700268 boolean hasTabOrEmoji = false;
269 boolean hasTab = false;
270 TabStops tabStops = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800271
Gilles Debunnecd943a72012-06-07 17:54:47 -0700272 for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
Doug Felte8e45f22010-03-29 14:58:40 -0700273
Gilles Debunnecd943a72012-06-07 17:54:47 -0700274 if (spanned == null) {
275 spanEnd = paraEnd;
Doug Felt23241882010-06-02 14:41:06 -0700276 int spanLen = spanEnd - spanStart;
Gilles Debunnecd943a72012-06-07 17:54:47 -0700277 measured.addStyleRun(paint, spanLen, fm);
278 } else {
279 spanEnd = spanned.nextSpanTransition(spanStart, paraEnd,
280 MetricAffectingSpan.class);
281 int spanLen = spanEnd - spanStart;
282 MetricAffectingSpan[] spans =
Doug Felt23241882010-06-02 14:41:06 -0700283 spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class);
Gilles Debunnecd943a72012-06-07 17:54:47 -0700284 spans = TextUtils.removeEmptySpans(spans, spanned, MetricAffectingSpan.class);
285 measured.addStyleRun(paint, spans, spanLen, fm);
Doug Felt23241882010-06-02 14:41:06 -0700286 }
287
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800288 int fmTop = fm.top;
289 int fmBottom = fm.bottom;
290 int fmAscent = fm.ascent;
291 int fmDescent = fm.descent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800292
Doug Felte8e45f22010-03-29 14:58:40 -0700293 for (int j = spanStart; j < spanEnd; j++) {
294 char c = chs[j - paraStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800295
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800296 if (c == CHAR_NEW_LINE) {
Gilles Debunne66111472010-11-19 11:04:37 -0800297 // intentionally left empty
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800298 } else if (c == CHAR_TAB) {
Doug Feltc982f602010-05-25 11:51:40 -0700299 if (hasTab == false) {
300 hasTab = true;
301 hasTabOrEmoji = true;
Kenny Root24ca4542010-06-22 23:46:35 -0700302 if (spanned != null) {
303 // First tab this para, check for tabstops
Eric Fischer74d31ef2010-08-05 15:29:36 -0700304 TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
Kenny Root24ca4542010-06-22 23:46:35 -0700305 paraEnd, TabStopSpan.class);
306 if (spans.length > 0) {
307 tabStops = new TabStops(TAB_INCREMENT, spans);
308 }
Doug Feltc982f602010-05-25 11:51:40 -0700309 }
310 }
311 if (tabStops != null) {
312 w = tabStops.nextTab(w);
313 } else {
314 w = TabStops.nextDefaultStop(w, TAB_INCREMENT);
315 }
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800316 } else if (c >= CHAR_FIRST_HIGH_SURROGATE && c <= CHAR_LAST_LOW_SURROGATE
317 && j + 1 < spanEnd) {
Doug Felte8e45f22010-03-29 14:58:40 -0700318 int emoji = Character.codePointAt(chs, j - paraStart);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800319
The Android Open Source Project10592532009-03-18 17:39:46 -0700320 if (emoji >= MIN_EMOJI && emoji <= MAX_EMOJI) {
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800321 Bitmap bm = EMOJI_FACTORY.getBitmapFromAndroidPua(emoji);
The Android Open Source Project10592532009-03-18 17:39:46 -0700322
323 if (bm != null) {
Eric Fischer423f0e42009-03-27 18:04:12 -0700324 Paint whichPaint;
325
326 if (spanned == null) {
327 whichPaint = paint;
328 } else {
329 whichPaint = mWorkPaint;
330 }
331
Gilles Debunned300e752011-10-17 13:37:36 -0700332 float wid = bm.getWidth() * -whichPaint.ascent() / bm.getHeight();
Eric Fischer423f0e42009-03-27 18:04:12 -0700333
334 w += wid;
Doug Feltc982f602010-05-25 11:51:40 -0700335 hasTabOrEmoji = true;
The Android Open Source Project10592532009-03-18 17:39:46 -0700336 j++;
337 } else {
Doug Felte8e45f22010-03-29 14:58:40 -0700338 w += widths[j - paraStart];
The Android Open Source Project10592532009-03-18 17:39:46 -0700339 }
340 } else {
Doug Felte8e45f22010-03-29 14:58:40 -0700341 w += widths[j - paraStart];
The Android Open Source Project10592532009-03-18 17:39:46 -0700342 }
343 } else {
Doug Felte8e45f22010-03-29 14:58:40 -0700344 w += widths[j - paraStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800345 }
346
Raph Levien8d087c32013-03-29 09:25:48 -0700347 boolean isSpaceOrTab = c == CHAR_SPACE || c == CHAR_TAB || c == CHAR_ZWSP;
Gilles Debunne81541492012-06-26 16:05:15 -0700348
349 if (w <= width || isSpaceOrTab) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800350 fitWidth = w;
Raph Levien26c87cf2014-08-28 12:53:10 -0700351 if (!isSpaceOrTab) {
352 fitWidthGraphing = w;
353 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800354 fit = j + 1;
355
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800356 if (fmTop < fitTop)
357 fitTop = fmTop;
358 if (fmAscent < fitAscent)
359 fitAscent = fmAscent;
360 if (fmDescent > fitDescent)
361 fitDescent = fmDescent;
362 if (fmBottom > fitBottom)
363 fitBottom = fmBottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800364
Anish Athalye88b5b0b2014-06-24 14:39:43 -0700365 while (breakOpp[breakOppIndex] != -1
366 && breakOpp[breakOppIndex] < j - paraStart + 1) {
367 breakOppIndex++;
368 }
369 boolean isLineBreak = breakOppIndex < breakOpp.length &&
370 breakOpp[breakOppIndex] == j - paraStart + 1;
Gilles Debunne81541492012-06-26 16:05:15 -0700371
372 if (isLineBreak) {
Raph Levien26c87cf2014-08-28 12:53:10 -0700373 okWidth = fitWidthGraphing;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800374 ok = j + 1;
375
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800376 if (fitTop < okTop)
377 okTop = fitTop;
378 if (fitAscent < okAscent)
379 okAscent = fitAscent;
380 if (fitDescent > okDescent)
381 okDescent = fitDescent;
382 if (fitBottom > okBottom)
383 okBottom = fitBottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800384 }
Gilles Debunne4cf435d2011-01-04 15:35:29 -0800385 } else {
Gilles Debunned300e752011-10-17 13:37:36 -0700386 int endPos;
387 int above, below, top, bottom;
388 float currentTextWidth;
Gilles Debunne32ea4ff2010-12-21 11:28:34 -0800389
Gilles Debunned300e752011-10-17 13:37:36 -0700390 if (ok != here) {
Gilles Debunned300e752011-10-17 13:37:36 -0700391 endPos = ok;
392 above = okAscent;
393 below = okDescent;
394 top = okTop;
395 bottom = okBottom;
396 currentTextWidth = okWidth;
397 } else if (fit != here) {
398 endPos = fit;
399 above = fitAscent;
400 below = fitDescent;
401 top = fitTop;
402 bottom = fitBottom;
403 currentTextWidth = fitWidth;
404 } else {
Anish Athalye9cd3bcc2014-06-27 14:37:53 -0700405 // must make progress, so take next character
Gilles Debunned300e752011-10-17 13:37:36 -0700406 endPos = here + 1;
Anish Athalye9cd3bcc2014-06-27 14:37:53 -0700407 // but to deal properly with clusters
408 // take all zero width characters following that
409 while (endPos < spanEnd && widths[endPos - paraStart] == 0) {
410 endPos++;
411 }
412 above = fmAscent;
413 below = fmDescent;
414 top = fmTop;
415 bottom = fmBottom;
Gilles Debunned300e752011-10-17 13:37:36 -0700416 currentTextWidth = widths[here - paraStart];
417 }
418
Raph Levien0e3c5e82014-12-04 13:26:07 -0800419 int ellipseEnd = endPos;
420 if (mMaximumVisibleLineCount == 1 && ellipsize == TextUtils.TruncateAt.MIDDLE) {
421 ellipseEnd = paraEnd;
422 }
423 v = out(source, here, ellipseEnd,
Gilles Debunned300e752011-10-17 13:37:36 -0700424 above, below, top, bottom,
425 v, spacingmult, spacingadd, chooseHt,chooseHtv, fm, hasTabOrEmoji,
426 needMultiply, chdirs, dir, easy, bufEnd, includepad, trackpad,
427 chs, widths, paraStart, ellipsize, ellipsizedWidth,
Raph Levien396879f2014-11-17 12:32:06 -0800428 currentTextWidth, paint, true);
Gilles Debunnecd943a72012-06-07 17:54:47 -0700429
Gilles Debunned300e752011-10-17 13:37:36 -0700430 here = endPos;
Gilles Debunnecd943a72012-06-07 17:54:47 -0700431 j = here - 1; // restart j-span loop from here, compensating for the j++
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800432 ok = fit = here;
433 w = 0;
Raph Levien26c87cf2014-08-28 12:53:10 -0700434 fitWidthGraphing = w;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800435 fitAscent = fitDescent = fitTop = fitBottom = 0;
436 okAscent = okDescent = okTop = okBottom = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800437
Doug Feltc982f602010-05-25 11:51:40 -0700438 if (--firstWidthLineLimit <= 0) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800439 width = restWidth;
Mark Wagner7b5676e2009-10-16 11:44:23 -0700440 }
Gilles Debunnecd943a72012-06-07 17:54:47 -0700441
442 if (here < spanStart) {
443 // The text was cut before the beginning of the current span range.
444 // Exit the span loop, and get spanStart to start over from here.
445 measured.setPos(here);
446 spanEnd = here;
447 break;
448 }
Gilles Debunne81541492012-06-26 16:05:15 -0700449
450 if (mLineCount >= mMaximumVisibleLineCount) {
Raph Levien63b3d8c2014-04-03 15:52:08 -0700451 return;
Gilles Debunne81541492012-06-26 16:05:15 -0700452 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700453 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800454 }
455 }
456
Fabrice Di Meglioad0b0512011-10-04 17:21:26 -0700457 if (paraEnd != here && mLineCount < mMaximumVisibleLineCount) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800458 if ((fitTop | fitBottom | fitDescent | fitAscent) == 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800459 paint.getFontMetricsInt(fm);
460
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800461 fitTop = fm.top;
462 fitBottom = fm.bottom;
463 fitAscent = fm.ascent;
464 fitDescent = fm.descent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800465 }
466
467 // Log.e("text", "output rest " + here + " to " + end);
468
469 v = out(source,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800470 here, paraEnd, fitAscent, fitDescent,
471 fitTop, fitBottom,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800472 v,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800473 spacingmult, spacingadd, chooseHt,
474 chooseHtv, fm, hasTabOrEmoji,
Gilles Debunned300e752011-10-17 13:37:36 -0700475 needMultiply, chdirs, dir, easy, bufEnd,
476 includepad, trackpad, chs,
477 widths, paraStart, ellipsize,
478 ellipsizedWidth, w, paint, paraEnd != bufEnd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800479 }
480
Doug Felte8e45f22010-03-29 14:58:40 -0700481 paraStart = paraEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800482
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800483 if (paraEnd == bufEnd)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800484 break;
485 }
486
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700487 if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE) &&
Fabrice Di Meglioad0b0512011-10-04 17:21:26 -0700488 mLineCount < mMaximumVisibleLineCount) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800489 // Log.e("text", "output last " + bufEnd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800490
Fabrice Di Meglioe6318892013-06-18 20:03:41 -0700491 measured.setPara(source, bufStart, bufEnd, textDir);
492
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800493 paint.getFontMetricsInt(fm);
494
495 v = out(source,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800496 bufEnd, bufEnd, fm.ascent, fm.descent,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800497 fm.top, fm.bottom,
498 v,
499 spacingmult, spacingadd, null,
500 null, fm, false,
Fabrice Di Meglioe6318892013-06-18 20:03:41 -0700501 needMultiply, measured.mLevels, measured.mDir, measured.mEasy, bufEnd,
Gilles Debunned300e752011-10-17 13:37:36 -0700502 includepad, trackpad, null,
503 null, bufStart, ellipsize,
504 ellipsizedWidth, 0, paint, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800505 }
506 }
507
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800508 private int out(CharSequence text, int start, int end,
509 int above, int below, int top, int bottom, int v,
510 float spacingmult, float spacingadd,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800511 LineHeightSpan[] chooseHt, int[] chooseHtv,
Doug Feltc982f602010-05-25 11:51:40 -0700512 Paint.FontMetricsInt fm, boolean hasTabOrEmoji,
Gilles Debunned300e752011-10-17 13:37:36 -0700513 boolean needMultiply, byte[] chdirs, int dir,
514 boolean easy, int bufEnd, boolean includePad,
515 boolean trackPad, char[] chs,
516 float[] widths, int widthStart, TextUtils.TruncateAt ellipsize,
517 float ellipsisWidth, float textWidth,
518 TextPaint paint, boolean moreChars) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800519 int j = mLineCount;
520 int off = j * mColumns;
521 int want = off + mColumns + TOP;
522 int[] lines = mLines;
523
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800524 if (want >= lines.length) {
Adam Lesinski776abc22014-03-07 11:30:59 -0500525 Directions[] grow2 = ArrayUtils.newUnpaddedArray(
526 Directions.class, GrowingArrayUtils.growSize(want));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800527 System.arraycopy(mLineDirections, 0, grow2, 0,
528 mLineDirections.length);
529 mLineDirections = grow2;
Adam Lesinski776abc22014-03-07 11:30:59 -0500530
531 int[] grow = new int[grow2.length];
532 System.arraycopy(lines, 0, grow, 0, lines.length);
533 mLines = grow;
534 lines = grow;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800535 }
536
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800537 if (chooseHt != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800538 fm.ascent = above;
539 fm.descent = below;
540 fm.top = top;
541 fm.bottom = bottom;
542
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800543 for (int i = 0; i < chooseHt.length; i++) {
544 if (chooseHt[i] instanceof LineHeightSpan.WithDensity) {
545 ((LineHeightSpan.WithDensity) chooseHt[i]).
546 chooseHeight(text, start, end, chooseHtv[i], v, fm, paint);
Eric Fischera9f1dd02009-08-12 15:00:10 -0700547
548 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800549 chooseHt[i].chooseHeight(text, start, end, chooseHtv[i], v, fm);
Eric Fischera9f1dd02009-08-12 15:00:10 -0700550 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800551 }
552
553 above = fm.ascent;
554 below = fm.descent;
555 top = fm.top;
556 bottom = fm.bottom;
557 }
558
Raph Leviend97b0972014-04-24 12:51:35 -0700559 boolean firstLine = (j == 0);
560 boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);
561 boolean lastLine = currentLineIsTheLastVisibleOne || (end == bufEnd);
562
563 if (firstLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800564 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800565 mTopPadding = top - above;
566 }
567
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800568 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800569 above = top;
570 }
571 }
Raph Leviend97b0972014-04-24 12:51:35 -0700572
573 int extra;
574
575 if (lastLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800576 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800577 mBottomPadding = bottom - below;
578 }
579
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800580 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800581 below = bottom;
582 }
583 }
584
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800585
Raph Leviend97b0972014-04-24 12:51:35 -0700586 if (needMultiply && !lastLine) {
Doug Felt10657582010-02-22 11:19:01 -0800587 double ex = (below - above) * (spacingmult - 1) + spacingadd;
588 if (ex >= 0) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800589 extra = (int)(ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -0800590 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800591 extra = -(int)(-ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -0800592 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800593 } else {
594 extra = 0;
595 }
596
597 lines[off + START] = start;
598 lines[off + TOP] = v;
599 lines[off + DESCENT] = below + extra;
600
601 v += (below - above) + extra;
602 lines[off + mColumns + START] = end;
603 lines[off + mColumns + TOP] = v;
604
Doug Feltc982f602010-05-25 11:51:40 -0700605 if (hasTabOrEmoji)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800606 lines[off + TAB] |= TAB_MASK;
607
Doug Felt9f7a4442010-03-01 12:45:56 -0800608 lines[off + DIR] |= dir << DIR_SHIFT;
609 Directions linedirs = DIRS_ALL_LEFT_TO_RIGHT;
610 // easy means all chars < the first RTL, so no emoji, no nothing
Doug Felt4e0c5e52010-03-15 16:56:02 -0700611 // XXX a run with no text or all spaces is easy but might be an empty
Doug Felt9f7a4442010-03-01 12:45:56 -0800612 // RTL paragraph. Make sure easy is false if this is the case.
613 if (easy) {
614 mLineDirections[j] = linedirs;
615 } else {
Gilles Debunnef3fa0cd2011-02-03 14:17:05 -0800616 mLineDirections[j] = AndroidBidi.directions(dir, chdirs, start - widthStart, chs,
617 start - widthStart, end - start);
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800618 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800619
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -0700620 if (ellipsize != null) {
621 // If there is only one line, then do any type of ellipsis except when it is MARQUEE
622 // if there are multiple lines, just allow END ellipsis on the last line
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700623 boolean forceEllipsis = moreChars && (mLineCount + 1 == mMaximumVisibleLineCount);
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -0700624
Fabrice Di Meglio34a126e2012-02-29 18:43:14 -0800625 boolean doEllipsis =
626 (((mMaximumVisibleLineCount == 1 && moreChars) || (firstLine && !moreChars)) &&
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -0700627 ellipsize != TextUtils.TruncateAt.MARQUEE) ||
628 (!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
629 ellipsize == TextUtils.TruncateAt.END);
630 if (doEllipsis) {
631 calculateEllipsis(start, end, widths, widthStart,
632 ellipsisWidth, ellipsize, j,
633 textWidth, paint, forceEllipsis);
634 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800635 }
636
637 mLineCount++;
638 return v;
639 }
640
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800641 private void calculateEllipsis(int lineStart, int lineEnd,
642 float[] widths, int widthStart,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800643 float avail, TextUtils.TruncateAt where,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700644 int line, float textWidth, TextPaint paint,
645 boolean forceEllipsis) {
646 if (textWidth <= avail && !forceEllipsis) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800647 // Everything fits!
648 mLines[mColumns * line + ELLIPSIS_START] = 0;
649 mLines[mColumns * line + ELLIPSIS_COUNT] = 0;
650 return;
651 }
652
Fabrice Di Megliocb332642011-09-23 19:08:04 -0700653 float ellipsisWidth = paint.measureText(
Fabrice Di Meglio8d44fff2012-06-13 15:45:38 -0700654 (where == TextUtils.TruncateAt.END_SMALL) ?
655 ELLIPSIS_TWO_DOTS : ELLIPSIS_NORMAL, 0, 1);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700656 int ellipsisStart = 0;
657 int ellipsisCount = 0;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800658 int len = lineEnd - lineStart;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800659
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700660 // We only support start ellipsis on a single line
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800661 if (where == TextUtils.TruncateAt.START) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700662 if (mMaximumVisibleLineCount == 1) {
663 float sum = 0;
664 int i;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800665
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700666 for (i = len; i >= 0; i--) {
667 float w = widths[i - 1 + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800668
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700669 if (w + sum + ellipsisWidth > avail) {
670 break;
671 }
672
673 sum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800674 }
675
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700676 ellipsisStart = 0;
677 ellipsisCount = i;
678 } else {
679 if (Log.isLoggable(TAG, Log.WARN)) {
680 Log.w(TAG, "Start Ellipsis only supported with one line");
681 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800682 }
Fabrice Di Megliocb332642011-09-23 19:08:04 -0700683 } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE ||
684 where == TextUtils.TruncateAt.END_SMALL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800685 float sum = 0;
686 int i;
687
688 for (i = 0; i < len; i++) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800689 float w = widths[i + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800690
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800691 if (w + sum + ellipsisWidth > avail) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800692 break;
693 }
694
695 sum += w;
696 }
697
698 ellipsisStart = i;
699 ellipsisCount = len - i;
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -0700700 if (forceEllipsis && ellipsisCount == 0 && len > 0) {
701 ellipsisStart = len - 1;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700702 ellipsisCount = 1;
703 }
704 } else {
705 // where = TextUtils.TruncateAt.MIDDLE We only support middle ellipsis on a single line
706 if (mMaximumVisibleLineCount == 1) {
707 float lsum = 0, rsum = 0;
708 int left = 0, right = len;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800709
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700710 float ravail = (avail - ellipsisWidth) / 2;
Raph Levien0e3c5e82014-12-04 13:26:07 -0800711 for (right = len; right > 0; right--) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700712 float w = widths[right - 1 + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800713
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700714 if (w + rsum > ravail) {
715 break;
716 }
717
718 rsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800719 }
720
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700721 float lavail = avail - ellipsisWidth - rsum;
722 for (left = 0; left < right; left++) {
723 float w = widths[left + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800724
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700725 if (w + lsum > lavail) {
726 break;
727 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800728
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700729 lsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800730 }
731
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700732 ellipsisStart = left;
733 ellipsisCount = right - left;
734 } else {
735 if (Log.isLoggable(TAG, Log.WARN)) {
736 Log.w(TAG, "Middle Ellipsis only supported with one line");
737 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800738 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800739 }
740
741 mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
742 mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
743 }
744
Doug Felte8e45f22010-03-29 14:58:40 -0700745 // Override the base class so we can directly access our members,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800746 // rather than relying on member functions.
747 // The logic mirrors that of Layout.getLineForVertical
748 // FIXME: It may be faster to do a linear search for layouts without many lines.
Gilles Debunne66111472010-11-19 11:04:37 -0800749 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800750 public int getLineForVertical(int vertical) {
751 int high = mLineCount;
752 int low = -1;
753 int guess;
754 int[] lines = mLines;
755 while (high - low > 1) {
756 guess = (high + low) >> 1;
757 if (lines[mColumns * guess + TOP] > vertical){
758 high = guess;
759 } else {
760 low = guess;
761 }
762 }
763 if (low < 0) {
764 return 0;
765 } else {
766 return low;
767 }
768 }
769
Gilles Debunne66111472010-11-19 11:04:37 -0800770 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800771 public int getLineCount() {
772 return mLineCount;
773 }
774
Gilles Debunne66111472010-11-19 11:04:37 -0800775 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800776 public int getLineTop(int line) {
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800777 int top = mLines[mColumns * line + TOP];
778 if (mMaximumVisibleLineCount > 0 && line >= mMaximumVisibleLineCount &&
779 line != mLineCount) {
780 top += getBottomPadding();
781 }
782 return top;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800783 }
784
Gilles Debunne66111472010-11-19 11:04:37 -0800785 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800786 public int getLineDescent(int line) {
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800787 int descent = mLines[mColumns * line + DESCENT];
Gilles Debunnef3fa0cd2011-02-03 14:17:05 -0800788 if (mMaximumVisibleLineCount > 0 && line >= mMaximumVisibleLineCount - 1 && // -1 intended
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800789 line != mLineCount) {
790 descent += getBottomPadding();
791 }
792 return descent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800793 }
794
Gilles Debunne66111472010-11-19 11:04:37 -0800795 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800796 public int getLineStart(int line) {
797 return mLines[mColumns * line + START] & START_MASK;
798 }
799
Gilles Debunne66111472010-11-19 11:04:37 -0800800 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800801 public int getParagraphDirection(int line) {
802 return mLines[mColumns * line + DIR] >> DIR_SHIFT;
803 }
804
Gilles Debunne66111472010-11-19 11:04:37 -0800805 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800806 public boolean getLineContainsTab(int line) {
807 return (mLines[mColumns * line + TAB] & TAB_MASK) != 0;
808 }
809
Gilles Debunne66111472010-11-19 11:04:37 -0800810 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800811 public final Directions getLineDirections(int line) {
812 return mLineDirections[line];
813 }
814
Gilles Debunne66111472010-11-19 11:04:37 -0800815 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800816 public int getTopPadding() {
817 return mTopPadding;
818 }
819
Gilles Debunne66111472010-11-19 11:04:37 -0800820 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800821 public int getBottomPadding() {
822 return mBottomPadding;
823 }
824
825 @Override
826 public int getEllipsisCount(int line) {
827 if (mColumns < COLUMNS_ELLIPSIZE) {
828 return 0;
829 }
830
831 return mLines[mColumns * line + ELLIPSIS_COUNT];
832 }
833
834 @Override
835 public int getEllipsisStart(int line) {
836 if (mColumns < COLUMNS_ELLIPSIZE) {
837 return 0;
838 }
839
840 return mLines[mColumns * line + ELLIPSIS_START];
841 }
842
843 @Override
844 public int getEllipsizedWidth() {
845 return mEllipsizedWidth;
846 }
847
Romain Guye5ea4402011-08-01 14:01:37 -0700848 void prepare() {
849 mMeasured = MeasuredText.obtain();
850 }
Raph Levien26c87cf2014-08-28 12:53:10 -0700851
Romain Guye5ea4402011-08-01 14:01:37 -0700852 void finish() {
853 mMeasured = MeasuredText.recycle(mMeasured);
854 }
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800855
Anish Athalye88b5b0b2014-06-24 14:39:43 -0700856 // returns an array with terminal sentinel value -1 to indicate end
857 // this is so that arrays can be recycled instead of allocating new arrays
858 // every time
859 private static native int[] nLineBreakOpportunities(String locale, char[] text, int length, int[] recycle);
860
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800861 private int mLineCount;
862 private int mTopPadding, mBottomPadding;
863 private int mColumns;
864 private int mEllipsizedWidth;
865
866 private static final int COLUMNS_NORMAL = 3;
867 private static final int COLUMNS_ELLIPSIZE = 5;
868 private static final int START = 0;
869 private static final int DIR = START;
870 private static final int TAB = START;
871 private static final int TOP = 1;
872 private static final int DESCENT = 2;
873 private static final int ELLIPSIS_START = 3;
874 private static final int ELLIPSIS_COUNT = 4;
875
876 private int[] mLines;
877 private Directions[] mLineDirections;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700878 private int mMaximumVisibleLineCount = Integer.MAX_VALUE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800879
880 private static final int START_MASK = 0x1FFFFFFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800881 private static final int DIR_SHIFT = 30;
882 private static final int TAB_MASK = 0x20000000;
883
Doug Feltc982f602010-05-25 11:51:40 -0700884 private static final int TAB_INCREMENT = 20; // same as Layout, but that's private
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800885
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800886 private static final char CHAR_NEW_LINE = '\n';
887 private static final char CHAR_TAB = '\t';
888 private static final char CHAR_SPACE = ' ';
Raph Levien8d087c32013-03-29 09:25:48 -0700889 private static final char CHAR_ZWSP = '\u200B';
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800890
891 private static final double EXTRA_ROUNDING = 0.5;
Fabrice Di Megliocb332642011-09-23 19:08:04 -0700892
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800893 private static final int CHAR_FIRST_HIGH_SURROGATE = 0xD800;
894 private static final int CHAR_LAST_LOW_SURROGATE = 0xDFFF;
895
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800896 /*
Doug Felte8e45f22010-03-29 14:58:40 -0700897 * This is reused across calls to generate()
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800898 */
Doug Felte8e45f22010-03-29 14:58:40 -0700899 private MeasuredText mMeasured;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800900 private Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
901}