blob: a03943dfa55a3642fff8ab4c1c363243fd765687 [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
Anish Athalyec8f9e622014-07-21 15:26:34 -070031import java.util.Arrays;
32
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
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080047 public StaticLayout(CharSequence source, TextPaint paint,
48 int width,
49 Alignment align, float spacingmult, float spacingadd,
50 boolean includepad) {
51 this(source, 0, source.length(), paint, width, align,
52 spacingmult, spacingadd, includepad);
53 }
54
Doug Feltcb3791202011-07-07 11:57:48 -070055 /**
56 * @hide
57 */
58 public StaticLayout(CharSequence source, TextPaint paint,
59 int width, Alignment align, TextDirectionHeuristic textDir,
60 float spacingmult, float spacingadd,
61 boolean includepad) {
62 this(source, 0, source.length(), paint, width, align, textDir,
63 spacingmult, spacingadd, includepad);
64 }
65
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080066 public StaticLayout(CharSequence source, int bufstart, int bufend,
67 TextPaint paint, int outerwidth,
68 Alignment align,
69 float spacingmult, float spacingadd,
70 boolean includepad) {
71 this(source, bufstart, bufend, paint, outerwidth, align,
72 spacingmult, spacingadd, includepad, null, 0);
73 }
74
Doug Feltcb3791202011-07-07 11:57:48 -070075 /**
76 * @hide
77 */
78 public StaticLayout(CharSequence source, int bufstart, int bufend,
79 TextPaint paint, int outerwidth,
80 Alignment align, TextDirectionHeuristic textDir,
81 float spacingmult, float spacingadd,
82 boolean includepad) {
83 this(source, bufstart, bufend, paint, outerwidth, align, textDir,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -070084 spacingmult, spacingadd, includepad, null, 0, Integer.MAX_VALUE);
Doug Feltcb3791202011-07-07 11:57:48 -070085}
86
87 public StaticLayout(CharSequence source, int bufstart, int bufend,
88 TextPaint paint, int outerwidth,
89 Alignment align,
90 float spacingmult, float spacingadd,
91 boolean includepad,
92 TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
93 this(source, bufstart, bufend, paint, outerwidth, align,
94 TextDirectionHeuristics.FIRSTSTRONG_LTR,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -070095 spacingmult, spacingadd, includepad, ellipsize, ellipsizedWidth, Integer.MAX_VALUE);
Doug Feltcb3791202011-07-07 11:57:48 -070096 }
97
98 /**
99 * @hide
100 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800101 public StaticLayout(CharSequence source, int bufstart, int bufend,
102 TextPaint paint, int outerwidth,
Doug Feltcb3791202011-07-07 11:57:48 -0700103 Alignment align, TextDirectionHeuristic textDir,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800104 float spacingmult, float spacingadd,
105 boolean includepad,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700106 TextUtils.TruncateAt ellipsize, int ellipsizedWidth, int maxLines) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800107 super((ellipsize == null)
Doug Felt4e0c5e52010-03-15 16:56:02 -0700108 ? source
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800109 : (source instanceof Spanned)
110 ? new SpannedEllipsizer(source)
111 : new Ellipsizer(source),
Doug Feltcb3791202011-07-07 11:57:48 -0700112 paint, outerwidth, align, textDir, spacingmult, spacingadd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800113
114 /*
115 * This is annoying, but we can't refer to the layout until
116 * superclass construction is finished, and the superclass
117 * constructor wants the reference to the display text.
Doug Felt4e0c5e52010-03-15 16:56:02 -0700118 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800119 * This will break if the superclass constructor ever actually
120 * cares about the content instead of just holding the reference.
121 */
122 if (ellipsize != null) {
123 Ellipsizer e = (Ellipsizer) getText();
124
125 e.mLayout = this;
126 e.mWidth = ellipsizedWidth;
127 e.mMethod = ellipsize;
128 mEllipsizedWidth = ellipsizedWidth;
129
130 mColumns = COLUMNS_ELLIPSIZE;
131 } else {
132 mColumns = COLUMNS_NORMAL;
133 mEllipsizedWidth = outerwidth;
134 }
135
Adam Lesinski776abc22014-03-07 11:30:59 -0500136 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2 * mColumns);
137 mLines = new int[mLineDirections.length];
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700138 mMaximumVisibleLineCount = maxLines;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800139
Doug Felte8e45f22010-03-29 14:58:40 -0700140 mMeasured = MeasuredText.obtain();
141
Gilles Debunned300e752011-10-17 13:37:36 -0700142 generate(source, bufstart, bufend, paint, outerwidth, textDir, spacingmult,
143 spacingadd, includepad, includepad, ellipsizedWidth,
144 ellipsize);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800145
Doug Felte8e45f22010-03-29 14:58:40 -0700146 mMeasured = MeasuredText.recycle(mMeasured);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800147 mFontMetricsInt = null;
148 }
149
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700150 /* package */ StaticLayout(CharSequence text) {
151 super(text, null, 0, null, 0, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800152
153 mColumns = COLUMNS_ELLIPSIZE;
Adam Lesinski776abc22014-03-07 11:30:59 -0500154 mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2 * mColumns);
155 mLines = new int[mLineDirections.length];
Gilles Debunne81541492012-06-26 16:05:15 -0700156 // FIXME This is never recycled
Doug Felte8e45f22010-03-29 14:58:40 -0700157 mMeasured = MeasuredText.obtain();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800158 }
159
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800160 /* package */ void generate(CharSequence source, int bufStart, int bufEnd,
161 TextPaint paint, int outerWidth,
Gilles Debunned300e752011-10-17 13:37:36 -0700162 TextDirectionHeuristic textDir, float spacingmult,
163 float spacingadd, boolean includepad,
164 boolean trackpad, float ellipsizedWidth,
165 TextUtils.TruncateAt ellipsize) {
Anish Athalyec8f9e622014-07-21 15:26:34 -0700166 LineBreaks lineBreaks = new LineBreaks();
167 // store span end locations
168 int[] spanEndCache = new int[4];
169 // store fontMetrics per span range
170 // must be a multiple of 4 (and > 0) (store top, bottom, ascent, and descent per range)
171 int[] fmCache = new int[4 * 4];
Anish Athalye88b5b0b2014-06-24 14:39:43 -0700172 final String localeLanguageTag = paint.getTextLocale().toLanguageTag();
173
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800174 mLineCount = 0;
175
176 int v = 0;
177 boolean needMultiply = (spacingmult != 1 || spacingadd != 0);
178
179 Paint.FontMetricsInt fm = mFontMetricsInt;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800180 int[] chooseHtv = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800181
Doug Felte8e45f22010-03-29 14:58:40 -0700182 MeasuredText measured = mMeasured;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800183
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800184 Spanned spanned = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800185 if (source instanceof Spanned)
186 spanned = (Spanned) source;
187
Doug Felte8e45f22010-03-29 14:58:40 -0700188 int paraEnd;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800189 for (int paraStart = bufStart; paraStart <= bufEnd; paraStart = paraEnd) {
190 paraEnd = TextUtils.indexOf(source, CHAR_NEW_LINE, paraStart, bufEnd);
Doug Felte8e45f22010-03-29 14:58:40 -0700191 if (paraEnd < 0)
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800192 paraEnd = bufEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800193 else
Doug Felte8e45f22010-03-29 14:58:40 -0700194 paraEnd++;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800195
Anish Athalyec8f9e622014-07-21 15:26:34 -0700196 int firstWidthLineCount = 1;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800197 int firstWidth = outerWidth;
198 int restWidth = outerWidth;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800199
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800200 LineHeightSpan[] chooseHt = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800201
202 if (spanned != null) {
Eric Fischer74d31ef2010-08-05 15:29:36 -0700203 LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
Doug Felte8e45f22010-03-29 14:58:40 -0700204 LeadingMarginSpan.class);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800205 for (int i = 0; i < sp.length; i++) {
Mark Wagner7b5676e2009-10-16 11:44:23 -0700206 LeadingMarginSpan lms = sp[i];
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800207 firstWidth -= sp[i].getLeadingMargin(true);
208 restWidth -= sp[i].getLeadingMargin(false);
Doug Feltcb3791202011-07-07 11:57:48 -0700209
Doug Feltc982f602010-05-25 11:51:40 -0700210 // LeadingMarginSpan2 is odd. The count affects all
Anish Athalyeab08c6d2014-08-08 12:09:58 -0700211 // leading margin spans, not just this particular one
Doug Feltc982f602010-05-25 11:51:40 -0700212 if (lms instanceof LeadingMarginSpan2) {
213 LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
Anish Athalyec8f9e622014-07-21 15:26:34 -0700214 firstWidthLineCount = Math.max(firstWidthLineCount,
215 lms2.getLeadingMarginLineCount());
Mark Wagner7b5676e2009-10-16 11:44:23 -0700216 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800217 }
218
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800219 chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800220
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800221 if (chooseHt.length != 0) {
222 if (chooseHtv == null ||
223 chooseHtv.length < chooseHt.length) {
Adam Lesinski776abc22014-03-07 11:30:59 -0500224 chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800225 }
226
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800227 for (int i = 0; i < chooseHt.length; i++) {
228 int o = spanned.getSpanStart(chooseHt[i]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800229
Doug Felte8e45f22010-03-29 14:58:40 -0700230 if (o < paraStart) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800231 // starts in this layout, before the
232 // current paragraph
233
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800234 chooseHtv[i] = getLineTop(getLineForOffset(o));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800235 } else {
236 // starts in this paragraph
237
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800238 chooseHtv[i] = v;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800239 }
240 }
241 }
242 }
243
Doug Feltcb3791202011-07-07 11:57:48 -0700244 measured.setPara(source, paraStart, paraEnd, textDir);
Doug Felte8e45f22010-03-29 14:58:40 -0700245 char[] chs = measured.mChars;
246 float[] widths = measured.mWidths;
247 byte[] chdirs = measured.mLevels;
248 int dir = measured.mDir;
249 boolean easy = measured.mEasy;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800250
Anish Athalyec8f9e622014-07-21 15:26:34 -0700251 // measurement has to be done before performing line breaking
252 // but we don't want to recompute fontmetrics or span ranges the
253 // second time, so we cache those and then use those stored values
254 int fmCacheCount = 0;
255 int spanEndCacheCount = 0;
Gilles Debunnecd943a72012-06-07 17:54:47 -0700256 for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
Anish Athalyec8f9e622014-07-21 15:26:34 -0700257 if (fmCacheCount * 4 >= fmCache.length) {
258 int[] grow = new int[fmCacheCount * 4 * 2];
259 System.arraycopy(fmCache, 0, grow, 0, fmCacheCount * 4);
260 fmCache = grow;
261 }
262
263 if (spanEndCacheCount >= spanEndCache.length) {
264 int[] grow = new int[spanEndCacheCount * 2];
265 System.arraycopy(spanEndCache, 0, grow, 0, spanEndCacheCount);
266 spanEndCache = grow;
267 }
Doug Felte8e45f22010-03-29 14:58:40 -0700268
Gilles Debunnecd943a72012-06-07 17:54:47 -0700269 if (spanned == null) {
270 spanEnd = paraEnd;
Doug Felt23241882010-06-02 14:41:06 -0700271 int spanLen = spanEnd - spanStart;
Gilles Debunnecd943a72012-06-07 17:54:47 -0700272 measured.addStyleRun(paint, spanLen, fm);
273 } else {
274 spanEnd = spanned.nextSpanTransition(spanStart, paraEnd,
275 MetricAffectingSpan.class);
276 int spanLen = spanEnd - spanStart;
277 MetricAffectingSpan[] spans =
Doug Felt23241882010-06-02 14:41:06 -0700278 spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class);
Gilles Debunnecd943a72012-06-07 17:54:47 -0700279 spans = TextUtils.removeEmptySpans(spans, spanned, MetricAffectingSpan.class);
280 measured.addStyleRun(paint, spans, spanLen, fm);
Doug Felt23241882010-06-02 14:41:06 -0700281 }
282
Anish Athalyec8f9e622014-07-21 15:26:34 -0700283 // the order of storage here (top, bottom, ascent, descent) has to match the code below
284 // where these values are retrieved
285 fmCache[fmCacheCount * 4 + 0] = fm.top;
286 fmCache[fmCacheCount * 4 + 1] = fm.bottom;
287 fmCache[fmCacheCount * 4 + 2] = fm.ascent;
288 fmCache[fmCacheCount * 4 + 3] = fm.descent;
289 fmCacheCount++;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800290
Anish Athalyec8f9e622014-07-21 15:26:34 -0700291 spanEndCache[spanEndCacheCount] = spanEnd;
292 spanEndCacheCount++;
293 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800294
Anish Athalyec8f9e622014-07-21 15:26:34 -0700295 // tab stop locations
296 int[] variableTabStops = null;
297 if (spanned != null) {
298 TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
299 paraEnd, TabStopSpan.class);
300 if (spans.length > 0) {
301 int[] stops = new int[spans.length];
302 for (int i = 0; i < spans.length; i++) {
303 stops[i] = spans[i].getTabStop();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800304 }
Anish Athalyec8f9e622014-07-21 15:26:34 -0700305 Arrays.sort(stops, 0, stops.length);
306 variableTabStops = stops;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800307 }
308 }
309
Anish Athalyec8f9e622014-07-21 15:26:34 -0700310 int breakCount = nComputeLineBreaks(localeLanguageTag, chs, widths, paraEnd - paraStart, firstWidth,
311 firstWidthLineCount, restWidth, variableTabStops, TAB_INCREMENT, lineBreaks,
312 lineBreaks.breaks, lineBreaks.widths, lineBreaks.flags, lineBreaks.breaks.length);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800313
Anish Athalyec8f9e622014-07-21 15:26:34 -0700314 int[] breaks = lineBreaks.breaks;
315 float[] lineWidths = lineBreaks.widths;
316 boolean[] flags = lineBreaks.flags;
317
318
319 // here is the offset of the starting character of the line we are currently measuring
320 int here = paraStart;
321
322 int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
323 int fmCacheIndex = 0;
324 int spanEndCacheIndex = 0;
325 int breakIndex = 0;
326 for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
327 // retrieve end of span
328 spanEnd = spanEndCache[spanEndCacheIndex++];
329
330 // retrieve cached metrics, order matches above
331 fm.top = fmCache[fmCacheIndex * 4 + 0];
332 fm.bottom = fmCache[fmCacheIndex * 4 + 1];
333 fm.ascent = fmCache[fmCacheIndex * 4 + 2];
334 fm.descent = fmCache[fmCacheIndex * 4 + 3];
335 fmCacheIndex++;
336
337 if (fm.top < fmTop) {
338 fmTop = fm.top;
339 }
340 if (fm.ascent < fmAscent) {
341 fmAscent = fm.ascent;
342 }
343 if (fm.descent > fmDescent) {
344 fmDescent = fm.descent;
345 }
346 if (fm.bottom > fmBottom) {
347 fmBottom = fm.bottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800348 }
349
Anish Athalyec8f9e622014-07-21 15:26:34 -0700350 // skip breaks ending before current span range
351 while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
352 breakIndex++;
353 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800354
Anish Athalyec8f9e622014-07-21 15:26:34 -0700355 while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
356 int endPos = paraStart + breaks[breakIndex];
357
358 boolean moreChars = (endPos < paraEnd); // XXX is this the right way to calculate this?
359
360 v = out(source, here, endPos,
361 fmAscent, fmDescent, fmTop, fmBottom,
362 v, spacingmult, spacingadd, chooseHt,chooseHtv, fm, flags[breakIndex],
363 needMultiply, chdirs, dir, easy, bufEnd, includepad, trackpad,
364 chs, widths, paraStart, ellipsize, ellipsizedWidth,
365 lineWidths[breakIndex], paint, moreChars);
366
367 if (endPos < spanEnd) {
368 // preserve metrics for current span
369 fmTop = fm.top;
370 fmBottom = fm.bottom;
371 fmAscent = fm.ascent;
372 fmDescent = fm.descent;
373 } else {
374 fmTop = fmBottom = fmAscent = fmDescent = 0;
375 }
376
377 here = endPos;
378 breakIndex++;
379
380 if (mLineCount >= mMaximumVisibleLineCount) {
381 return;
382 }
383 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800384 }
385
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800386 if (paraEnd == bufEnd)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800387 break;
388 }
389
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700390 if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE) &&
Fabrice Di Meglioad0b0512011-10-04 17:21:26 -0700391 mLineCount < mMaximumVisibleLineCount) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800392 // Log.e("text", "output last " + bufEnd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800393
Anish Athalye46514a72014-08-06 18:00:09 -0700394 measured.setPara(source, bufEnd, bufEnd, textDir);
Fabrice Di Meglioe6318892013-06-18 20:03:41 -0700395
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800396 paint.getFontMetricsInt(fm);
397
398 v = out(source,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800399 bufEnd, bufEnd, fm.ascent, fm.descent,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800400 fm.top, fm.bottom,
401 v,
402 spacingmult, spacingadd, null,
403 null, fm, false,
Fabrice Di Meglioe6318892013-06-18 20:03:41 -0700404 needMultiply, measured.mLevels, measured.mDir, measured.mEasy, bufEnd,
Gilles Debunned300e752011-10-17 13:37:36 -0700405 includepad, trackpad, null,
406 null, bufStart, ellipsize,
407 ellipsizedWidth, 0, paint, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800408 }
409 }
410
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800411 private int out(CharSequence text, int start, int end,
412 int above, int below, int top, int bottom, int v,
413 float spacingmult, float spacingadd,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800414 LineHeightSpan[] chooseHt, int[] chooseHtv,
Doug Feltc982f602010-05-25 11:51:40 -0700415 Paint.FontMetricsInt fm, boolean hasTabOrEmoji,
Gilles Debunned300e752011-10-17 13:37:36 -0700416 boolean needMultiply, byte[] chdirs, int dir,
417 boolean easy, int bufEnd, boolean includePad,
418 boolean trackPad, char[] chs,
419 float[] widths, int widthStart, TextUtils.TruncateAt ellipsize,
420 float ellipsisWidth, float textWidth,
421 TextPaint paint, boolean moreChars) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800422 int j = mLineCount;
423 int off = j * mColumns;
424 int want = off + mColumns + TOP;
425 int[] lines = mLines;
426
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800427 if (want >= lines.length) {
Adam Lesinski776abc22014-03-07 11:30:59 -0500428 Directions[] grow2 = ArrayUtils.newUnpaddedArray(
429 Directions.class, GrowingArrayUtils.growSize(want));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800430 System.arraycopy(mLineDirections, 0, grow2, 0,
431 mLineDirections.length);
432 mLineDirections = grow2;
Adam Lesinski776abc22014-03-07 11:30:59 -0500433
434 int[] grow = new int[grow2.length];
435 System.arraycopy(lines, 0, grow, 0, lines.length);
436 mLines = grow;
437 lines = grow;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800438 }
439
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800440 if (chooseHt != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800441 fm.ascent = above;
442 fm.descent = below;
443 fm.top = top;
444 fm.bottom = bottom;
445
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800446 for (int i = 0; i < chooseHt.length; i++) {
447 if (chooseHt[i] instanceof LineHeightSpan.WithDensity) {
448 ((LineHeightSpan.WithDensity) chooseHt[i]).
449 chooseHeight(text, start, end, chooseHtv[i], v, fm, paint);
Eric Fischera9f1dd02009-08-12 15:00:10 -0700450
451 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800452 chooseHt[i].chooseHeight(text, start, end, chooseHtv[i], v, fm);
Eric Fischera9f1dd02009-08-12 15:00:10 -0700453 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800454 }
455
456 above = fm.ascent;
457 below = fm.descent;
458 top = fm.top;
459 bottom = fm.bottom;
460 }
461
Raph Leviend97b0972014-04-24 12:51:35 -0700462 boolean firstLine = (j == 0);
463 boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);
464 boolean lastLine = currentLineIsTheLastVisibleOne || (end == bufEnd);
465
466 if (firstLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800467 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800468 mTopPadding = top - above;
469 }
470
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800471 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800472 above = top;
473 }
474 }
Raph Leviend97b0972014-04-24 12:51:35 -0700475
476 int extra;
477
478 if (lastLine) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800479 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800480 mBottomPadding = bottom - below;
481 }
482
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800483 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800484 below = bottom;
485 }
486 }
487
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800488
Raph Leviend97b0972014-04-24 12:51:35 -0700489 if (needMultiply && !lastLine) {
Doug Felt10657582010-02-22 11:19:01 -0800490 double ex = (below - above) * (spacingmult - 1) + spacingadd;
491 if (ex >= 0) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800492 extra = (int)(ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -0800493 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800494 extra = -(int)(-ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -0800495 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800496 } else {
497 extra = 0;
498 }
499
500 lines[off + START] = start;
501 lines[off + TOP] = v;
502 lines[off + DESCENT] = below + extra;
503
504 v += (below - above) + extra;
505 lines[off + mColumns + START] = end;
506 lines[off + mColumns + TOP] = v;
507
Doug Feltc982f602010-05-25 11:51:40 -0700508 if (hasTabOrEmoji)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800509 lines[off + TAB] |= TAB_MASK;
510
Doug Felt9f7a4442010-03-01 12:45:56 -0800511 lines[off + DIR] |= dir << DIR_SHIFT;
512 Directions linedirs = DIRS_ALL_LEFT_TO_RIGHT;
513 // easy means all chars < the first RTL, so no emoji, no nothing
Doug Felt4e0c5e52010-03-15 16:56:02 -0700514 // XXX a run with no text or all spaces is easy but might be an empty
Doug Felt9f7a4442010-03-01 12:45:56 -0800515 // RTL paragraph. Make sure easy is false if this is the case.
516 if (easy) {
517 mLineDirections[j] = linedirs;
518 } else {
Gilles Debunnef3fa0cd2011-02-03 14:17:05 -0800519 mLineDirections[j] = AndroidBidi.directions(dir, chdirs, start - widthStart, chs,
520 start - widthStart, end - start);
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800521 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800522
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -0700523 if (ellipsize != null) {
524 // If there is only one line, then do any type of ellipsis except when it is MARQUEE
525 // if there are multiple lines, just allow END ellipsis on the last line
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700526 boolean forceEllipsis = moreChars && (mLineCount + 1 == mMaximumVisibleLineCount);
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -0700527
Fabrice Di Meglio34a126e2012-02-29 18:43:14 -0800528 boolean doEllipsis =
529 (((mMaximumVisibleLineCount == 1 && moreChars) || (firstLine && !moreChars)) &&
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -0700530 ellipsize != TextUtils.TruncateAt.MARQUEE) ||
531 (!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
532 ellipsize == TextUtils.TruncateAt.END);
533 if (doEllipsis) {
534 calculateEllipsis(start, end, widths, widthStart,
535 ellipsisWidth, ellipsize, j,
536 textWidth, paint, forceEllipsis);
537 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800538 }
539
540 mLineCount++;
541 return v;
542 }
543
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800544 private void calculateEllipsis(int lineStart, int lineEnd,
545 float[] widths, int widthStart,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800546 float avail, TextUtils.TruncateAt where,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700547 int line, float textWidth, TextPaint paint,
548 boolean forceEllipsis) {
549 if (textWidth <= avail && !forceEllipsis) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800550 // Everything fits!
551 mLines[mColumns * line + ELLIPSIS_START] = 0;
552 mLines[mColumns * line + ELLIPSIS_COUNT] = 0;
553 return;
554 }
555
Fabrice Di Megliocb332642011-09-23 19:08:04 -0700556 float ellipsisWidth = paint.measureText(
Fabrice Di Meglio8d44fff2012-06-13 15:45:38 -0700557 (where == TextUtils.TruncateAt.END_SMALL) ?
558 ELLIPSIS_TWO_DOTS : ELLIPSIS_NORMAL, 0, 1);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700559 int ellipsisStart = 0;
560 int ellipsisCount = 0;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800561 int len = lineEnd - lineStart;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800562
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700563 // We only support start ellipsis on a single line
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800564 if (where == TextUtils.TruncateAt.START) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700565 if (mMaximumVisibleLineCount == 1) {
566 float sum = 0;
567 int i;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800568
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700569 for (i = len; i >= 0; i--) {
570 float w = widths[i - 1 + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800571
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700572 if (w + sum + ellipsisWidth > avail) {
573 break;
574 }
575
576 sum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800577 }
578
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700579 ellipsisStart = 0;
580 ellipsisCount = i;
581 } else {
582 if (Log.isLoggable(TAG, Log.WARN)) {
583 Log.w(TAG, "Start Ellipsis only supported with one line");
584 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800585 }
Fabrice Di Megliocb332642011-09-23 19:08:04 -0700586 } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE ||
587 where == TextUtils.TruncateAt.END_SMALL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800588 float sum = 0;
589 int i;
590
591 for (i = 0; i < len; i++) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800592 float w = widths[i + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800593
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800594 if (w + sum + ellipsisWidth > avail) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800595 break;
596 }
597
598 sum += w;
599 }
600
601 ellipsisStart = i;
602 ellipsisCount = len - i;
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -0700603 if (forceEllipsis && ellipsisCount == 0 && len > 0) {
604 ellipsisStart = len - 1;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700605 ellipsisCount = 1;
606 }
607 } else {
608 // where = TextUtils.TruncateAt.MIDDLE We only support middle ellipsis on a single line
609 if (mMaximumVisibleLineCount == 1) {
610 float lsum = 0, rsum = 0;
611 int left = 0, right = len;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800612
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700613 float ravail = (avail - ellipsisWidth) / 2;
614 for (right = len; right >= 0; right--) {
615 float w = widths[right - 1 + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800616
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700617 if (w + rsum > ravail) {
618 break;
619 }
620
621 rsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800622 }
623
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700624 float lavail = avail - ellipsisWidth - rsum;
625 for (left = 0; left < right; left++) {
626 float w = widths[left + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800627
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700628 if (w + lsum > lavail) {
629 break;
630 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800631
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700632 lsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800633 }
634
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700635 ellipsisStart = left;
636 ellipsisCount = right - left;
637 } else {
638 if (Log.isLoggable(TAG, Log.WARN)) {
639 Log.w(TAG, "Middle Ellipsis only supported with one line");
640 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800641 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800642 }
643
644 mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
645 mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
646 }
647
Doug Felte8e45f22010-03-29 14:58:40 -0700648 // Override the base class so we can directly access our members,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800649 // rather than relying on member functions.
650 // The logic mirrors that of Layout.getLineForVertical
651 // FIXME: It may be faster to do a linear search for layouts without many lines.
Gilles Debunne66111472010-11-19 11:04:37 -0800652 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800653 public int getLineForVertical(int vertical) {
654 int high = mLineCount;
655 int low = -1;
656 int guess;
657 int[] lines = mLines;
658 while (high - low > 1) {
659 guess = (high + low) >> 1;
660 if (lines[mColumns * guess + TOP] > vertical){
661 high = guess;
662 } else {
663 low = guess;
664 }
665 }
666 if (low < 0) {
667 return 0;
668 } else {
669 return low;
670 }
671 }
672
Gilles Debunne66111472010-11-19 11:04:37 -0800673 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800674 public int getLineCount() {
675 return mLineCount;
676 }
677
Gilles Debunne66111472010-11-19 11:04:37 -0800678 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800679 public int getLineTop(int line) {
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800680 int top = mLines[mColumns * line + TOP];
681 if (mMaximumVisibleLineCount > 0 && line >= mMaximumVisibleLineCount &&
682 line != mLineCount) {
683 top += getBottomPadding();
684 }
685 return top;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800686 }
687
Gilles Debunne66111472010-11-19 11:04:37 -0800688 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800689 public int getLineDescent(int line) {
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800690 int descent = mLines[mColumns * line + DESCENT];
Gilles Debunnef3fa0cd2011-02-03 14:17:05 -0800691 if (mMaximumVisibleLineCount > 0 && line >= mMaximumVisibleLineCount - 1 && // -1 intended
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800692 line != mLineCount) {
693 descent += getBottomPadding();
694 }
695 return descent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800696 }
697
Gilles Debunne66111472010-11-19 11:04:37 -0800698 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800699 public int getLineStart(int line) {
700 return mLines[mColumns * line + START] & START_MASK;
701 }
702
Gilles Debunne66111472010-11-19 11:04:37 -0800703 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800704 public int getParagraphDirection(int line) {
705 return mLines[mColumns * line + DIR] >> DIR_SHIFT;
706 }
707
Gilles Debunne66111472010-11-19 11:04:37 -0800708 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800709 public boolean getLineContainsTab(int line) {
710 return (mLines[mColumns * line + TAB] & TAB_MASK) != 0;
711 }
712
Gilles Debunne66111472010-11-19 11:04:37 -0800713 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800714 public final Directions getLineDirections(int line) {
715 return mLineDirections[line];
716 }
717
Gilles Debunne66111472010-11-19 11:04:37 -0800718 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800719 public int getTopPadding() {
720 return mTopPadding;
721 }
722
Gilles Debunne66111472010-11-19 11:04:37 -0800723 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800724 public int getBottomPadding() {
725 return mBottomPadding;
726 }
727
728 @Override
729 public int getEllipsisCount(int line) {
730 if (mColumns < COLUMNS_ELLIPSIZE) {
731 return 0;
732 }
733
734 return mLines[mColumns * line + ELLIPSIS_COUNT];
735 }
736
737 @Override
738 public int getEllipsisStart(int line) {
739 if (mColumns < COLUMNS_ELLIPSIZE) {
740 return 0;
741 }
742
743 return mLines[mColumns * line + ELLIPSIS_START];
744 }
745
746 @Override
747 public int getEllipsizedWidth() {
748 return mEllipsizedWidth;
749 }
750
Romain Guye5ea4402011-08-01 14:01:37 -0700751 void prepare() {
752 mMeasured = MeasuredText.obtain();
753 }
754
755 void finish() {
756 mMeasured = MeasuredText.recycle(mMeasured);
757 }
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800758
Anish Athalyec8f9e622014-07-21 15:26:34 -0700759 // populates LineBreaks and returns the number of breaks found
760 //
761 // the arrays inside the LineBreaks objects are passed in as well
762 // to reduce the number of JNI calls in the common case where the
763 // arrays do not have to be resized
764 private static native int nComputeLineBreaks(String locale, char[] text, float[] widths,
765 int length, float firstWidth, int firstWidthLineCount, float restWidth,
766 int[] variableTabStops, int defaultTabStop, LineBreaks recycle,
767 int[] recycleBreaks, float[] recycleWidths, boolean[] recycleFlags, int recycleLength);
Anish Athalye88b5b0b2014-06-24 14:39:43 -0700768
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800769 private int mLineCount;
770 private int mTopPadding, mBottomPadding;
771 private int mColumns;
772 private int mEllipsizedWidth;
773
774 private static final int COLUMNS_NORMAL = 3;
775 private static final int COLUMNS_ELLIPSIZE = 5;
776 private static final int START = 0;
777 private static final int DIR = START;
778 private static final int TAB = START;
779 private static final int TOP = 1;
780 private static final int DESCENT = 2;
781 private static final int ELLIPSIS_START = 3;
782 private static final int ELLIPSIS_COUNT = 4;
783
784 private int[] mLines;
785 private Directions[] mLineDirections;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700786 private int mMaximumVisibleLineCount = Integer.MAX_VALUE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800787
788 private static final int START_MASK = 0x1FFFFFFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800789 private static final int DIR_SHIFT = 30;
790 private static final int TAB_MASK = 0x20000000;
791
Doug Feltc982f602010-05-25 11:51:40 -0700792 private static final int TAB_INCREMENT = 20; // same as Layout, but that's private
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800793
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800794 private static final char CHAR_NEW_LINE = '\n';
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800795
796 private static final double EXTRA_ROUNDING = 0.5;
Fabrice Di Megliocb332642011-09-23 19:08:04 -0700797
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800798 /*
Doug Felte8e45f22010-03-29 14:58:40 -0700799 * This is reused across calls to generate()
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800800 */
Doug Felte8e45f22010-03-29 14:58:40 -0700801 private MeasuredText mMeasured;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800802 private Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
Anish Athalyec8f9e622014-07-21 15:26:34 -0700803
804 // This is used to return three arrays from a single JNI call when
805 // performing line breaking
806 private static class LineBreaks {
807 private static final int INITIAL_SIZE = 16;
808 public int[] breaks = new int[INITIAL_SIZE];
809 public float[] widths = new float[INITIAL_SIZE];
810 public boolean[] flags = new boolean[INITIAL_SIZE]; // hasTabOrEmoji
811 // breaks, widths, and flags should all have the same length
812 }
813
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800814}