blob: 535eee1397edc70b9a861da21386da7b5fb34edc [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) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800164 mLineCount = 0;
165
166 int v = 0;
167 boolean needMultiply = (spacingmult != 1 || spacingadd != 0);
168
169 Paint.FontMetricsInt fm = mFontMetricsInt;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800170 int[] chooseHtv = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800171
Doug Felte8e45f22010-03-29 14:58:40 -0700172 MeasuredText measured = mMeasured;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800173
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800174 Spanned spanned = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800175 if (source instanceof Spanned)
176 spanned = (Spanned) source;
177
178 int DEFAULT_DIR = DIR_LEFT_TO_RIGHT; // XXX
179
Doug Felte8e45f22010-03-29 14:58:40 -0700180 int paraEnd;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800181 for (int paraStart = bufStart; paraStart <= bufEnd; paraStart = paraEnd) {
182 paraEnd = TextUtils.indexOf(source, CHAR_NEW_LINE, paraStart, bufEnd);
Doug Felte8e45f22010-03-29 14:58:40 -0700183 if (paraEnd < 0)
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800184 paraEnd = bufEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800185 else
Doug Felte8e45f22010-03-29 14:58:40 -0700186 paraEnd++;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800187
Doug Feltc982f602010-05-25 11:51:40 -0700188 int firstWidthLineLimit = mLineCount + 1;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800189 int firstWidth = outerWidth;
190 int restWidth = outerWidth;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800191
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800192 LineHeightSpan[] chooseHt = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800193
194 if (spanned != null) {
Eric Fischer74d31ef2010-08-05 15:29:36 -0700195 LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
Doug Felte8e45f22010-03-29 14:58:40 -0700196 LeadingMarginSpan.class);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800197 for (int i = 0; i < sp.length; i++) {
Mark Wagner7b5676e2009-10-16 11:44:23 -0700198 LeadingMarginSpan lms = sp[i];
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800199 firstWidth -= sp[i].getLeadingMargin(true);
200 restWidth -= sp[i].getLeadingMargin(false);
Doug Feltcb3791202011-07-07 11:57:48 -0700201
Doug Feltc982f602010-05-25 11:51:40 -0700202 // LeadingMarginSpan2 is odd. The count affects all
203 // leading margin spans, not just this particular one,
204 // and start from the top of the span, not the top of the
205 // paragraph.
206 if (lms instanceof LeadingMarginSpan2) {
207 LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
208 int lmsFirstLine = getLineForOffset(spanned.getSpanStart(lms2));
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800209 firstWidthLineLimit = 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
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800245 int width = firstWidth;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800246
247 float w = 0;
Gilles Debunnecd943a72012-06-07 17:54:47 -0700248 // here is the offset of the starting character of the line we are currently measuring
Doug Felte8e45f22010-03-29 14:58:40 -0700249 int here = paraStart;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800250
Gilles Debunnecd943a72012-06-07 17:54:47 -0700251 // ok is a character offset located after a word separator (space, tab, number...) where
252 // we would prefer to cut the current line. Equals to here when no such break was found.
Doug Felte8e45f22010-03-29 14:58:40 -0700253 int ok = paraStart;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800254 float okWidth = w;
255 int okAscent = 0, okDescent = 0, okTop = 0, okBottom = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800256
Gilles Debunnecd943a72012-06-07 17:54:47 -0700257 // fit is a character offset such that the [here, fit[ range fits in the allowed width.
258 // We will cut the line there if no ok position is found.
Doug Felte8e45f22010-03-29 14:58:40 -0700259 int fit = paraStart;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800260 float fitWidth = w;
261 int fitAscent = 0, fitDescent = 0, fitTop = 0, fitBottom = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800262
Doug Feltc982f602010-05-25 11:51:40 -0700263 boolean hasTabOrEmoji = false;
264 boolean hasTab = false;
265 TabStops tabStops = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800266
Gilles Debunnecd943a72012-06-07 17:54:47 -0700267 for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
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
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800283 int fmTop = fm.top;
284 int fmBottom = fm.bottom;
285 int fmAscent = fm.ascent;
286 int fmDescent = fm.descent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800287
Doug Felte8e45f22010-03-29 14:58:40 -0700288 for (int j = spanStart; j < spanEnd; j++) {
289 char c = chs[j - paraStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800290
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800291 if (c == CHAR_NEW_LINE) {
Gilles Debunne66111472010-11-19 11:04:37 -0800292 // intentionally left empty
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800293 } else if (c == CHAR_TAB) {
Doug Feltc982f602010-05-25 11:51:40 -0700294 if (hasTab == false) {
295 hasTab = true;
296 hasTabOrEmoji = true;
Kenny Root24ca4542010-06-22 23:46:35 -0700297 if (spanned != null) {
298 // First tab this para, check for tabstops
Eric Fischer74d31ef2010-08-05 15:29:36 -0700299 TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
Kenny Root24ca4542010-06-22 23:46:35 -0700300 paraEnd, TabStopSpan.class);
301 if (spans.length > 0) {
302 tabStops = new TabStops(TAB_INCREMENT, spans);
303 }
Doug Feltc982f602010-05-25 11:51:40 -0700304 }
305 }
306 if (tabStops != null) {
307 w = tabStops.nextTab(w);
308 } else {
309 w = TabStops.nextDefaultStop(w, TAB_INCREMENT);
310 }
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800311 } else if (c >= CHAR_FIRST_HIGH_SURROGATE && c <= CHAR_LAST_LOW_SURROGATE
312 && j + 1 < spanEnd) {
Doug Felte8e45f22010-03-29 14:58:40 -0700313 int emoji = Character.codePointAt(chs, j - paraStart);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800314
The Android Open Source Project10592532009-03-18 17:39:46 -0700315 if (emoji >= MIN_EMOJI && emoji <= MAX_EMOJI) {
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800316 Bitmap bm = EMOJI_FACTORY.getBitmapFromAndroidPua(emoji);
The Android Open Source Project10592532009-03-18 17:39:46 -0700317
318 if (bm != null) {
Eric Fischer423f0e42009-03-27 18:04:12 -0700319 Paint whichPaint;
320
321 if (spanned == null) {
322 whichPaint = paint;
323 } else {
324 whichPaint = mWorkPaint;
325 }
326
Gilles Debunned300e752011-10-17 13:37:36 -0700327 float wid = bm.getWidth() * -whichPaint.ascent() / bm.getHeight();
Eric Fischer423f0e42009-03-27 18:04:12 -0700328
329 w += wid;
Doug Feltc982f602010-05-25 11:51:40 -0700330 hasTabOrEmoji = true;
The Android Open Source Project10592532009-03-18 17:39:46 -0700331 j++;
332 } else {
Doug Felte8e45f22010-03-29 14:58:40 -0700333 w += widths[j - paraStart];
The Android Open Source Project10592532009-03-18 17:39:46 -0700334 }
335 } else {
Doug Felte8e45f22010-03-29 14:58:40 -0700336 w += widths[j - paraStart];
The Android Open Source Project10592532009-03-18 17:39:46 -0700337 }
338 } else {
Doug Felte8e45f22010-03-29 14:58:40 -0700339 w += widths[j - paraStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800340 }
341
Raph Levien8d087c32013-03-29 09:25:48 -0700342 boolean isSpaceOrTab = c == CHAR_SPACE || c == CHAR_TAB || c == CHAR_ZWSP;
Gilles Debunne81541492012-06-26 16:05:15 -0700343
344 if (w <= width || isSpaceOrTab) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800345 fitWidth = w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800346 fit = j + 1;
347
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800348 if (fmTop < fitTop)
349 fitTop = fmTop;
350 if (fmAscent < fitAscent)
351 fitAscent = fmAscent;
352 if (fmDescent > fitDescent)
353 fitDescent = fmDescent;
354 if (fmBottom > fitBottom)
355 fitBottom = fmBottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800356
Gilles Debunne81541492012-06-26 16:05:15 -0700357 // From the Unicode Line Breaking Algorithm (at least approximately)
358 boolean isLineBreak = isSpaceOrTab ||
Gilles Debunne81541492012-06-26 16:05:15 -0700359 // / is class SY and - is class HY, except when followed by a digit
360 ((c == CHAR_SLASH || c == CHAR_HYPHEN) &&
361 (j + 1 >= spanEnd || !Character.isDigit(chs[j + 1 - paraStart]))) ||
362 // Ideographs are class ID: breakpoints when adjacent, except for NS
363 // (non-starters), which can be broken after but not before
364 (c >= CHAR_FIRST_CJK && isIdeographic(c, true) &&
365 j + 1 < spanEnd && isIdeographic(chs[j + 1 - paraStart], false));
366
367 if (isLineBreak) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800368 okWidth = w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800369 ok = j + 1;
370
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800371 if (fitTop < okTop)
372 okTop = fitTop;
373 if (fitAscent < okAscent)
374 okAscent = fitAscent;
375 if (fitDescent > okDescent)
376 okDescent = fitDescent;
377 if (fitBottom > okBottom)
378 okBottom = fitBottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800379 }
Gilles Debunne4cf435d2011-01-04 15:35:29 -0800380 } else {
Gilles Debunned300e752011-10-17 13:37:36 -0700381 final boolean moreChars = (j + 1 < spanEnd);
382 int endPos;
383 int above, below, top, bottom;
384 float currentTextWidth;
Gilles Debunne32ea4ff2010-12-21 11:28:34 -0800385
Gilles Debunned300e752011-10-17 13:37:36 -0700386 if (ok != here) {
Gilles Debunned300e752011-10-17 13:37:36 -0700387 endPos = ok;
388 above = okAscent;
389 below = okDescent;
390 top = okTop;
391 bottom = okBottom;
392 currentTextWidth = okWidth;
393 } else if (fit != here) {
394 endPos = fit;
395 above = fitAscent;
396 below = fitDescent;
397 top = fitTop;
398 bottom = fitBottom;
399 currentTextWidth = fitWidth;
400 } else {
401 endPos = here + 1;
402 above = fm.ascent;
403 below = fm.descent;
404 top = fm.top;
405 bottom = fm.bottom;
406 currentTextWidth = widths[here - paraStart];
407 }
408
409 v = out(source, here, endPos,
410 above, below, top, bottom,
411 v, spacingmult, spacingadd, chooseHt,chooseHtv, fm, hasTabOrEmoji,
412 needMultiply, chdirs, dir, easy, bufEnd, includepad, trackpad,
413 chs, widths, paraStart, ellipsize, ellipsizedWidth,
414 currentTextWidth, paint, moreChars);
Gilles Debunnecd943a72012-06-07 17:54:47 -0700415
Gilles Debunned300e752011-10-17 13:37:36 -0700416 here = endPos;
Gilles Debunnecd943a72012-06-07 17:54:47 -0700417 j = here - 1; // restart j-span loop from here, compensating for the j++
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800418 ok = fit = here;
419 w = 0;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800420 fitAscent = fitDescent = fitTop = fitBottom = 0;
421 okAscent = okDescent = okTop = okBottom = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800422
Doug Feltc982f602010-05-25 11:51:40 -0700423 if (--firstWidthLineLimit <= 0) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800424 width = restWidth;
Mark Wagner7b5676e2009-10-16 11:44:23 -0700425 }
Gilles Debunnecd943a72012-06-07 17:54:47 -0700426
427 if (here < spanStart) {
428 // The text was cut before the beginning of the current span range.
429 // Exit the span loop, and get spanStart to start over from here.
430 measured.setPos(here);
431 spanEnd = here;
432 break;
433 }
Gilles Debunne81541492012-06-26 16:05:15 -0700434
435 if (mLineCount >= mMaximumVisibleLineCount) {
436 break;
437 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700438 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800439 }
440 }
441
Fabrice Di Meglioad0b0512011-10-04 17:21:26 -0700442 if (paraEnd != here && mLineCount < mMaximumVisibleLineCount) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800443 if ((fitTop | fitBottom | fitDescent | fitAscent) == 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800444 paint.getFontMetricsInt(fm);
445
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800446 fitTop = fm.top;
447 fitBottom = fm.bottom;
448 fitAscent = fm.ascent;
449 fitDescent = fm.descent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800450 }
451
452 // Log.e("text", "output rest " + here + " to " + end);
453
454 v = out(source,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800455 here, paraEnd, fitAscent, fitDescent,
456 fitTop, fitBottom,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800457 v,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800458 spacingmult, spacingadd, chooseHt,
459 chooseHtv, fm, hasTabOrEmoji,
Gilles Debunned300e752011-10-17 13:37:36 -0700460 needMultiply, chdirs, dir, easy, bufEnd,
461 includepad, trackpad, chs,
462 widths, paraStart, ellipsize,
463 ellipsizedWidth, w, paint, paraEnd != bufEnd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800464 }
465
Doug Felte8e45f22010-03-29 14:58:40 -0700466 paraStart = paraEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800467
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800468 if (paraEnd == bufEnd)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800469 break;
470 }
471
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700472 if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE) &&
Fabrice Di Meglioad0b0512011-10-04 17:21:26 -0700473 mLineCount < mMaximumVisibleLineCount) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800474 // Log.e("text", "output last " + bufEnd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800475
Fabrice Di Meglioe6318892013-06-18 20:03:41 -0700476 measured.setPara(source, bufStart, bufEnd, textDir);
477
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800478 paint.getFontMetricsInt(fm);
479
480 v = out(source,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800481 bufEnd, bufEnd, fm.ascent, fm.descent,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800482 fm.top, fm.bottom,
483 v,
484 spacingmult, spacingadd, null,
485 null, fm, false,
Fabrice Di Meglioe6318892013-06-18 20:03:41 -0700486 needMultiply, measured.mLevels, measured.mDir, measured.mEasy, bufEnd,
Gilles Debunned300e752011-10-17 13:37:36 -0700487 includepad, trackpad, null,
488 null, bufStart, ellipsize,
489 ellipsizedWidth, 0, paint, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800490 }
491 }
492
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800493 /**
494 * Returns true if the specified character is one of those specified
495 * as being Ideographic (class ID) by the Unicode Line Breaking Algorithm
496 * (http://www.unicode.org/unicode/reports/tr14/), and is therefore OK
497 * to break between a pair of.
Eric Fischer549d7242009-03-31 14:19:47 -0700498 *
499 * @param includeNonStarters also return true for category NS
500 * (non-starters), which can be broken
501 * after but not before.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800502 */
Eric Fischer549d7242009-03-31 14:19:47 -0700503 private static final boolean isIdeographic(char c, boolean includeNonStarters) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800504 if (c >= '\u2E80' && c <= '\u2FFF') {
505 return true; // CJK, KANGXI RADICALS, DESCRIPTION SYMBOLS
506 }
507 if (c == '\u3000') {
508 return true; // IDEOGRAPHIC SPACE
509 }
510 if (c >= '\u3040' && c <= '\u309F') {
Eric Fischer549d7242009-03-31 14:19:47 -0700511 if (!includeNonStarters) {
512 switch (c) {
513 case '\u3041': // # HIRAGANA LETTER SMALL A
514 case '\u3043': // # HIRAGANA LETTER SMALL I
515 case '\u3045': // # HIRAGANA LETTER SMALL U
516 case '\u3047': // # HIRAGANA LETTER SMALL E
517 case '\u3049': // # HIRAGANA LETTER SMALL O
518 case '\u3063': // # HIRAGANA LETTER SMALL TU
519 case '\u3083': // # HIRAGANA LETTER SMALL YA
520 case '\u3085': // # HIRAGANA LETTER SMALL YU
521 case '\u3087': // # HIRAGANA LETTER SMALL YO
522 case '\u308E': // # HIRAGANA LETTER SMALL WA
523 case '\u3095': // # HIRAGANA LETTER SMALL KA
524 case '\u3096': // # HIRAGANA LETTER SMALL KE
525 case '\u309B': // # KATAKANA-HIRAGANA VOICED SOUND MARK
526 case '\u309C': // # KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK
527 case '\u309D': // # HIRAGANA ITERATION MARK
528 case '\u309E': // # HIRAGANA VOICED ITERATION MARK
529 return false;
530 }
531 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800532 return true; // Hiragana (except small characters)
533 }
534 if (c >= '\u30A0' && c <= '\u30FF') {
Eric Fischer549d7242009-03-31 14:19:47 -0700535 if (!includeNonStarters) {
536 switch (c) {
537 case '\u30A0': // # KATAKANA-HIRAGANA DOUBLE HYPHEN
538 case '\u30A1': // # KATAKANA LETTER SMALL A
539 case '\u30A3': // # KATAKANA LETTER SMALL I
540 case '\u30A5': // # KATAKANA LETTER SMALL U
541 case '\u30A7': // # KATAKANA LETTER SMALL E
542 case '\u30A9': // # KATAKANA LETTER SMALL O
543 case '\u30C3': // # KATAKANA LETTER SMALL TU
544 case '\u30E3': // # KATAKANA LETTER SMALL YA
545 case '\u30E5': // # KATAKANA LETTER SMALL YU
546 case '\u30E7': // # KATAKANA LETTER SMALL YO
547 case '\u30EE': // # KATAKANA LETTER SMALL WA
548 case '\u30F5': // # KATAKANA LETTER SMALL KA
549 case '\u30F6': // # KATAKANA LETTER SMALL KE
550 case '\u30FB': // # KATAKANA MIDDLE DOT
551 case '\u30FC': // # KATAKANA-HIRAGANA PROLONGED SOUND MARK
552 case '\u30FD': // # KATAKANA ITERATION MARK
553 case '\u30FE': // # KATAKANA VOICED ITERATION MARK
554 return false;
555 }
556 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800557 return true; // Katakana (except small characters)
558 }
559 if (c >= '\u3400' && c <= '\u4DB5') {
560 return true; // CJK UNIFIED IDEOGRAPHS EXTENSION A
561 }
562 if (c >= '\u4E00' && c <= '\u9FBB') {
563 return true; // CJK UNIFIED IDEOGRAPHS
564 }
565 if (c >= '\uF900' && c <= '\uFAD9') {
566 return true; // CJK COMPATIBILITY IDEOGRAPHS
567 }
568 if (c >= '\uA000' && c <= '\uA48F') {
569 return true; // YI SYLLABLES
570 }
571 if (c >= '\uA490' && c <= '\uA4CF') {
572 return true; // YI RADICALS
573 }
574 if (c >= '\uFE62' && c <= '\uFE66') {
575 return true; // SMALL PLUS SIGN to SMALL EQUALS SIGN
576 }
577 if (c >= '\uFF10' && c <= '\uFF19') {
578 return true; // WIDE DIGITS
579 }
580
581 return false;
582 }
583
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800584 private int out(CharSequence text, int start, int end,
585 int above, int below, int top, int bottom, int v,
586 float spacingmult, float spacingadd,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800587 LineHeightSpan[] chooseHt, int[] chooseHtv,
Doug Feltc982f602010-05-25 11:51:40 -0700588 Paint.FontMetricsInt fm, boolean hasTabOrEmoji,
Gilles Debunned300e752011-10-17 13:37:36 -0700589 boolean needMultiply, byte[] chdirs, int dir,
590 boolean easy, int bufEnd, boolean includePad,
591 boolean trackPad, char[] chs,
592 float[] widths, int widthStart, TextUtils.TruncateAt ellipsize,
593 float ellipsisWidth, float textWidth,
594 TextPaint paint, boolean moreChars) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800595 int j = mLineCount;
596 int off = j * mColumns;
597 int want = off + mColumns + TOP;
598 int[] lines = mLines;
599
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800600 if (want >= lines.length) {
Adam Lesinski776abc22014-03-07 11:30:59 -0500601 Directions[] grow2 = ArrayUtils.newUnpaddedArray(
602 Directions.class, GrowingArrayUtils.growSize(want));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800603 System.arraycopy(mLineDirections, 0, grow2, 0,
604 mLineDirections.length);
605 mLineDirections = grow2;
Adam Lesinski776abc22014-03-07 11:30:59 -0500606
607 int[] grow = new int[grow2.length];
608 System.arraycopy(lines, 0, grow, 0, lines.length);
609 mLines = grow;
610 lines = grow;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800611 }
612
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800613 if (chooseHt != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800614 fm.ascent = above;
615 fm.descent = below;
616 fm.top = top;
617 fm.bottom = bottom;
618
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800619 for (int i = 0; i < chooseHt.length; i++) {
620 if (chooseHt[i] instanceof LineHeightSpan.WithDensity) {
621 ((LineHeightSpan.WithDensity) chooseHt[i]).
622 chooseHeight(text, start, end, chooseHtv[i], v, fm, paint);
Eric Fischera9f1dd02009-08-12 15:00:10 -0700623
624 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800625 chooseHt[i].chooseHeight(text, start, end, chooseHtv[i], v, fm);
Eric Fischera9f1dd02009-08-12 15:00:10 -0700626 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800627 }
628
629 above = fm.ascent;
630 below = fm.descent;
631 top = fm.top;
632 bottom = fm.bottom;
633 }
634
635 if (j == 0) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800636 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800637 mTopPadding = top - above;
638 }
639
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800640 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800641 above = top;
642 }
643 }
Gilles Debunned300e752011-10-17 13:37:36 -0700644 if (end == bufEnd) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800645 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800646 mBottomPadding = bottom - below;
647 }
648
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800649 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800650 below = bottom;
651 }
652 }
653
654 int extra;
655
656 if (needMultiply) {
Doug Felt10657582010-02-22 11:19:01 -0800657 double ex = (below - above) * (spacingmult - 1) + spacingadd;
658 if (ex >= 0) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800659 extra = (int)(ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -0800660 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800661 extra = -(int)(-ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -0800662 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800663 } else {
664 extra = 0;
665 }
666
667 lines[off + START] = start;
668 lines[off + TOP] = v;
669 lines[off + DESCENT] = below + extra;
670
671 v += (below - above) + extra;
672 lines[off + mColumns + START] = end;
673 lines[off + mColumns + TOP] = v;
674
Doug Feltc982f602010-05-25 11:51:40 -0700675 if (hasTabOrEmoji)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800676 lines[off + TAB] |= TAB_MASK;
677
Doug Felt9f7a4442010-03-01 12:45:56 -0800678 lines[off + DIR] |= dir << DIR_SHIFT;
679 Directions linedirs = DIRS_ALL_LEFT_TO_RIGHT;
680 // easy means all chars < the first RTL, so no emoji, no nothing
Doug Felt4e0c5e52010-03-15 16:56:02 -0700681 // XXX a run with no text or all spaces is easy but might be an empty
Doug Felt9f7a4442010-03-01 12:45:56 -0800682 // RTL paragraph. Make sure easy is false if this is the case.
683 if (easy) {
684 mLineDirections[j] = linedirs;
685 } else {
Gilles Debunnef3fa0cd2011-02-03 14:17:05 -0800686 mLineDirections[j] = AndroidBidi.directions(dir, chdirs, start - widthStart, chs,
687 start - widthStart, end - start);
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800688 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800689
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -0700690 if (ellipsize != null) {
691 // If there is only one line, then do any type of ellipsis except when it is MARQUEE
692 // if there are multiple lines, just allow END ellipsis on the last line
693 boolean firstLine = (j == 0);
694 boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700695 boolean forceEllipsis = moreChars && (mLineCount + 1 == mMaximumVisibleLineCount);
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -0700696
Fabrice Di Meglio34a126e2012-02-29 18:43:14 -0800697 boolean doEllipsis =
698 (((mMaximumVisibleLineCount == 1 && moreChars) || (firstLine && !moreChars)) &&
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -0700699 ellipsize != TextUtils.TruncateAt.MARQUEE) ||
700 (!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
701 ellipsize == TextUtils.TruncateAt.END);
702 if (doEllipsis) {
703 calculateEllipsis(start, end, widths, widthStart,
704 ellipsisWidth, ellipsize, j,
705 textWidth, paint, forceEllipsis);
706 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800707 }
708
709 mLineCount++;
710 return v;
711 }
712
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800713 private void calculateEllipsis(int lineStart, int lineEnd,
714 float[] widths, int widthStart,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800715 float avail, TextUtils.TruncateAt where,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700716 int line, float textWidth, TextPaint paint,
717 boolean forceEllipsis) {
718 if (textWidth <= avail && !forceEllipsis) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800719 // Everything fits!
720 mLines[mColumns * line + ELLIPSIS_START] = 0;
721 mLines[mColumns * line + ELLIPSIS_COUNT] = 0;
722 return;
723 }
724
Fabrice Di Megliocb332642011-09-23 19:08:04 -0700725 float ellipsisWidth = paint.measureText(
Fabrice Di Meglio8d44fff2012-06-13 15:45:38 -0700726 (where == TextUtils.TruncateAt.END_SMALL) ?
727 ELLIPSIS_TWO_DOTS : ELLIPSIS_NORMAL, 0, 1);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700728 int ellipsisStart = 0;
729 int ellipsisCount = 0;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800730 int len = lineEnd - lineStart;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800731
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700732 // We only support start ellipsis on a single line
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800733 if (where == TextUtils.TruncateAt.START) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700734 if (mMaximumVisibleLineCount == 1) {
735 float sum = 0;
736 int i;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800737
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700738 for (i = len; i >= 0; i--) {
739 float w = widths[i - 1 + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800740
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700741 if (w + sum + ellipsisWidth > avail) {
742 break;
743 }
744
745 sum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800746 }
747
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700748 ellipsisStart = 0;
749 ellipsisCount = i;
750 } else {
751 if (Log.isLoggable(TAG, Log.WARN)) {
752 Log.w(TAG, "Start Ellipsis only supported with one line");
753 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800754 }
Fabrice Di Megliocb332642011-09-23 19:08:04 -0700755 } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE ||
756 where == TextUtils.TruncateAt.END_SMALL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800757 float sum = 0;
758 int i;
759
760 for (i = 0; i < len; i++) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800761 float w = widths[i + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800762
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800763 if (w + sum + ellipsisWidth > avail) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800764 break;
765 }
766
767 sum += w;
768 }
769
770 ellipsisStart = i;
771 ellipsisCount = len - i;
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -0700772 if (forceEllipsis && ellipsisCount == 0 && len > 0) {
773 ellipsisStart = len - 1;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700774 ellipsisCount = 1;
775 }
776 } else {
777 // where = TextUtils.TruncateAt.MIDDLE We only support middle ellipsis on a single line
778 if (mMaximumVisibleLineCount == 1) {
779 float lsum = 0, rsum = 0;
780 int left = 0, right = len;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800781
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700782 float ravail = (avail - ellipsisWidth) / 2;
783 for (right = len; right >= 0; right--) {
784 float w = widths[right - 1 + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800785
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700786 if (w + rsum > ravail) {
787 break;
788 }
789
790 rsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800791 }
792
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700793 float lavail = avail - ellipsisWidth - rsum;
794 for (left = 0; left < right; left++) {
795 float w = widths[left + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800796
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700797 if (w + lsum > lavail) {
798 break;
799 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800800
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700801 lsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800802 }
803
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700804 ellipsisStart = left;
805 ellipsisCount = right - left;
806 } else {
807 if (Log.isLoggable(TAG, Log.WARN)) {
808 Log.w(TAG, "Middle Ellipsis only supported with one line");
809 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800810 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800811 }
812
813 mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
814 mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
815 }
816
Doug Felte8e45f22010-03-29 14:58:40 -0700817 // Override the base class so we can directly access our members,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800818 // rather than relying on member functions.
819 // The logic mirrors that of Layout.getLineForVertical
820 // FIXME: It may be faster to do a linear search for layouts without many lines.
Gilles Debunne66111472010-11-19 11:04:37 -0800821 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800822 public int getLineForVertical(int vertical) {
823 int high = mLineCount;
824 int low = -1;
825 int guess;
826 int[] lines = mLines;
827 while (high - low > 1) {
828 guess = (high + low) >> 1;
829 if (lines[mColumns * guess + TOP] > vertical){
830 high = guess;
831 } else {
832 low = guess;
833 }
834 }
835 if (low < 0) {
836 return 0;
837 } else {
838 return low;
839 }
840 }
841
Gilles Debunne66111472010-11-19 11:04:37 -0800842 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800843 public int getLineCount() {
844 return mLineCount;
845 }
846
Gilles Debunne66111472010-11-19 11:04:37 -0800847 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800848 public int getLineTop(int line) {
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800849 int top = mLines[mColumns * line + TOP];
850 if (mMaximumVisibleLineCount > 0 && line >= mMaximumVisibleLineCount &&
851 line != mLineCount) {
852 top += getBottomPadding();
853 }
854 return top;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800855 }
856
Gilles Debunne66111472010-11-19 11:04:37 -0800857 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800858 public int getLineDescent(int line) {
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800859 int descent = mLines[mColumns * line + DESCENT];
Gilles Debunnef3fa0cd2011-02-03 14:17:05 -0800860 if (mMaximumVisibleLineCount > 0 && line >= mMaximumVisibleLineCount - 1 && // -1 intended
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800861 line != mLineCount) {
862 descent += getBottomPadding();
863 }
864 return descent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800865 }
866
Gilles Debunne66111472010-11-19 11:04:37 -0800867 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800868 public int getLineStart(int line) {
869 return mLines[mColumns * line + START] & START_MASK;
870 }
871
Gilles Debunne66111472010-11-19 11:04:37 -0800872 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800873 public int getParagraphDirection(int line) {
874 return mLines[mColumns * line + DIR] >> DIR_SHIFT;
875 }
876
Gilles Debunne66111472010-11-19 11:04:37 -0800877 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800878 public boolean getLineContainsTab(int line) {
879 return (mLines[mColumns * line + TAB] & TAB_MASK) != 0;
880 }
881
Gilles Debunne66111472010-11-19 11:04:37 -0800882 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800883 public final Directions getLineDirections(int line) {
884 return mLineDirections[line];
885 }
886
Gilles Debunne66111472010-11-19 11:04:37 -0800887 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800888 public int getTopPadding() {
889 return mTopPadding;
890 }
891
Gilles Debunne66111472010-11-19 11:04:37 -0800892 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800893 public int getBottomPadding() {
894 return mBottomPadding;
895 }
896
897 @Override
898 public int getEllipsisCount(int line) {
899 if (mColumns < COLUMNS_ELLIPSIZE) {
900 return 0;
901 }
902
903 return mLines[mColumns * line + ELLIPSIS_COUNT];
904 }
905
906 @Override
907 public int getEllipsisStart(int line) {
908 if (mColumns < COLUMNS_ELLIPSIZE) {
909 return 0;
910 }
911
912 return mLines[mColumns * line + ELLIPSIS_START];
913 }
914
915 @Override
916 public int getEllipsizedWidth() {
917 return mEllipsizedWidth;
918 }
919
Romain Guye5ea4402011-08-01 14:01:37 -0700920 void prepare() {
921 mMeasured = MeasuredText.obtain();
922 }
923
924 void finish() {
925 mMeasured = MeasuredText.recycle(mMeasured);
926 }
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800927
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800928 private int mLineCount;
929 private int mTopPadding, mBottomPadding;
930 private int mColumns;
931 private int mEllipsizedWidth;
932
933 private static final int COLUMNS_NORMAL = 3;
934 private static final int COLUMNS_ELLIPSIZE = 5;
935 private static final int START = 0;
936 private static final int DIR = START;
937 private static final int TAB = START;
938 private static final int TOP = 1;
939 private static final int DESCENT = 2;
940 private static final int ELLIPSIS_START = 3;
941 private static final int ELLIPSIS_COUNT = 4;
942
943 private int[] mLines;
944 private Directions[] mLineDirections;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700945 private int mMaximumVisibleLineCount = Integer.MAX_VALUE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800946
947 private static final int START_MASK = 0x1FFFFFFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800948 private static final int DIR_SHIFT = 30;
949 private static final int TAB_MASK = 0x20000000;
950
Doug Feltc982f602010-05-25 11:51:40 -0700951 private static final int TAB_INCREMENT = 20; // same as Layout, but that's private
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800952
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800953 private static final char CHAR_FIRST_CJK = '\u2E80';
954
955 private static final char CHAR_NEW_LINE = '\n';
956 private static final char CHAR_TAB = '\t';
957 private static final char CHAR_SPACE = ' ';
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800958 private static final char CHAR_SLASH = '/';
959 private static final char CHAR_HYPHEN = '-';
Raph Levien8d087c32013-03-29 09:25:48 -0700960 private static final char CHAR_ZWSP = '\u200B';
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800961
962 private static final double EXTRA_ROUNDING = 0.5;
Fabrice Di Megliocb332642011-09-23 19:08:04 -0700963
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800964 private static final int CHAR_FIRST_HIGH_SURROGATE = 0xD800;
965 private static final int CHAR_LAST_LOW_SURROGATE = 0xDFFF;
966
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800967 /*
Doug Felte8e45f22010-03-29 14:58:40 -0700968 * This is reused across calls to generate()
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800969 */
Doug Felte8e45f22010-03-29 14:58:40 -0700970 private MeasuredText mMeasured;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800971 private Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
972}