blob: 583cbe615fa4a9d94f783a4e4ae84de80ecc5234 [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
Doug Feltcb3791202011-07-07 11:57:48 -0700140 generate(source, bufstart, bufend, paint, outerwidth, align, textDir,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800141 spacingmult, spacingadd, includepad, includepad,
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800142 ellipsizedWidth, 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)];
153 mLineDirections = new Directions[
154 ArrayUtils.idealIntArraySize(2 * mColumns)];
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,
Doug Feltcb3791202011-07-07 11:57:48 -0700160 Alignment align, TextDirectionHeuristic textDir,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800161 float spacingmult, float spacingadd,
162 boolean includepad, boolean trackpad,
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800163 float ellipsizedWidth, 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;
Doug Felte8e45f22010-03-29 14:58:40 -0700249 int here = paraStart;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800250
Doug Felte8e45f22010-03-29 14:58:40 -0700251 int ok = paraStart;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800252 float okWidth = w;
253 int okAscent = 0, okDescent = 0, okTop = 0, okBottom = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800254
Doug Felte8e45f22010-03-29 14:58:40 -0700255 int fit = paraStart;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800256 float fitWidth = w;
257 int fitAscent = 0, fitDescent = 0, fitTop = 0, fitBottom = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800258
Doug Feltc982f602010-05-25 11:51:40 -0700259 boolean hasTabOrEmoji = false;
260 boolean hasTab = false;
261 TabStops tabStops = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800262
Doug Felt23241882010-06-02 14:41:06 -0700263 for (int spanStart = paraStart, spanEnd = spanStart, nextSpanStart;
264 spanStart < paraEnd; spanStart = nextSpanStart) {
Doug Felte8e45f22010-03-29 14:58:40 -0700265
Doug Felt23241882010-06-02 14:41:06 -0700266 if (spanStart == spanEnd) {
267 if (spanned == null)
268 spanEnd = paraEnd;
269 else
270 spanEnd = spanned.nextSpanTransition(spanStart, paraEnd,
271 MetricAffectingSpan.class);
272
273 int spanLen = spanEnd - spanStart;
274 if (spanned == null) {
275 measured.addStyleRun(paint, spanLen, fm);
276 } else {
277 MetricAffectingSpan[] spans =
278 spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class);
Gilles Debunne1e3ac182011-03-08 14:22:34 -0800279 spans = TextUtils.removeEmptySpans(spans, spanned,
280 MetricAffectingSpan.class);
Doug Felt23241882010-06-02 14:41:06 -0700281 measured.addStyleRun(paint, spans, spanLen, fm);
282 }
283 }
284
285 nextSpanStart = spanEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800286
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800287 int fmTop = fm.top;
288 int fmBottom = fm.bottom;
289 int fmAscent = fm.ascent;
290 int fmDescent = fm.descent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800291
Doug Felte8e45f22010-03-29 14:58:40 -0700292 for (int j = spanStart; j < spanEnd; j++) {
293 char c = chs[j - paraStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800294
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800295 if (c == CHAR_NEW_LINE) {
Gilles Debunne66111472010-11-19 11:04:37 -0800296 // intentionally left empty
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800297 } else if (c == CHAR_TAB) {
Doug Feltc982f602010-05-25 11:51:40 -0700298 if (hasTab == false) {
299 hasTab = true;
300 hasTabOrEmoji = true;
Kenny Root24ca4542010-06-22 23:46:35 -0700301 if (spanned != null) {
302 // First tab this para, check for tabstops
Eric Fischer74d31ef2010-08-05 15:29:36 -0700303 TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
Kenny Root24ca4542010-06-22 23:46:35 -0700304 paraEnd, TabStopSpan.class);
305 if (spans.length > 0) {
306 tabStops = new TabStops(TAB_INCREMENT, spans);
307 }
Doug Feltc982f602010-05-25 11:51:40 -0700308 }
309 }
310 if (tabStops != null) {
311 w = tabStops.nextTab(w);
312 } else {
313 w = TabStops.nextDefaultStop(w, TAB_INCREMENT);
314 }
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800315 } else if (c >= CHAR_FIRST_HIGH_SURROGATE && c <= CHAR_LAST_LOW_SURROGATE
316 && j + 1 < spanEnd) {
Doug Felte8e45f22010-03-29 14:58:40 -0700317 int emoji = Character.codePointAt(chs, j - paraStart);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800318
The Android Open Source Project10592532009-03-18 17:39:46 -0700319 if (emoji >= MIN_EMOJI && emoji <= MAX_EMOJI) {
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800320 Bitmap bm = EMOJI_FACTORY.getBitmapFromAndroidPua(emoji);
The Android Open Source Project10592532009-03-18 17:39:46 -0700321
322 if (bm != null) {
Eric Fischer423f0e42009-03-27 18:04:12 -0700323 Paint whichPaint;
324
325 if (spanned == null) {
326 whichPaint = paint;
327 } else {
328 whichPaint = mWorkPaint;
329 }
330
Doug Felt4e0c5e52010-03-15 16:56:02 -0700331 float wid = bm.getWidth() *
Eric Fischer423f0e42009-03-27 18:04:12 -0700332 -whichPaint.ascent() /
333 bm.getHeight();
334
335 w += wid;
Doug Feltc982f602010-05-25 11:51:40 -0700336 hasTabOrEmoji = true;
The Android Open Source Project10592532009-03-18 17:39:46 -0700337 j++;
338 } else {
Doug Felte8e45f22010-03-29 14:58:40 -0700339 w += widths[j - paraStart];
The Android Open Source Project10592532009-03-18 17:39:46 -0700340 }
341 } else {
Doug Felte8e45f22010-03-29 14:58:40 -0700342 w += widths[j - paraStart];
The Android Open Source Project10592532009-03-18 17:39:46 -0700343 }
344 } else {
Doug Felte8e45f22010-03-29 14:58:40 -0700345 w += widths[j - paraStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800346 }
347
348 // Log.e("text", "was " + before + " now " + w + " after " + c + " within " + width);
349
350 if (w <= width) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800351 fitWidth = w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800352 fit = j + 1;
353
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800354 if (fmTop < fitTop)
355 fitTop = fmTop;
356 if (fmAscent < fitAscent)
357 fitAscent = fmAscent;
358 if (fmDescent > fitDescent)
359 fitDescent = fmDescent;
360 if (fmBottom > fitBottom)
361 fitBottom = fmBottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800362
363 /*
364 * From the Unicode Line Breaking Algorithm:
365 * (at least approximately)
Doug Felt4e0c5e52010-03-15 16:56:02 -0700366 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800367 * .,:; are class IS: breakpoints
368 * except when adjacent to digits
369 * / is class SY: a breakpoint
370 * except when followed by a digit.
371 * - is class HY: a breakpoint
372 * except when followed by a digit.
373 *
Eric Fischer549d7242009-03-31 14:19:47 -0700374 * Ideographs are class ID: breakpoints when adjacent,
375 * except for NS (non-starters), which can be broken
376 * after but not before.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800377 */
378
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800379 if (c == CHAR_SPACE || c == CHAR_TAB ||
380 ((c == CHAR_DOT || c == CHAR_COMMA ||
381 c == CHAR_COLON || c == CHAR_SEMICOLON) &&
Doug Felte8e45f22010-03-29 14:58:40 -0700382 (j - 1 < here || !Character.isDigit(chs[j - 1 - paraStart])) &&
383 (j + 1 >= spanEnd || !Character.isDigit(chs[j + 1 - paraStart]))) ||
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800384 ((c == CHAR_SLASH || c == CHAR_HYPHEN) &&
Doug Felte8e45f22010-03-29 14:58:40 -0700385 (j + 1 >= spanEnd || !Character.isDigit(chs[j + 1 - paraStart]))) ||
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800386 (c >= CHAR_FIRST_CJK && isIdeographic(c, true) &&
Doug Felte8e45f22010-03-29 14:58:40 -0700387 j + 1 < spanEnd && isIdeographic(chs[j + 1 - paraStart], false))) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800388 okWidth = w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800389 ok = j + 1;
390
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800391 if (fitTop < okTop)
392 okTop = fitTop;
393 if (fitAscent < okAscent)
394 okAscent = fitAscent;
395 if (fitDescent > okDescent)
396 okDescent = fitDescent;
397 if (fitBottom > okBottom)
398 okBottom = fitBottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800399 }
Gilles Debunne4cf435d2011-01-04 15:35:29 -0800400 } else {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700401 final boolean moreChars = (j + 1 < spanEnd);
Gilles Debunned434d232011-01-04 17:15:14 -0800402 if (ok != here) {
403 // Log.e("text", "output ok " + here + " to " +ok);
Gilles Debunne32ea4ff2010-12-21 11:28:34 -0800404
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800405 while (ok < spanEnd && chs[ok - paraStart] == CHAR_SPACE) {
Gilles Debunned434d232011-01-04 17:15:14 -0800406 ok++;
407 }
408
409 v = out(source,
410 here, ok,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800411 okAscent, okDescent, okTop, okBottom,
Gilles Debunned434d232011-01-04 17:15:14 -0800412 v,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800413 spacingmult, spacingadd, chooseHt,
414 chooseHtv, fm, hasTabOrEmoji,
Gilles Debunned434d232011-01-04 17:15:14 -0800415 needMultiply, paraStart, chdirs, dir, easy,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800416 ok == bufEnd, includepad, trackpad,
Gilles Debunnef3fa0cd2011-02-03 14:17:05 -0800417 chs, widths, paraStart,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800418 ellipsize, ellipsizedWidth, okWidth,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700419 paint, moreChars);
Gilles Debunned434d232011-01-04 17:15:14 -0800420
421 here = ok;
422 } else if (fit != here) {
423 // Log.e("text", "output fit " + here + " to " +fit);
424 v = out(source,
425 here, fit,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800426 fitAscent, fitDescent,
427 fitTop, fitBottom,
Gilles Debunned434d232011-01-04 17:15:14 -0800428 v,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800429 spacingmult, spacingadd, chooseHt,
430 chooseHtv, fm, hasTabOrEmoji,
Gilles Debunned434d232011-01-04 17:15:14 -0800431 needMultiply, paraStart, chdirs, dir, easy,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800432 fit == bufEnd, includepad, trackpad,
Gilles Debunnef3fa0cd2011-02-03 14:17:05 -0800433 chs, widths, paraStart,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800434 ellipsize, ellipsizedWidth, fitWidth,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700435 paint, moreChars);
Gilles Debunned434d232011-01-04 17:15:14 -0800436
437 here = fit;
438 } else {
439 // Log.e("text", "output one " + here + " to " +(here + 1));
440 // XXX not sure why the existing fm wasn't ok.
441 // measureText(paint, mWorkPaint,
442 // source, here, here + 1, fm, tab,
443 // null);
444
445 v = out(source,
446 here, here+1,
447 fm.ascent, fm.descent,
448 fm.top, fm.bottom,
449 v,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800450 spacingmult, spacingadd, chooseHt,
451 chooseHtv, fm, hasTabOrEmoji,
Gilles Debunned434d232011-01-04 17:15:14 -0800452 needMultiply, paraStart, chdirs, dir, easy,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800453 here + 1 == bufEnd, includepad,
Gilles Debunned434d232011-01-04 17:15:14 -0800454 trackpad,
Gilles Debunnef3fa0cd2011-02-03 14:17:05 -0800455 chs, widths, paraStart,
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800456 ellipsize, ellipsizedWidth,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700457 widths[here - paraStart], paint, moreChars);
Gilles Debunned434d232011-01-04 17:15:14 -0800458
459 here = here + 1;
460 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800461
Doug Felte8e45f22010-03-29 14:58:40 -0700462 if (here < spanStart) {
Doug Felt23241882010-06-02 14:41:06 -0700463 // didn't output all the text for this span
464 // we've measured the raw widths, though, so
465 // just reset the start point
466 j = nextSpanStart = here;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800467 } else {
468 j = here - 1; // continue looping
469 }
470
471 ok = fit = here;
472 w = 0;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800473 fitAscent = fitDescent = fitTop = fitBottom = 0;
474 okAscent = okDescent = okTop = okBottom = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800475
Doug Feltc982f602010-05-25 11:51:40 -0700476 if (--firstWidthLineLimit <= 0) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800477 width = restWidth;
Mark Wagner7b5676e2009-10-16 11:44:23 -0700478 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800479 }
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700480 if (mLineCount >= mMaximumVisibleLineCount) {
481 break;
482 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800483 }
484 }
485
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700486 if (paraEnd != here && mLineCount < mMaximumVisibleLineCount) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800487 if ((fitTop | fitBottom | fitDescent | fitAscent) == 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800488 paint.getFontMetricsInt(fm);
489
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800490 fitTop = fm.top;
491 fitBottom = fm.bottom;
492 fitAscent = fm.ascent;
493 fitDescent = fm.descent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800494 }
495
496 // Log.e("text", "output rest " + here + " to " + end);
497
498 v = out(source,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800499 here, paraEnd, fitAscent, fitDescent,
500 fitTop, fitBottom,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800501 v,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800502 spacingmult, spacingadd, chooseHt,
503 chooseHtv, fm, hasTabOrEmoji,
Doug Felte8e45f22010-03-29 14:58:40 -0700504 needMultiply, paraStart, chdirs, dir, easy,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800505 paraEnd == bufEnd, includepad, trackpad,
Gilles Debunnef3fa0cd2011-02-03 14:17:05 -0800506 chs, widths, paraStart,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700507 ellipsize, ellipsizedWidth, w, paint, paraEnd != bufEnd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800508 }
509
Doug Felte8e45f22010-03-29 14:58:40 -0700510 paraStart = paraEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800511
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800512 if (paraEnd == bufEnd)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800513 break;
514 }
515
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700516 if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE) &&
517 mLineCount < mMaximumVisibleLineCount) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800518 // Log.e("text", "output last " + bufEnd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800519
520 paint.getFontMetricsInt(fm);
521
522 v = out(source,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800523 bufEnd, bufEnd, fm.ascent, fm.descent,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800524 fm.top, fm.bottom,
525 v,
526 spacingmult, spacingadd, null,
527 null, fm, false,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800528 needMultiply, bufEnd, null, DEFAULT_DIR, true,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800529 true, includepad, trackpad,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800530 null, null, bufStart,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700531 ellipsize, ellipsizedWidth, 0, paint, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800532 }
533 }
534
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800535 /**
536 * Returns true if the specified character is one of those specified
537 * as being Ideographic (class ID) by the Unicode Line Breaking Algorithm
538 * (http://www.unicode.org/unicode/reports/tr14/), and is therefore OK
539 * to break between a pair of.
Eric Fischer549d7242009-03-31 14:19:47 -0700540 *
541 * @param includeNonStarters also return true for category NS
542 * (non-starters), which can be broken
543 * after but not before.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800544 */
Eric Fischer549d7242009-03-31 14:19:47 -0700545 private static final boolean isIdeographic(char c, boolean includeNonStarters) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800546 if (c >= '\u2E80' && c <= '\u2FFF') {
547 return true; // CJK, KANGXI RADICALS, DESCRIPTION SYMBOLS
548 }
549 if (c == '\u3000') {
550 return true; // IDEOGRAPHIC SPACE
551 }
552 if (c >= '\u3040' && c <= '\u309F') {
Eric Fischer549d7242009-03-31 14:19:47 -0700553 if (!includeNonStarters) {
554 switch (c) {
555 case '\u3041': // # HIRAGANA LETTER SMALL A
556 case '\u3043': // # HIRAGANA LETTER SMALL I
557 case '\u3045': // # HIRAGANA LETTER SMALL U
558 case '\u3047': // # HIRAGANA LETTER SMALL E
559 case '\u3049': // # HIRAGANA LETTER SMALL O
560 case '\u3063': // # HIRAGANA LETTER SMALL TU
561 case '\u3083': // # HIRAGANA LETTER SMALL YA
562 case '\u3085': // # HIRAGANA LETTER SMALL YU
563 case '\u3087': // # HIRAGANA LETTER SMALL YO
564 case '\u308E': // # HIRAGANA LETTER SMALL WA
565 case '\u3095': // # HIRAGANA LETTER SMALL KA
566 case '\u3096': // # HIRAGANA LETTER SMALL KE
567 case '\u309B': // # KATAKANA-HIRAGANA VOICED SOUND MARK
568 case '\u309C': // # KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK
569 case '\u309D': // # HIRAGANA ITERATION MARK
570 case '\u309E': // # HIRAGANA VOICED ITERATION MARK
571 return false;
572 }
573 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800574 return true; // Hiragana (except small characters)
575 }
576 if (c >= '\u30A0' && c <= '\u30FF') {
Eric Fischer549d7242009-03-31 14:19:47 -0700577 if (!includeNonStarters) {
578 switch (c) {
579 case '\u30A0': // # KATAKANA-HIRAGANA DOUBLE HYPHEN
580 case '\u30A1': // # KATAKANA LETTER SMALL A
581 case '\u30A3': // # KATAKANA LETTER SMALL I
582 case '\u30A5': // # KATAKANA LETTER SMALL U
583 case '\u30A7': // # KATAKANA LETTER SMALL E
584 case '\u30A9': // # KATAKANA LETTER SMALL O
585 case '\u30C3': // # KATAKANA LETTER SMALL TU
586 case '\u30E3': // # KATAKANA LETTER SMALL YA
587 case '\u30E5': // # KATAKANA LETTER SMALL YU
588 case '\u30E7': // # KATAKANA LETTER SMALL YO
589 case '\u30EE': // # KATAKANA LETTER SMALL WA
590 case '\u30F5': // # KATAKANA LETTER SMALL KA
591 case '\u30F6': // # KATAKANA LETTER SMALL KE
592 case '\u30FB': // # KATAKANA MIDDLE DOT
593 case '\u30FC': // # KATAKANA-HIRAGANA PROLONGED SOUND MARK
594 case '\u30FD': // # KATAKANA ITERATION MARK
595 case '\u30FE': // # KATAKANA VOICED ITERATION MARK
596 return false;
597 }
598 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800599 return true; // Katakana (except small characters)
600 }
601 if (c >= '\u3400' && c <= '\u4DB5') {
602 return true; // CJK UNIFIED IDEOGRAPHS EXTENSION A
603 }
604 if (c >= '\u4E00' && c <= '\u9FBB') {
605 return true; // CJK UNIFIED IDEOGRAPHS
606 }
607 if (c >= '\uF900' && c <= '\uFAD9') {
608 return true; // CJK COMPATIBILITY IDEOGRAPHS
609 }
610 if (c >= '\uA000' && c <= '\uA48F') {
611 return true; // YI SYLLABLES
612 }
613 if (c >= '\uA490' && c <= '\uA4CF') {
614 return true; // YI RADICALS
615 }
616 if (c >= '\uFE62' && c <= '\uFE66') {
617 return true; // SMALL PLUS SIGN to SMALL EQUALS SIGN
618 }
619 if (c >= '\uFF10' && c <= '\uFF19') {
620 return true; // WIDE DIGITS
621 }
622
623 return false;
624 }
625
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800626 private int out(CharSequence text, int start, int end,
627 int above, int below, int top, int bottom, int v,
628 float spacingmult, float spacingadd,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800629 LineHeightSpan[] chooseHt, int[] chooseHtv,
Doug Feltc982f602010-05-25 11:51:40 -0700630 Paint.FontMetricsInt fm, boolean hasTabOrEmoji,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800631 boolean needMultiply, int pstart, byte[] chdirs,
632 int dir, boolean easy, boolean last,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800633 boolean includePad, boolean trackPad,
634 char[] chs, float[] widths, int widthStart,
635 TextUtils.TruncateAt ellipsize, float ellipsisWidth,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700636 float textWidth, TextPaint paint, boolean moreChars) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800637 int j = mLineCount;
638 int off = j * mColumns;
639 int want = off + mColumns + TOP;
640 int[] lines = mLines;
641
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800642 if (want >= lines.length) {
643 int nlen = ArrayUtils.idealIntArraySize(want + 1);
644 int[] grow = new int[nlen];
645 System.arraycopy(lines, 0, grow, 0, lines.length);
646 mLines = grow;
647 lines = grow;
648
649 Directions[] grow2 = new Directions[nlen];
650 System.arraycopy(mLineDirections, 0, grow2, 0,
651 mLineDirections.length);
652 mLineDirections = grow2;
653 }
654
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800655 if (chooseHt != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800656 fm.ascent = above;
657 fm.descent = below;
658 fm.top = top;
659 fm.bottom = bottom;
660
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800661 for (int i = 0; i < chooseHt.length; i++) {
662 if (chooseHt[i] instanceof LineHeightSpan.WithDensity) {
663 ((LineHeightSpan.WithDensity) chooseHt[i]).
664 chooseHeight(text, start, end, chooseHtv[i], v, fm, paint);
Eric Fischera9f1dd02009-08-12 15:00:10 -0700665
666 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800667 chooseHt[i].chooseHeight(text, start, end, chooseHtv[i], v, fm);
Eric Fischera9f1dd02009-08-12 15:00:10 -0700668 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800669 }
670
671 above = fm.ascent;
672 below = fm.descent;
673 top = fm.top;
674 bottom = fm.bottom;
675 }
676
677 if (j == 0) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800678 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800679 mTopPadding = top - above;
680 }
681
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800682 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800683 above = top;
684 }
685 }
686 if (last) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800687 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800688 mBottomPadding = bottom - below;
689 }
690
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800691 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800692 below = bottom;
693 }
694 }
695
696 int extra;
697
698 if (needMultiply) {
Doug Felt10657582010-02-22 11:19:01 -0800699 double ex = (below - above) * (spacingmult - 1) + spacingadd;
700 if (ex >= 0) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800701 extra = (int)(ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -0800702 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800703 extra = -(int)(-ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -0800704 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800705 } else {
706 extra = 0;
707 }
708
709 lines[off + START] = start;
710 lines[off + TOP] = v;
711 lines[off + DESCENT] = below + extra;
712
713 v += (below - above) + extra;
714 lines[off + mColumns + START] = end;
715 lines[off + mColumns + TOP] = v;
716
Doug Feltc982f602010-05-25 11:51:40 -0700717 if (hasTabOrEmoji)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800718 lines[off + TAB] |= TAB_MASK;
719
Doug Felt9f7a4442010-03-01 12:45:56 -0800720 lines[off + DIR] |= dir << DIR_SHIFT;
721 Directions linedirs = DIRS_ALL_LEFT_TO_RIGHT;
722 // easy means all chars < the first RTL, so no emoji, no nothing
Doug Felt4e0c5e52010-03-15 16:56:02 -0700723 // XXX a run with no text or all spaces is easy but might be an empty
Doug Felt9f7a4442010-03-01 12:45:56 -0800724 // RTL paragraph. Make sure easy is false if this is the case.
725 if (easy) {
726 mLineDirections[j] = linedirs;
727 } else {
Gilles Debunnef3fa0cd2011-02-03 14:17:05 -0800728 mLineDirections[j] = AndroidBidi.directions(dir, chdirs, start - widthStart, chs,
729 start - widthStart, end - start);
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800730 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800731
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -0700732 if (ellipsize != null) {
733 // If there is only one line, then do any type of ellipsis except when it is MARQUEE
734 // if there are multiple lines, just allow END ellipsis on the last line
735 boolean firstLine = (j == 0);
736 boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700737 boolean forceEllipsis = moreChars && (mLineCount + 1 == mMaximumVisibleLineCount);
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -0700738
739 boolean doEllipsis = (firstLine && !moreChars &&
740 ellipsize != TextUtils.TruncateAt.MARQUEE) ||
741 (!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
742 ellipsize == TextUtils.TruncateAt.END);
743 if (doEllipsis) {
744 calculateEllipsis(start, end, widths, widthStart,
745 ellipsisWidth, ellipsize, j,
746 textWidth, paint, forceEllipsis);
747 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800748 }
749
750 mLineCount++;
751 return v;
752 }
753
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800754 private void calculateEllipsis(int lineStart, int lineEnd,
755 float[] widths, int widthStart,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800756 float avail, TextUtils.TruncateAt where,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700757 int line, float textWidth, TextPaint paint,
758 boolean forceEllipsis) {
759 if (textWidth <= avail && !forceEllipsis) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800760 // Everything fits!
761 mLines[mColumns * line + ELLIPSIS_START] = 0;
762 mLines[mColumns * line + ELLIPSIS_COUNT] = 0;
763 return;
764 }
765
Fabrice Di Megliocb332642011-09-23 19:08:04 -0700766 float ellipsisWidth = paint.measureText(
767 (where == TextUtils.TruncateAt.END_SMALL) ? ELLIPSIS_TWO_DOTS : ELLIPSIS_NORMAL);
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700768 int ellipsisStart = 0;
769 int ellipsisCount = 0;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800770 int len = lineEnd - lineStart;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800771
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700772 // We only support start ellipsis on a single line
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800773 if (where == TextUtils.TruncateAt.START) {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700774 if (mMaximumVisibleLineCount == 1) {
775 float sum = 0;
776 int i;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800777
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700778 for (i = len; i >= 0; i--) {
779 float w = widths[i - 1 + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800780
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700781 if (w + sum + ellipsisWidth > avail) {
782 break;
783 }
784
785 sum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800786 }
787
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700788 ellipsisStart = 0;
789 ellipsisCount = i;
790 } else {
791 if (Log.isLoggable(TAG, Log.WARN)) {
792 Log.w(TAG, "Start Ellipsis only supported with one line");
793 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800794 }
Fabrice Di Megliocb332642011-09-23 19:08:04 -0700795 } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE ||
796 where == TextUtils.TruncateAt.END_SMALL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800797 float sum = 0;
798 int i;
799
800 for (i = 0; i < len; i++) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800801 float w = widths[i + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800802
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800803 if (w + sum + ellipsisWidth > avail) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800804 break;
805 }
806
807 sum += w;
808 }
809
810 ellipsisStart = i;
811 ellipsisCount = len - i;
Fabrice Di Meglioaef455f2011-08-29 15:39:11 -0700812 if (forceEllipsis && ellipsisCount == 0 && len > 0) {
813 ellipsisStart = len - 1;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700814 ellipsisCount = 1;
815 }
816 } else {
817 // where = TextUtils.TruncateAt.MIDDLE We only support middle ellipsis on a single line
818 if (mMaximumVisibleLineCount == 1) {
819 float lsum = 0, rsum = 0;
820 int left = 0, right = len;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800821
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700822 float ravail = (avail - ellipsisWidth) / 2;
823 for (right = len; right >= 0; right--) {
824 float w = widths[right - 1 + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800825
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700826 if (w + rsum > ravail) {
827 break;
828 }
829
830 rsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800831 }
832
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700833 float lavail = avail - ellipsisWidth - rsum;
834 for (left = 0; left < right; left++) {
835 float w = widths[left + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800836
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700837 if (w + lsum > lavail) {
838 break;
839 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800840
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700841 lsum += w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800842 }
843
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700844 ellipsisStart = left;
845 ellipsisCount = right - left;
846 } else {
847 if (Log.isLoggable(TAG, Log.WARN)) {
848 Log.w(TAG, "Middle Ellipsis only supported with one line");
849 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800850 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800851 }
852
853 mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
854 mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
855 }
856
Doug Felte8e45f22010-03-29 14:58:40 -0700857 // Override the base class so we can directly access our members,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800858 // rather than relying on member functions.
859 // The logic mirrors that of Layout.getLineForVertical
860 // FIXME: It may be faster to do a linear search for layouts without many lines.
Gilles Debunne66111472010-11-19 11:04:37 -0800861 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800862 public int getLineForVertical(int vertical) {
863 int high = mLineCount;
864 int low = -1;
865 int guess;
866 int[] lines = mLines;
867 while (high - low > 1) {
868 guess = (high + low) >> 1;
869 if (lines[mColumns * guess + TOP] > vertical){
870 high = guess;
871 } else {
872 low = guess;
873 }
874 }
875 if (low < 0) {
876 return 0;
877 } else {
878 return low;
879 }
880 }
881
Gilles Debunne66111472010-11-19 11:04:37 -0800882 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800883 public int getLineCount() {
884 return mLineCount;
885 }
886
Gilles Debunne66111472010-11-19 11:04:37 -0800887 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800888 public int getLineTop(int line) {
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800889 int top = mLines[mColumns * line + TOP];
890 if (mMaximumVisibleLineCount > 0 && line >= mMaximumVisibleLineCount &&
891 line != mLineCount) {
892 top += getBottomPadding();
893 }
894 return top;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800895 }
896
Gilles Debunne66111472010-11-19 11:04:37 -0800897 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800898 public int getLineDescent(int line) {
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800899 int descent = mLines[mColumns * line + DESCENT];
Gilles Debunnef3fa0cd2011-02-03 14:17:05 -0800900 if (mMaximumVisibleLineCount > 0 && line >= mMaximumVisibleLineCount - 1 && // -1 intended
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800901 line != mLineCount) {
902 descent += getBottomPadding();
903 }
904 return descent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800905 }
906
Gilles Debunne66111472010-11-19 11:04:37 -0800907 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800908 public int getLineStart(int line) {
909 return mLines[mColumns * line + START] & START_MASK;
910 }
911
Gilles Debunne66111472010-11-19 11:04:37 -0800912 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800913 public int getParagraphDirection(int line) {
914 return mLines[mColumns * line + DIR] >> DIR_SHIFT;
915 }
916
Gilles Debunne66111472010-11-19 11:04:37 -0800917 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800918 public boolean getLineContainsTab(int line) {
919 return (mLines[mColumns * line + TAB] & TAB_MASK) != 0;
920 }
921
Gilles Debunne66111472010-11-19 11:04:37 -0800922 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800923 public final Directions getLineDirections(int line) {
924 return mLineDirections[line];
925 }
926
Gilles Debunne66111472010-11-19 11:04:37 -0800927 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800928 public int getTopPadding() {
929 return mTopPadding;
930 }
931
Gilles Debunne66111472010-11-19 11:04:37 -0800932 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800933 public int getBottomPadding() {
934 return mBottomPadding;
935 }
936
937 @Override
938 public int getEllipsisCount(int line) {
939 if (mColumns < COLUMNS_ELLIPSIZE) {
940 return 0;
941 }
942
943 return mLines[mColumns * line + ELLIPSIS_COUNT];
944 }
945
946 @Override
947 public int getEllipsisStart(int line) {
948 if (mColumns < COLUMNS_ELLIPSIZE) {
949 return 0;
950 }
951
952 return mLines[mColumns * line + ELLIPSIS_START];
953 }
954
955 @Override
956 public int getEllipsizedWidth() {
957 return mEllipsizedWidth;
958 }
959
Romain Guye5ea4402011-08-01 14:01:37 -0700960 void prepare() {
961 mMeasured = MeasuredText.obtain();
962 }
963
964 void finish() {
965 mMeasured = MeasuredText.recycle(mMeasured);
966 }
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800967
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800968 private int mLineCount;
969 private int mTopPadding, mBottomPadding;
970 private int mColumns;
971 private int mEllipsizedWidth;
972
973 private static final int COLUMNS_NORMAL = 3;
974 private static final int COLUMNS_ELLIPSIZE = 5;
975 private static final int START = 0;
976 private static final int DIR = START;
977 private static final int TAB = START;
978 private static final int TOP = 1;
979 private static final int DESCENT = 2;
980 private static final int ELLIPSIS_START = 3;
981 private static final int ELLIPSIS_COUNT = 4;
982
983 private int[] mLines;
984 private Directions[] mLineDirections;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -0700985 private int mMaximumVisibleLineCount = Integer.MAX_VALUE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800986
987 private static final int START_MASK = 0x1FFFFFFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800988 private static final int DIR_SHIFT = 30;
989 private static final int TAB_MASK = 0x20000000;
990
Doug Feltc982f602010-05-25 11:51:40 -0700991 private static final int TAB_INCREMENT = 20; // same as Layout, but that's private
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800992
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800993 private static final char CHAR_FIRST_CJK = '\u2E80';
994
995 private static final char CHAR_NEW_LINE = '\n';
996 private static final char CHAR_TAB = '\t';
997 private static final char CHAR_SPACE = ' ';
998 private static final char CHAR_DOT = '.';
999 private static final char CHAR_COMMA = ',';
1000 private static final char CHAR_COLON = ':';
1001 private static final char CHAR_SEMICOLON = ';';
1002 private static final char CHAR_SLASH = '/';
1003 private static final char CHAR_HYPHEN = '-';
1004
1005 private static final double EXTRA_ROUNDING = 0.5;
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001006
1007 private static final String ELLIPSIS_NORMAL = "\u2026"; // this is "..."
1008 private static final String ELLIPSIS_TWO_DOTS = "\u2025"; // this is ".."
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -08001009
1010 private static final int CHAR_FIRST_HIGH_SURROGATE = 0xD800;
1011 private static final int CHAR_LAST_LOW_SURROGATE = 0xDFFF;
1012
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001013 /*
Doug Felte8e45f22010-03-29 14:58:40 -07001014 * This is reused across calls to generate()
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001015 */
Doug Felte8e45f22010-03-29 14:58:40 -07001016 private MeasuredText mMeasured;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001017 private Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
1018}