blob: e7d6fda8e8f03314fb414024c2ec74e472713c2f [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;
29
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080030/**
31 * StaticLayout is a Layout for text that will not be edited after it
32 * is laid out. Use {@link DynamicLayout} for text that may change.
33 * <p>This is used by widgets to control text layout. You should not need
34 * to use this class directly unless you are implementing your own widget
35 * or custom display object, or would be tempted to call
Doug Felt4e0c5e52010-03-15 16:56:02 -070036 * {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int,
37 * float, float, android.graphics.Paint)
38 * Canvas.drawText()} directly.</p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080039 */
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -080040public class StaticLayout extends Layout {
41
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -070042 static final String TAG = "StaticLayout";
43
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080044 public StaticLayout(CharSequence source, TextPaint paint,
45 int width,
46 Alignment align, float spacingmult, float spacingadd,
47 boolean includepad) {
48 this(source, 0, source.length(), paint, width, align,
49 spacingmult, spacingadd, includepad);
50 }
51
Doug Feltcb3791202011-07-07 11:57:48 -070052 /**
53 * @hide
54 */
55 public StaticLayout(CharSequence source, TextPaint paint,
56 int width, Alignment align, TextDirectionHeuristic textDir,
57 float spacingmult, float spacingadd,
58 boolean includepad) {
59 this(source, 0, source.length(), paint, width, align, textDir,
60 spacingmult, spacingadd, includepad);
61 }
62
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080063 public StaticLayout(CharSequence source, int bufstart, int bufend,
64 TextPaint paint, int outerwidth,
65 Alignment align,
66 float spacingmult, float spacingadd,
67 boolean includepad) {
68 this(source, bufstart, bufend, paint, outerwidth, align,
69 spacingmult, spacingadd, includepad, null, 0);
70 }
71
Doug Feltcb3791202011-07-07 11:57:48 -070072 /**
73 * @hide
74 */
75 public StaticLayout(CharSequence source, int bufstart, int bufend,
76 TextPaint paint, int outerwidth,
77 Alignment align, TextDirectionHeuristic textDir,
78 float spacingmult, float spacingadd,
79 boolean includepad) {
80 this(source, bufstart, bufend, paint, outerwidth, align, textDir,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -070081 spacingmult, spacingadd, includepad, null, 0, Integer.MAX_VALUE);
Doug Feltcb3791202011-07-07 11:57:48 -070082}
83
84 public StaticLayout(CharSequence source, int bufstart, int bufend,
85 TextPaint paint, int outerwidth,
86 Alignment align,
87 float spacingmult, float spacingadd,
88 boolean includepad,
89 TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
90 this(source, bufstart, bufend, paint, outerwidth, align,
91 TextDirectionHeuristics.FIRSTSTRONG_LTR,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -070092 spacingmult, spacingadd, includepad, ellipsize, ellipsizedWidth, Integer.MAX_VALUE);
Doug Feltcb3791202011-07-07 11:57:48 -070093 }
94
95 /**
96 * @hide
97 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080098 public StaticLayout(CharSequence source, int bufstart, int bufend,
99 TextPaint paint, int outerwidth,
Doug Feltcb3791202011-07-07 11:57:48 -0700100 Alignment align, TextDirectionHeuristic textDir,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800101 float spacingmult, float spacingadd,
102 boolean includepad,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700103 TextUtils.TruncateAt ellipsize, int ellipsizedWidth, int maxLines) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800104 super((ellipsize == null)
Doug Felt4e0c5e52010-03-15 16:56:02 -0700105 ? source
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800106 : (source instanceof Spanned)
107 ? new SpannedEllipsizer(source)
108 : new Ellipsizer(source),
Doug Feltcb3791202011-07-07 11:57:48 -0700109 paint, outerwidth, align, textDir, spacingmult, spacingadd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800110
111 /*
112 * This is annoying, but we can't refer to the layout until
113 * superclass construction is finished, and the superclass
114 * constructor wants the reference to the display text.
Doug Felt4e0c5e52010-03-15 16:56:02 -0700115 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800116 * This will break if the superclass constructor ever actually
117 * cares about the content instead of just holding the reference.
118 */
119 if (ellipsize != null) {
120 Ellipsizer e = (Ellipsizer) getText();
121
122 e.mLayout = this;
123 e.mWidth = ellipsizedWidth;
124 e.mMethod = ellipsize;
125 mEllipsizedWidth = ellipsizedWidth;
126
127 mColumns = COLUMNS_ELLIPSIZE;
128 } else {
129 mColumns = COLUMNS_NORMAL;
130 mEllipsizedWidth = outerwidth;
131 }
132
133 mLines = new int[ArrayUtils.idealIntArraySize(2 * mColumns)];
134 mLineDirections = new Directions[
135 ArrayUtils.idealIntArraySize(2 * mColumns)];
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;
152 mLines = new int[ArrayUtils.idealIntArraySize(2 * mColumns)];
Gilles Debunne81541492012-06-26 16:05:15 -0700153 mLineDirections = new Directions[ArrayUtils.idealIntArraySize(2 * mColumns)];
154 // 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) {
218 chooseHtv = new int[ArrayUtils.idealIntArraySize(
219 chooseHt.length)];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800220 }
221
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800222 for (int i = 0; i < chooseHt.length; i++) {
223 int o = spanned.getSpanStart(chooseHt[i]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800224
Doug Felte8e45f22010-03-29 14:58:40 -0700225 if (o < paraStart) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800226 // starts in this layout, before the
227 // current paragraph
228
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800229 chooseHtv[i] = getLineTop(getLineForOffset(o));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800230 } else {
231 // starts in this paragraph
232
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800233 chooseHtv[i] = v;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800234 }
235 }
236 }
237 }
238
Doug Feltcb3791202011-07-07 11:57:48 -0700239 measured.setPara(source, paraStart, paraEnd, textDir);
Doug Felte8e45f22010-03-29 14:58:40 -0700240 char[] chs = measured.mChars;
241 float[] widths = measured.mWidths;
242 byte[] chdirs = measured.mLevels;
243 int dir = measured.mDir;
244 boolean easy = measured.mEasy;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800245
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800246 int width = firstWidth;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800247
248 float w = 0;
Gilles Debunnecd943a72012-06-07 17:54:47 -0700249 // here is the offset of the starting character of the line we are currently measuring
Doug Felte8e45f22010-03-29 14:58:40 -0700250 int here = paraStart;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800251
Gilles Debunnecd943a72012-06-07 17:54:47 -0700252 // ok is a character offset located after a word separator (space, tab, number...) where
253 // we would prefer to cut the current line. Equals to here when no such break was found.
Doug Felte8e45f22010-03-29 14:58:40 -0700254 int ok = paraStart;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800255 float okWidth = w;
256 int okAscent = 0, okDescent = 0, okTop = 0, okBottom = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800257
Gilles Debunnecd943a72012-06-07 17:54:47 -0700258 // fit is a character offset such that the [here, fit[ range fits in the allowed width.
259 // We will cut the line there if no ok position is found.
Doug Felte8e45f22010-03-29 14:58:40 -0700260 int fit = paraStart;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800261 float fitWidth = w;
262 int fitAscent = 0, fitDescent = 0, fitTop = 0, fitBottom = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800263
Doug Feltc982f602010-05-25 11:51:40 -0700264 boolean hasTabOrEmoji = false;
265 boolean hasTab = false;
266 TabStops tabStops = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800267
Gilles Debunnecd943a72012-06-07 17:54:47 -0700268 for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
Doug Felte8e45f22010-03-29 14:58:40 -0700269
Gilles Debunnecd943a72012-06-07 17:54:47 -0700270 if (spanned == null) {
271 spanEnd = paraEnd;
Doug Felt23241882010-06-02 14:41:06 -0700272 int spanLen = spanEnd - spanStart;
Gilles Debunnecd943a72012-06-07 17:54:47 -0700273 measured.addStyleRun(paint, spanLen, fm);
274 } else {
275 spanEnd = spanned.nextSpanTransition(spanStart, paraEnd,
276 MetricAffectingSpan.class);
277 int spanLen = spanEnd - spanStart;
278 MetricAffectingSpan[] spans =
Doug Felt23241882010-06-02 14:41:06 -0700279 spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class);
Gilles Debunnecd943a72012-06-07 17:54:47 -0700280 spans = TextUtils.removeEmptySpans(spans, spanned, MetricAffectingSpan.class);
281 measured.addStyleRun(paint, spans, spanLen, fm);
Doug Felt23241882010-06-02 14:41:06 -0700282 }
283
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800284 int fmTop = fm.top;
285 int fmBottom = fm.bottom;
286 int fmAscent = fm.ascent;
287 int fmDescent = fm.descent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800288
Doug Felte8e45f22010-03-29 14:58:40 -0700289 for (int j = spanStart; j < spanEnd; j++) {
290 char c = chs[j - paraStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800291
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800292 if (c == CHAR_NEW_LINE) {
Gilles Debunne66111472010-11-19 11:04:37 -0800293 // intentionally left empty
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800294 } else if (c == CHAR_TAB) {
Doug Feltc982f602010-05-25 11:51:40 -0700295 if (hasTab == false) {
296 hasTab = true;
297 hasTabOrEmoji = true;
Kenny Root24ca4542010-06-22 23:46:35 -0700298 if (spanned != null) {
299 // First tab this para, check for tabstops
Eric Fischer74d31ef2010-08-05 15:29:36 -0700300 TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
Kenny Root24ca4542010-06-22 23:46:35 -0700301 paraEnd, TabStopSpan.class);
302 if (spans.length > 0) {
303 tabStops = new TabStops(TAB_INCREMENT, spans);
304 }
Doug Feltc982f602010-05-25 11:51:40 -0700305 }
306 }
307 if (tabStops != null) {
308 w = tabStops.nextTab(w);
309 } else {
310 w = TabStops.nextDefaultStop(w, TAB_INCREMENT);
311 }
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800312 } else if (c >= CHAR_FIRST_HIGH_SURROGATE && c <= CHAR_LAST_LOW_SURROGATE
313 && j + 1 < spanEnd) {
Doug Felte8e45f22010-03-29 14:58:40 -0700314 int emoji = Character.codePointAt(chs, j - paraStart);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800315
The Android Open Source Project10592532009-03-18 17:39:46 -0700316 if (emoji >= MIN_EMOJI && emoji <= MAX_EMOJI) {
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800317 Bitmap bm = EMOJI_FACTORY.getBitmapFromAndroidPua(emoji);
The Android Open Source Project10592532009-03-18 17:39:46 -0700318
319 if (bm != null) {
Eric Fischer423f0e42009-03-27 18:04:12 -0700320 Paint whichPaint;
321
322 if (spanned == null) {
323 whichPaint = paint;
324 } else {
325 whichPaint = mWorkPaint;
326 }
327
Gilles Debunned300e752011-10-17 13:37:36 -0700328 float wid = bm.getWidth() * -whichPaint.ascent() / bm.getHeight();
Eric Fischer423f0e42009-03-27 18:04:12 -0700329
330 w += wid;
Doug Feltc982f602010-05-25 11:51:40 -0700331 hasTabOrEmoji = true;
The Android Open Source Project10592532009-03-18 17:39:46 -0700332 j++;
333 } else {
Doug Felte8e45f22010-03-29 14:58:40 -0700334 w += widths[j - paraStart];
The Android Open Source Project10592532009-03-18 17:39:46 -0700335 }
336 } else {
Doug Felte8e45f22010-03-29 14:58:40 -0700337 w += widths[j - paraStart];
The Android Open Source Project10592532009-03-18 17:39:46 -0700338 }
339 } else {
Doug Felte8e45f22010-03-29 14:58:40 -0700340 w += widths[j - paraStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800341 }
342
Raph Levien8d087c32013-03-29 09:25:48 -0700343 boolean isSpaceOrTab = c == CHAR_SPACE || c == CHAR_TAB || c == CHAR_ZWSP;
Gilles Debunne81541492012-06-26 16:05:15 -0700344
345 if (w <= width || isSpaceOrTab) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800346 fitWidth = w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800347 fit = j + 1;
348
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800349 if (fmTop < fitTop)
350 fitTop = fmTop;
351 if (fmAscent < fitAscent)
352 fitAscent = fmAscent;
353 if (fmDescent > fitDescent)
354 fitDescent = fmDescent;
355 if (fmBottom > fitBottom)
356 fitBottom = fmBottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800357
Gilles Debunne81541492012-06-26 16:05:15 -0700358 // From the Unicode Line Breaking Algorithm (at least approximately)
359 boolean isLineBreak = isSpaceOrTab ||
Gilles Debunne81541492012-06-26 16:05:15 -0700360 // / is class SY and - is class HY, except when followed by a digit
361 ((c == CHAR_SLASH || c == CHAR_HYPHEN) &&
362 (j + 1 >= spanEnd || !Character.isDigit(chs[j + 1 - paraStart]))) ||
363 // Ideographs are class ID: breakpoints when adjacent, except for NS
364 // (non-starters), which can be broken after but not before
365 (c >= CHAR_FIRST_CJK && isIdeographic(c, true) &&
366 j + 1 < spanEnd && isIdeographic(chs[j + 1 - paraStart], false));
367
368 if (isLineBreak) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800369 okWidth = w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800370 ok = j + 1;
371
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800372 if (fitTop < okTop)
373 okTop = fitTop;
374 if (fitAscent < okAscent)
375 okAscent = fitAscent;
376 if (fitDescent > okDescent)
377 okDescent = fitDescent;
378 if (fitBottom > okBottom)
379 okBottom = fitBottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800380 }
Gilles Debunne4cf435d2011-01-04 15:35:29 -0800381 } else {
Gilles Debunned300e752011-10-17 13:37:36 -0700382 final boolean moreChars = (j + 1 < spanEnd);
383 int endPos;
384 int above, below, top, bottom;
385 float currentTextWidth;
Gilles Debunne32ea4ff2010-12-21 11:28:34 -0800386
Gilles Debunned300e752011-10-17 13:37:36 -0700387 if (ok != here) {
Gilles Debunned300e752011-10-17 13:37:36 -0700388 endPos = ok;
389 above = okAscent;
390 below = okDescent;
391 top = okTop;
392 bottom = okBottom;
393 currentTextWidth = okWidth;
394 } else if (fit != here) {
395 endPos = fit;
396 above = fitAscent;
397 below = fitDescent;
398 top = fitTop;
399 bottom = fitBottom;
400 currentTextWidth = fitWidth;
401 } else {
402 endPos = here + 1;
403 above = fm.ascent;
404 below = fm.descent;
405 top = fm.top;
406 bottom = fm.bottom;
407 currentTextWidth = widths[here - paraStart];
408 }
409
410 v = out(source, here, endPos,
411 above, below, top, bottom,
412 v, spacingmult, spacingadd, chooseHt,chooseHtv, fm, hasTabOrEmoji,
413 needMultiply, chdirs, dir, easy, bufEnd, includepad, trackpad,
414 chs, widths, paraStart, ellipsize, ellipsizedWidth,
415 currentTextWidth, paint, moreChars);
Gilles Debunnecd943a72012-06-07 17:54:47 -0700416
Gilles Debunned300e752011-10-17 13:37:36 -0700417 here = endPos;
Gilles Debunnecd943a72012-06-07 17:54:47 -0700418 j = here - 1; // restart j-span loop from here, compensating for the j++
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800419 ok = fit = here;
420 w = 0;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800421 fitAscent = fitDescent = fitTop = fitBottom = 0;
422 okAscent = okDescent = okTop = okBottom = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800423
Doug Feltc982f602010-05-25 11:51:40 -0700424 if (--firstWidthLineLimit <= 0) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800425 width = restWidth;
Mark Wagner7b5676e2009-10-16 11:44:23 -0700426 }
Gilles Debunnecd943a72012-06-07 17:54:47 -0700427
428 if (here < spanStart) {
429 // The text was cut before the beginning of the current span range.
430 // Exit the span loop, and get spanStart to start over from here.
431 measured.setPos(here);
432 spanEnd = here;
433 break;
434 }
Gilles Debunne81541492012-06-26 16:05:15 -0700435
436 if (mLineCount >= mMaximumVisibleLineCount) {
437 break;
438 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700439 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800440 }
441 }
442
Fabrice Di Meglioad0b0512011-10-04 17:21:26 -0700443 if (paraEnd != here && mLineCount < mMaximumVisibleLineCount) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800444 if ((fitTop | fitBottom | fitDescent | fitAscent) == 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800445 paint.getFontMetricsInt(fm);
446
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800447 fitTop = fm.top;
448 fitBottom = fm.bottom;
449 fitAscent = fm.ascent;
450 fitDescent = fm.descent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800451 }
452
453 // Log.e("text", "output rest " + here + " to " + end);
454
455 v = out(source,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800456 here, paraEnd, fitAscent, fitDescent,
457 fitTop, fitBottom,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800458 v,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800459 spacingmult, spacingadd, chooseHt,
460 chooseHtv, fm, hasTabOrEmoji,
Gilles Debunned300e752011-10-17 13:37:36 -0700461 needMultiply, chdirs, dir, easy, bufEnd,
462 includepad, trackpad, chs,
463 widths, paraStart, ellipsize,
464 ellipsizedWidth, w, paint, paraEnd != bufEnd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800465 }
466
Doug Felte8e45f22010-03-29 14:58:40 -0700467 paraStart = paraEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800468
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800469 if (paraEnd == bufEnd)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800470 break;
471 }
472
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700473 if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE) &&
Fabrice Di Meglioad0b0512011-10-04 17:21:26 -0700474 mLineCount < mMaximumVisibleLineCount) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800475 // Log.e("text", "output last " + bufEnd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800476
Fabrice Di Meglioe6318892013-06-18 20:03:41 -0700477 measured.setPara(source, bufStart, bufEnd, textDir);
478
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800479 paint.getFontMetricsInt(fm);
480
481 v = out(source,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800482 bufEnd, bufEnd, fm.ascent, fm.descent,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800483 fm.top, fm.bottom,
484 v,
485 spacingmult, spacingadd, null,
486 null, fm, false,
Fabrice Di Meglioe6318892013-06-18 20:03:41 -0700487 needMultiply, measured.mLevels, measured.mDir, measured.mEasy, bufEnd,
Gilles Debunned300e752011-10-17 13:37:36 -0700488 includepad, trackpad, null,
489 null, bufStart, ellipsize,
490 ellipsizedWidth, 0, paint, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800491 }
492 }
493
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800494 /**
495 * Returns true if the specified character is one of those specified
496 * as being Ideographic (class ID) by the Unicode Line Breaking Algorithm
497 * (http://www.unicode.org/unicode/reports/tr14/), and is therefore OK
498 * to break between a pair of.
Eric Fischer549d7242009-03-31 14:19:47 -0700499 *
500 * @param includeNonStarters also return true for category NS
501 * (non-starters), which can be broken
502 * after but not before.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800503 */
Eric Fischer549d7242009-03-31 14:19:47 -0700504 private static final boolean isIdeographic(char c, boolean includeNonStarters) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800505 if (c >= '\u2E80' && c <= '\u2FFF') {
506 return true; // CJK, KANGXI RADICALS, DESCRIPTION SYMBOLS
507 }
508 if (c == '\u3000') {
509 return true; // IDEOGRAPHIC SPACE
510 }
511 if (c >= '\u3040' && c <= '\u309F') {
Eric Fischer549d7242009-03-31 14:19:47 -0700512 if (!includeNonStarters) {
513 switch (c) {
514 case '\u3041': // # HIRAGANA LETTER SMALL A
515 case '\u3043': // # HIRAGANA LETTER SMALL I
516 case '\u3045': // # HIRAGANA LETTER SMALL U
517 case '\u3047': // # HIRAGANA LETTER SMALL E
518 case '\u3049': // # HIRAGANA LETTER SMALL O
519 case '\u3063': // # HIRAGANA LETTER SMALL TU
520 case '\u3083': // # HIRAGANA LETTER SMALL YA
521 case '\u3085': // # HIRAGANA LETTER SMALL YU
522 case '\u3087': // # HIRAGANA LETTER SMALL YO
523 case '\u308E': // # HIRAGANA LETTER SMALL WA
524 case '\u3095': // # HIRAGANA LETTER SMALL KA
525 case '\u3096': // # HIRAGANA LETTER SMALL KE
526 case '\u309B': // # KATAKANA-HIRAGANA VOICED SOUND MARK
527 case '\u309C': // # KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK
528 case '\u309D': // # HIRAGANA ITERATION MARK
529 case '\u309E': // # HIRAGANA VOICED ITERATION MARK
530 return false;
531 }
532 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800533 return true; // Hiragana (except small characters)
534 }
535 if (c >= '\u30A0' && c <= '\u30FF') {
Eric Fischer549d7242009-03-31 14:19:47 -0700536 if (!includeNonStarters) {
537 switch (c) {
538 case '\u30A0': // # KATAKANA-HIRAGANA DOUBLE HYPHEN
539 case '\u30A1': // # KATAKANA LETTER SMALL A
540 case '\u30A3': // # KATAKANA LETTER SMALL I
541 case '\u30A5': // # KATAKANA LETTER SMALL U
542 case '\u30A7': // # KATAKANA LETTER SMALL E
543 case '\u30A9': // # KATAKANA LETTER SMALL O
544 case '\u30C3': // # KATAKANA LETTER SMALL TU
545 case '\u30E3': // # KATAKANA LETTER SMALL YA
546 case '\u30E5': // # KATAKANA LETTER SMALL YU
547 case '\u30E7': // # KATAKANA LETTER SMALL YO
548 case '\u30EE': // # KATAKANA LETTER SMALL WA
549 case '\u30F5': // # KATAKANA LETTER SMALL KA
550 case '\u30F6': // # KATAKANA LETTER SMALL KE
551 case '\u30FB': // # KATAKANA MIDDLE DOT
552 case '\u30FC': // # KATAKANA-HIRAGANA PROLONGED SOUND MARK
553 case '\u30FD': // # KATAKANA ITERATION MARK
554 case '\u30FE': // # KATAKANA VOICED ITERATION MARK
555 return false;
556 }
557 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800558 return true; // Katakana (except small characters)
559 }
560 if (c >= '\u3400' && c <= '\u4DB5') {
561 return true; // CJK UNIFIED IDEOGRAPHS EXTENSION A
562 }
563 if (c >= '\u4E00' && c <= '\u9FBB') {
564 return true; // CJK UNIFIED IDEOGRAPHS
565 }
566 if (c >= '\uF900' && c <= '\uFAD9') {
567 return true; // CJK COMPATIBILITY IDEOGRAPHS
568 }
569 if (c >= '\uA000' && c <= '\uA48F') {
570 return true; // YI SYLLABLES
571 }
572 if (c >= '\uA490' && c <= '\uA4CF') {
573 return true; // YI RADICALS
574 }
575 if (c >= '\uFE62' && c <= '\uFE66') {
576 return true; // SMALL PLUS SIGN to SMALL EQUALS SIGN
577 }
578 if (c >= '\uFF10' && c <= '\uFF19') {
579 return true; // WIDE DIGITS
580 }
581
582 return false;
583 }
584
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800585 private int out(CharSequence text, int start, int end,
586 int above, int below, int top, int bottom, int v,
587 float spacingmult, float spacingadd,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800588 LineHeightSpan[] chooseHt, int[] chooseHtv,
Doug Feltc982f602010-05-25 11:51:40 -0700589 Paint.FontMetricsInt fm, boolean hasTabOrEmoji,
Gilles Debunned300e752011-10-17 13:37:36 -0700590 boolean needMultiply, byte[] chdirs, int dir,
591 boolean easy, int bufEnd, boolean includePad,
592 boolean trackPad, char[] chs,
593 float[] widths, int widthStart, TextUtils.TruncateAt ellipsize,
594 float ellipsisWidth, float textWidth,
595 TextPaint paint, boolean moreChars) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800596 int j = mLineCount;
597 int off = j * mColumns;
598 int want = off + mColumns + TOP;
599 int[] lines = mLines;
600
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800601 if (want >= lines.length) {
602 int nlen = ArrayUtils.idealIntArraySize(want + 1);
603 int[] grow = new int[nlen];
604 System.arraycopy(lines, 0, grow, 0, lines.length);
605 mLines = grow;
606 lines = grow;
607
608 Directions[] grow2 = new Directions[nlen];
609 System.arraycopy(mLineDirections, 0, grow2, 0,
610 mLineDirections.length);
611 mLineDirections = grow2;
612 }
613
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800614 if (chooseHt != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800615 fm.ascent = above;
616 fm.descent = below;
617 fm.top = top;
618 fm.bottom = bottom;
619
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800620 for (int i = 0; i < chooseHt.length; i++) {
621 if (chooseHt[i] instanceof LineHeightSpan.WithDensity) {
622 ((LineHeightSpan.WithDensity) chooseHt[i]).
623 chooseHeight(text, start, end, chooseHtv[i], v, fm, paint);
Eric Fischera9f1dd02009-08-12 15:00:10 -0700624
625 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800626 chooseHt[i].chooseHeight(text, start, end, chooseHtv[i], v, fm);
Eric Fischera9f1dd02009-08-12 15:00:10 -0700627 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800628 }
629
630 above = fm.ascent;
631 below = fm.descent;
632 top = fm.top;
633 bottom = fm.bottom;
634 }
635
636 if (j == 0) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800637 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800638 mTopPadding = top - above;
639 }
640
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800641 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800642 above = top;
643 }
644 }
Gilles Debunned300e752011-10-17 13:37:36 -0700645 if (end == bufEnd) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800646 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800647 mBottomPadding = bottom - below;
648 }
649
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800650 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800651 below = bottom;
652 }
653 }
654
655 int extra;
656
657 if (needMultiply) {
Doug Felt10657582010-02-22 11:19:01 -0800658 double ex = (below - above) * (spacingmult - 1) + spacingadd;
659 if (ex >= 0) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800660 extra = (int)(ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -0800661 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800662 extra = -(int)(-ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -0800663 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800664 } else {
665 extra = 0;
666 }
667
668 lines[off + START] = start;
669 lines[off + TOP] = v;
670 lines[off + DESCENT] = below + extra;
671
672 v += (below - above) + extra;
673 lines[off + mColumns + START] = end;
674 lines[off + mColumns + TOP] = v;
675
Doug Feltc982f602010-05-25 11:51:40 -0700676 if (hasTabOrEmoji)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800677 lines[off + TAB] |= TAB_MASK;
678
Doug Felt9f7a4442010-03-01 12:45:56 -0800679 lines[off + DIR] |= dir << DIR_SHIFT;
680 Directions linedirs = DIRS_ALL_LEFT_TO_RIGHT;
681 // easy means all chars < the first RTL, so no emoji, no nothing
Doug Felt4e0c5e52010-03-15 16:56:02 -0700682 // XXX a run with no text or all spaces is easy but might be an empty
Doug Felt9f7a4442010-03-01 12:45:56 -0800683 // RTL paragraph. Make sure easy is false if this is the case.
684 if (easy) {
685 mLineDirections[j] = linedirs;
686 } else {
Gilles Debunnef3fa0cd2011-02-03 14:17:05 -0800687 mLineDirections[j] = AndroidBidi.directions(dir, chdirs, start - widthStart, chs,
688 start - widthStart, end - start);
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800689 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800690
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -0700691 if (ellipsize != null) {
692 // If there is only one line, then do any type of ellipsis except when it is MARQUEE
693 // if there are multiple lines, just allow END ellipsis on the last line
694 boolean firstLine = (j == 0);
695 boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700696 boolean forceEllipsis = moreChars && (mLineCount + 1 == mMaximumVisibleLineCount);
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -0700697
Fabrice Di Meglio34a126e2012-02-29 18:43:14 -0800698 boolean doEllipsis =
699 (((mMaximumVisibleLineCount == 1 && moreChars) || (firstLine && !moreChars)) &&
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -0700700 ellipsize != TextUtils.TruncateAt.MARQUEE) ||
701 (!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
702 ellipsize == TextUtils.TruncateAt.END);
703 if (doEllipsis) {
704 calculateEllipsis(start, end, widths, widthStart,
705 ellipsisWidth, ellipsize, j,
706 textWidth, paint, forceEllipsis);
707 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800708 }
709
710 mLineCount++;
711 return v;
712 }
713
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800714 private void calculateEllipsis(int lineStart, int lineEnd,
715 float[] widths, int widthStart,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800716 float avail, TextUtils.TruncateAt where,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700717 int line, float textWidth, TextPaint paint,
718 boolean forceEllipsis) {
719 if (textWidth <= avail && !forceEllipsis) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800720 // Everything fits!
721 mLines[mColumns * line + ELLIPSIS_START] = 0;
722 mLines[mColumns * line + ELLIPSIS_COUNT] = 0;
723 return;
724 }
725
Fabrice Di Megliocb332642011-09-23 19:08:04 -0700726 float ellipsisWidth = paint.measureText(
Fabrice Di Meglio8d44fff2012-06-13 15:45:38 -0700727 (where == TextUtils.TruncateAt.END_SMALL) ?
728 ELLIPSIS_TWO_DOTS : ELLIPSIS_NORMAL, 0, 1);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700729 int ellipsisStart = 0;
730 int ellipsisCount = 0;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800731 int len = lineEnd - lineStart;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800732
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700733 // We only support start ellipsis on a single line
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800734 if (where == TextUtils.TruncateAt.START) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700735 if (mMaximumVisibleLineCount == 1) {
736 float sum = 0;
737 int i;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800738
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700739 for (i = len; i >= 0; i--) {
740 float w = widths[i - 1 + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800741
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700742 if (w + sum + ellipsisWidth > avail) {
743 break;
744 }
745
746 sum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800747 }
748
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700749 ellipsisStart = 0;
750 ellipsisCount = i;
751 } else {
752 if (Log.isLoggable(TAG, Log.WARN)) {
753 Log.w(TAG, "Start Ellipsis only supported with one line");
754 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800755 }
Fabrice Di Megliocb332642011-09-23 19:08:04 -0700756 } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE ||
757 where == TextUtils.TruncateAt.END_SMALL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800758 float sum = 0;
759 int i;
760
761 for (i = 0; i < len; i++) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800762 float w = widths[i + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800763
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800764 if (w + sum + ellipsisWidth > avail) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800765 break;
766 }
767
768 sum += w;
769 }
770
771 ellipsisStart = i;
772 ellipsisCount = len - i;
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -0700773 if (forceEllipsis && ellipsisCount == 0 && len > 0) {
774 ellipsisStart = len - 1;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700775 ellipsisCount = 1;
776 }
777 } else {
778 // where = TextUtils.TruncateAt.MIDDLE We only support middle ellipsis on a single line
779 if (mMaximumVisibleLineCount == 1) {
780 float lsum = 0, rsum = 0;
781 int left = 0, right = len;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800782
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700783 float ravail = (avail - ellipsisWidth) / 2;
784 for (right = len; right >= 0; right--) {
785 float w = widths[right - 1 + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800786
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700787 if (w + rsum > ravail) {
788 break;
789 }
790
791 rsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800792 }
793
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700794 float lavail = avail - ellipsisWidth - rsum;
795 for (left = 0; left < right; left++) {
796 float w = widths[left + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800797
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700798 if (w + lsum > lavail) {
799 break;
800 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800801
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700802 lsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800803 }
804
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700805 ellipsisStart = left;
806 ellipsisCount = right - left;
807 } else {
808 if (Log.isLoggable(TAG, Log.WARN)) {
809 Log.w(TAG, "Middle Ellipsis only supported with one line");
810 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800811 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800812 }
813
814 mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
815 mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
816 }
817
Doug Felte8e45f22010-03-29 14:58:40 -0700818 // Override the base class so we can directly access our members,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800819 // rather than relying on member functions.
820 // The logic mirrors that of Layout.getLineForVertical
821 // FIXME: It may be faster to do a linear search for layouts without many lines.
Gilles Debunne66111472010-11-19 11:04:37 -0800822 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800823 public int getLineForVertical(int vertical) {
824 int high = mLineCount;
825 int low = -1;
826 int guess;
827 int[] lines = mLines;
828 while (high - low > 1) {
829 guess = (high + low) >> 1;
830 if (lines[mColumns * guess + TOP] > vertical){
831 high = guess;
832 } else {
833 low = guess;
834 }
835 }
836 if (low < 0) {
837 return 0;
838 } else {
839 return low;
840 }
841 }
842
Gilles Debunne66111472010-11-19 11:04:37 -0800843 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800844 public int getLineCount() {
845 return mLineCount;
846 }
847
Gilles Debunne66111472010-11-19 11:04:37 -0800848 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800849 public int getLineTop(int line) {
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800850 int top = mLines[mColumns * line + TOP];
851 if (mMaximumVisibleLineCount > 0 && line >= mMaximumVisibleLineCount &&
852 line != mLineCount) {
853 top += getBottomPadding();
854 }
855 return top;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800856 }
857
Gilles Debunne66111472010-11-19 11:04:37 -0800858 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800859 public int getLineDescent(int line) {
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800860 int descent = mLines[mColumns * line + DESCENT];
Gilles Debunnef3fa0cd2011-02-03 14:17:05 -0800861 if (mMaximumVisibleLineCount > 0 && line >= mMaximumVisibleLineCount - 1 && // -1 intended
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800862 line != mLineCount) {
863 descent += getBottomPadding();
864 }
865 return descent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800866 }
867
Gilles Debunne66111472010-11-19 11:04:37 -0800868 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800869 public int getLineStart(int line) {
870 return mLines[mColumns * line + START] & START_MASK;
871 }
872
Gilles Debunne66111472010-11-19 11:04:37 -0800873 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800874 public int getParagraphDirection(int line) {
875 return mLines[mColumns * line + DIR] >> DIR_SHIFT;
876 }
877
Gilles Debunne66111472010-11-19 11:04:37 -0800878 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800879 public boolean getLineContainsTab(int line) {
880 return (mLines[mColumns * line + TAB] & TAB_MASK) != 0;
881 }
882
Gilles Debunne66111472010-11-19 11:04:37 -0800883 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800884 public final Directions getLineDirections(int line) {
885 return mLineDirections[line];
886 }
887
Gilles Debunne66111472010-11-19 11:04:37 -0800888 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800889 public int getTopPadding() {
890 return mTopPadding;
891 }
892
Gilles Debunne66111472010-11-19 11:04:37 -0800893 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800894 public int getBottomPadding() {
895 return mBottomPadding;
896 }
897
898 @Override
899 public int getEllipsisCount(int line) {
900 if (mColumns < COLUMNS_ELLIPSIZE) {
901 return 0;
902 }
903
904 return mLines[mColumns * line + ELLIPSIS_COUNT];
905 }
906
907 @Override
908 public int getEllipsisStart(int line) {
909 if (mColumns < COLUMNS_ELLIPSIZE) {
910 return 0;
911 }
912
913 return mLines[mColumns * line + ELLIPSIS_START];
914 }
915
916 @Override
917 public int getEllipsizedWidth() {
918 return mEllipsizedWidth;
919 }
920
Romain Guye5ea4402011-08-01 14:01:37 -0700921 void prepare() {
922 mMeasured = MeasuredText.obtain();
923 }
924
925 void finish() {
926 mMeasured = MeasuredText.recycle(mMeasured);
927 }
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800928
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800929 private int mLineCount;
930 private int mTopPadding, mBottomPadding;
931 private int mColumns;
932 private int mEllipsizedWidth;
933
934 private static final int COLUMNS_NORMAL = 3;
935 private static final int COLUMNS_ELLIPSIZE = 5;
936 private static final int START = 0;
937 private static final int DIR = START;
938 private static final int TAB = START;
939 private static final int TOP = 1;
940 private static final int DESCENT = 2;
941 private static final int ELLIPSIS_START = 3;
942 private static final int ELLIPSIS_COUNT = 4;
943
944 private int[] mLines;
945 private Directions[] mLineDirections;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700946 private int mMaximumVisibleLineCount = Integer.MAX_VALUE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800947
948 private static final int START_MASK = 0x1FFFFFFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800949 private static final int DIR_SHIFT = 30;
950 private static final int TAB_MASK = 0x20000000;
951
Doug Feltc982f602010-05-25 11:51:40 -0700952 private static final int TAB_INCREMENT = 20; // same as Layout, but that's private
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800953
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800954 private static final char CHAR_FIRST_CJK = '\u2E80';
955
956 private static final char CHAR_NEW_LINE = '\n';
957 private static final char CHAR_TAB = '\t';
958 private static final char CHAR_SPACE = ' ';
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800959 private static final char CHAR_SLASH = '/';
960 private static final char CHAR_HYPHEN = '-';
Raph Levien8d087c32013-03-29 09:25:48 -0700961 private static final char CHAR_ZWSP = '\u200B';
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800962
963 private static final double EXTRA_ROUNDING = 0.5;
Fabrice Di Megliocb332642011-09-23 19:08:04 -0700964
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800965 private static final int CHAR_FIRST_HIGH_SURROGATE = 0xD800;
966 private static final int CHAR_LAST_LOW_SURROGATE = 0xDFFF;
967
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800968 /*
Doug Felte8e45f22010-03-29 14:58:40 -0700969 * This is reused across calls to generate()
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800970 */
Doug Felte8e45f22010-03-29 14:58:40 -0700971 private MeasuredText mMeasured;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800972 private Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
973}