blob: f7b9502693097d8323a99bc4059ce4dc690b459d [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;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080026
Doug Feltcb3791202011-07-07 11:57:48 -070027import com.android.internal.util.ArrayUtils;
28
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080029/**
30 * StaticLayout is a Layout for text that will not be edited after it
31 * is laid out. Use {@link DynamicLayout} for text that may change.
32 * <p>This is used by widgets to control text layout. You should not need
33 * to use this class directly unless you are implementing your own widget
34 * or custom display object, or would be tempted to call
Doug Felt4e0c5e52010-03-15 16:56:02 -070035 * {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int,
36 * float, float, android.graphics.Paint)
37 * Canvas.drawText()} directly.</p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080038 */
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -080039public class StaticLayout extends Layout {
40
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080041 public StaticLayout(CharSequence source, TextPaint paint,
42 int width,
43 Alignment align, float spacingmult, float spacingadd,
44 boolean includepad) {
45 this(source, 0, source.length(), paint, width, align,
46 spacingmult, spacingadd, includepad);
47 }
48
Doug Feltcb3791202011-07-07 11:57:48 -070049 /**
50 * @hide
51 */
52 public StaticLayout(CharSequence source, TextPaint paint,
53 int width, Alignment align, TextDirectionHeuristic textDir,
54 float spacingmult, float spacingadd,
55 boolean includepad) {
56 this(source, 0, source.length(), paint, width, align, textDir,
57 spacingmult, spacingadd, includepad);
58 }
59
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080060 public StaticLayout(CharSequence source, int bufstart, int bufend,
61 TextPaint paint, int outerwidth,
62 Alignment align,
63 float spacingmult, float spacingadd,
64 boolean includepad) {
65 this(source, bufstart, bufend, paint, outerwidth, align,
66 spacingmult, spacingadd, includepad, null, 0);
67 }
68
Doug Feltcb3791202011-07-07 11:57:48 -070069 /**
70 * @hide
71 */
72 public StaticLayout(CharSequence source, int bufstart, int bufend,
73 TextPaint paint, int outerwidth,
74 Alignment align, TextDirectionHeuristic textDir,
75 float spacingmult, float spacingadd,
76 boolean includepad) {
77 this(source, bufstart, bufend, paint, outerwidth, align, textDir,
78 spacingmult, spacingadd, includepad, null, 0);
79}
80
81 public StaticLayout(CharSequence source, int bufstart, int bufend,
82 TextPaint paint, int outerwidth,
83 Alignment align,
84 float spacingmult, float spacingadd,
85 boolean includepad,
86 TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
87 this(source, bufstart, bufend, paint, outerwidth, align,
88 TextDirectionHeuristics.FIRSTSTRONG_LTR,
89 spacingmult, spacingadd, includepad, ellipsize, ellipsizedWidth);
90 }
91
92 /**
93 * @hide
94 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080095 public StaticLayout(CharSequence source, int bufstart, int bufend,
96 TextPaint paint, int outerwidth,
Doug Feltcb3791202011-07-07 11:57:48 -070097 Alignment align, TextDirectionHeuristic textDir,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080098 float spacingmult, float spacingadd,
99 boolean includepad,
100 TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
101 super((ellipsize == null)
Doug Felt4e0c5e52010-03-15 16:56:02 -0700102 ? source
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800103 : (source instanceof Spanned)
104 ? new SpannedEllipsizer(source)
105 : new Ellipsizer(source),
Doug Feltcb3791202011-07-07 11:57:48 -0700106 paint, outerwidth, align, textDir, spacingmult, spacingadd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800107
108 /*
109 * This is annoying, but we can't refer to the layout until
110 * superclass construction is finished, and the superclass
111 * constructor wants the reference to the display text.
Doug Felt4e0c5e52010-03-15 16:56:02 -0700112 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800113 * This will break if the superclass constructor ever actually
114 * cares about the content instead of just holding the reference.
115 */
116 if (ellipsize != null) {
117 Ellipsizer e = (Ellipsizer) getText();
118
119 e.mLayout = this;
120 e.mWidth = ellipsizedWidth;
121 e.mMethod = ellipsize;
122 mEllipsizedWidth = ellipsizedWidth;
123
124 mColumns = COLUMNS_ELLIPSIZE;
125 } else {
126 mColumns = COLUMNS_NORMAL;
127 mEllipsizedWidth = outerwidth;
128 }
129
130 mLines = new int[ArrayUtils.idealIntArraySize(2 * mColumns)];
131 mLineDirections = new Directions[
132 ArrayUtils.idealIntArraySize(2 * mColumns)];
133
Doug Felte8e45f22010-03-29 14:58:40 -0700134 mMeasured = MeasuredText.obtain();
135
Doug Feltcb3791202011-07-07 11:57:48 -0700136 generate(source, bufstart, bufend, paint, outerwidth, align, textDir,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800137 spacingmult, spacingadd, includepad, includepad,
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800138 ellipsizedWidth, ellipsize);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800139
Doug Felte8e45f22010-03-29 14:58:40 -0700140 mMeasured = MeasuredText.recycle(mMeasured);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800141 mFontMetricsInt = null;
142 }
143
144 /* package */ StaticLayout(boolean ellipsize) {
145 super(null, null, 0, null, 0, 0);
146
147 mColumns = COLUMNS_ELLIPSIZE;
148 mLines = new int[ArrayUtils.idealIntArraySize(2 * mColumns)];
149 mLineDirections = new Directions[
150 ArrayUtils.idealIntArraySize(2 * mColumns)];
Doug Felte8e45f22010-03-29 14:58:40 -0700151 mMeasured = MeasuredText.obtain();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800152 }
153
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800154 /* package */ void generate(CharSequence source, int bufStart, int bufEnd,
155 TextPaint paint, int outerWidth,
Doug Feltcb3791202011-07-07 11:57:48 -0700156 Alignment align, TextDirectionHeuristic textDir,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800157 float spacingmult, float spacingadd,
158 boolean includepad, boolean trackpad,
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800159 float ellipsizedWidth, TextUtils.TruncateAt ellipsize) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800160 mLineCount = 0;
161
162 int v = 0;
163 boolean needMultiply = (spacingmult != 1 || spacingadd != 0);
164
165 Paint.FontMetricsInt fm = mFontMetricsInt;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800166 int[] chooseHtv = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800167
Doug Felte8e45f22010-03-29 14:58:40 -0700168 MeasuredText measured = mMeasured;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800169
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800170 Spanned spanned = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800171 if (source instanceof Spanned)
172 spanned = (Spanned) source;
173
174 int DEFAULT_DIR = DIR_LEFT_TO_RIGHT; // XXX
175
Doug Felte8e45f22010-03-29 14:58:40 -0700176 int paraEnd;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800177 for (int paraStart = bufStart; paraStart <= bufEnd; paraStart = paraEnd) {
178 paraEnd = TextUtils.indexOf(source, CHAR_NEW_LINE, paraStart, bufEnd);
Doug Felte8e45f22010-03-29 14:58:40 -0700179 if (paraEnd < 0)
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800180 paraEnd = bufEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800181 else
Doug Felte8e45f22010-03-29 14:58:40 -0700182 paraEnd++;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800183
Doug Feltc982f602010-05-25 11:51:40 -0700184 int firstWidthLineLimit = mLineCount + 1;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800185 int firstWidth = outerWidth;
186 int restWidth = outerWidth;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800187
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800188 LineHeightSpan[] chooseHt = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800189
190 if (spanned != null) {
Eric Fischer74d31ef2010-08-05 15:29:36 -0700191 LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
Doug Felte8e45f22010-03-29 14:58:40 -0700192 LeadingMarginSpan.class);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800193 for (int i = 0; i < sp.length; i++) {
Mark Wagner7b5676e2009-10-16 11:44:23 -0700194 LeadingMarginSpan lms = sp[i];
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800195 firstWidth -= sp[i].getLeadingMargin(true);
196 restWidth -= sp[i].getLeadingMargin(false);
Doug Feltcb3791202011-07-07 11:57:48 -0700197
Doug Feltc982f602010-05-25 11:51:40 -0700198 // LeadingMarginSpan2 is odd. The count affects all
199 // leading margin spans, not just this particular one,
200 // and start from the top of the span, not the top of the
201 // paragraph.
202 if (lms instanceof LeadingMarginSpan2) {
203 LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
204 int lmsFirstLine = getLineForOffset(spanned.getSpanStart(lms2));
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800205 firstWidthLineLimit = lmsFirstLine + lms2.getLeadingMarginLineCount();
Mark Wagner7b5676e2009-10-16 11:44:23 -0700206 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800207 }
208
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800209 chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800210
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800211 if (chooseHt.length != 0) {
212 if (chooseHtv == null ||
213 chooseHtv.length < chooseHt.length) {
214 chooseHtv = new int[ArrayUtils.idealIntArraySize(
215 chooseHt.length)];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800216 }
217
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800218 for (int i = 0; i < chooseHt.length; i++) {
219 int o = spanned.getSpanStart(chooseHt[i]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800220
Doug Felte8e45f22010-03-29 14:58:40 -0700221 if (o < paraStart) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800222 // starts in this layout, before the
223 // current paragraph
224
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800225 chooseHtv[i] = getLineTop(getLineForOffset(o));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800226 } else {
227 // starts in this paragraph
228
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800229 chooseHtv[i] = v;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800230 }
231 }
232 }
233 }
234
Doug Feltcb3791202011-07-07 11:57:48 -0700235 measured.setPara(source, paraStart, paraEnd, textDir);
Doug Felte8e45f22010-03-29 14:58:40 -0700236 char[] chs = measured.mChars;
237 float[] widths = measured.mWidths;
238 byte[] chdirs = measured.mLevels;
239 int dir = measured.mDir;
240 boolean easy = measured.mEasy;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800241
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800242 int width = firstWidth;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800243
244 float w = 0;
Doug Felte8e45f22010-03-29 14:58:40 -0700245 int here = paraStart;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800246
Doug Felte8e45f22010-03-29 14:58:40 -0700247 int ok = paraStart;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800248 float okWidth = w;
249 int okAscent = 0, okDescent = 0, okTop = 0, okBottom = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800250
Doug Felte8e45f22010-03-29 14:58:40 -0700251 int fit = paraStart;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800252 float fitWidth = w;
253 int fitAscent = 0, fitDescent = 0, fitTop = 0, fitBottom = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800254
Doug Feltc982f602010-05-25 11:51:40 -0700255 boolean hasTabOrEmoji = false;
256 boolean hasTab = false;
257 TabStops tabStops = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800258
Doug Felt23241882010-06-02 14:41:06 -0700259 for (int spanStart = paraStart, spanEnd = spanStart, nextSpanStart;
260 spanStart < paraEnd; spanStart = nextSpanStart) {
Doug Felte8e45f22010-03-29 14:58:40 -0700261
Doug Felt23241882010-06-02 14:41:06 -0700262 if (spanStart == spanEnd) {
263 if (spanned == null)
264 spanEnd = paraEnd;
265 else
266 spanEnd = spanned.nextSpanTransition(spanStart, paraEnd,
267 MetricAffectingSpan.class);
268
269 int spanLen = spanEnd - spanStart;
270 if (spanned == null) {
271 measured.addStyleRun(paint, spanLen, fm);
272 } else {
273 MetricAffectingSpan[] spans =
274 spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class);
Gilles Debunne1e3ac182011-03-08 14:22:34 -0800275 spans = TextUtils.removeEmptySpans(spans, spanned,
276 MetricAffectingSpan.class);
Doug Felt23241882010-06-02 14:41:06 -0700277 measured.addStyleRun(paint, spans, spanLen, fm);
278 }
279 }
280
281 nextSpanStart = spanEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800282
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800283 int fmTop = fm.top;
284 int fmBottom = fm.bottom;
285 int fmAscent = fm.ascent;
286 int fmDescent = fm.descent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800287
Doug Felte8e45f22010-03-29 14:58:40 -0700288 for (int j = spanStart; j < spanEnd; j++) {
289 char c = chs[j - paraStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800290
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800291 if (c == CHAR_NEW_LINE) {
Gilles Debunne66111472010-11-19 11:04:37 -0800292 // intentionally left empty
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800293 } else if (c == CHAR_TAB) {
Doug Feltc982f602010-05-25 11:51:40 -0700294 if (hasTab == false) {
295 hasTab = true;
296 hasTabOrEmoji = true;
Kenny Root24ca4542010-06-22 23:46:35 -0700297 if (spanned != null) {
298 // First tab this para, check for tabstops
Eric Fischer74d31ef2010-08-05 15:29:36 -0700299 TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
Kenny Root24ca4542010-06-22 23:46:35 -0700300 paraEnd, TabStopSpan.class);
301 if (spans.length > 0) {
302 tabStops = new TabStops(TAB_INCREMENT, spans);
303 }
Doug Feltc982f602010-05-25 11:51:40 -0700304 }
305 }
306 if (tabStops != null) {
307 w = tabStops.nextTab(w);
308 } else {
309 w = TabStops.nextDefaultStop(w, TAB_INCREMENT);
310 }
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800311 } else if (c >= CHAR_FIRST_HIGH_SURROGATE && c <= CHAR_LAST_LOW_SURROGATE
312 && j + 1 < spanEnd) {
Doug Felte8e45f22010-03-29 14:58:40 -0700313 int emoji = Character.codePointAt(chs, j - paraStart);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800314
The Android Open Source Project10592532009-03-18 17:39:46 -0700315 if (emoji >= MIN_EMOJI && emoji <= MAX_EMOJI) {
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800316 Bitmap bm = EMOJI_FACTORY.getBitmapFromAndroidPua(emoji);
The Android Open Source Project10592532009-03-18 17:39:46 -0700317
318 if (bm != null) {
Eric Fischer423f0e42009-03-27 18:04:12 -0700319 Paint whichPaint;
320
321 if (spanned == null) {
322 whichPaint = paint;
323 } else {
324 whichPaint = mWorkPaint;
325 }
326
Doug Felt4e0c5e52010-03-15 16:56:02 -0700327 float wid = bm.getWidth() *
Eric Fischer423f0e42009-03-27 18:04:12 -0700328 -whichPaint.ascent() /
329 bm.getHeight();
330
331 w += wid;
Doug Feltc982f602010-05-25 11:51:40 -0700332 hasTabOrEmoji = true;
The Android Open Source Project10592532009-03-18 17:39:46 -0700333 j++;
334 } else {
Doug Felte8e45f22010-03-29 14:58:40 -0700335 w += widths[j - paraStart];
The Android Open Source Project10592532009-03-18 17:39:46 -0700336 }
337 } else {
Doug Felte8e45f22010-03-29 14:58:40 -0700338 w += widths[j - paraStart];
The Android Open Source Project10592532009-03-18 17:39:46 -0700339 }
340 } else {
Doug Felte8e45f22010-03-29 14:58:40 -0700341 w += widths[j - paraStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800342 }
343
344 // Log.e("text", "was " + before + " now " + w + " after " + c + " within " + width);
345
346 if (w <= width) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800347 fitWidth = w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800348 fit = j + 1;
349
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800350 if (fmTop < fitTop)
351 fitTop = fmTop;
352 if (fmAscent < fitAscent)
353 fitAscent = fmAscent;
354 if (fmDescent > fitDescent)
355 fitDescent = fmDescent;
356 if (fmBottom > fitBottom)
357 fitBottom = fmBottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800358
359 /*
360 * From the Unicode Line Breaking Algorithm:
361 * (at least approximately)
Doug Felt4e0c5e52010-03-15 16:56:02 -0700362 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800363 * .,:; are class IS: breakpoints
364 * except when adjacent to digits
365 * / is class SY: a breakpoint
366 * except when followed by a digit.
367 * - is class HY: a breakpoint
368 * except when followed by a digit.
369 *
Eric Fischer549d7242009-03-31 14:19:47 -0700370 * Ideographs are class ID: breakpoints when adjacent,
371 * except for NS (non-starters), which can be broken
372 * after but not before.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800373 */
374
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800375 if (c == CHAR_SPACE || c == CHAR_TAB ||
376 ((c == CHAR_DOT || c == CHAR_COMMA ||
377 c == CHAR_COLON || c == CHAR_SEMICOLON) &&
Doug Felte8e45f22010-03-29 14:58:40 -0700378 (j - 1 < here || !Character.isDigit(chs[j - 1 - paraStart])) &&
379 (j + 1 >= spanEnd || !Character.isDigit(chs[j + 1 - paraStart]))) ||
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800380 ((c == CHAR_SLASH || c == CHAR_HYPHEN) &&
Doug Felte8e45f22010-03-29 14:58:40 -0700381 (j + 1 >= spanEnd || !Character.isDigit(chs[j + 1 - paraStart]))) ||
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800382 (c >= CHAR_FIRST_CJK && isIdeographic(c, true) &&
Doug Felte8e45f22010-03-29 14:58:40 -0700383 j + 1 < spanEnd && isIdeographic(chs[j + 1 - paraStart], false))) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800384 okWidth = w;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800385 ok = j + 1;
386
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800387 if (fitTop < okTop)
388 okTop = fitTop;
389 if (fitAscent < okAscent)
390 okAscent = fitAscent;
391 if (fitDescent > okDescent)
392 okDescent = fitDescent;
393 if (fitBottom > okBottom)
394 okBottom = fitBottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800395 }
Gilles Debunne4cf435d2011-01-04 15:35:29 -0800396 } else {
Gilles Debunned434d232011-01-04 17:15:14 -0800397 if (ok != here) {
398 // Log.e("text", "output ok " + here + " to " +ok);
Gilles Debunne32ea4ff2010-12-21 11:28:34 -0800399
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800400 while (ok < spanEnd && chs[ok - paraStart] == CHAR_SPACE) {
Gilles Debunned434d232011-01-04 17:15:14 -0800401 ok++;
402 }
403
404 v = out(source,
405 here, ok,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800406 okAscent, okDescent, okTop, okBottom,
Gilles Debunned434d232011-01-04 17:15:14 -0800407 v,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800408 spacingmult, spacingadd, chooseHt,
409 chooseHtv, fm, hasTabOrEmoji,
Gilles Debunned434d232011-01-04 17:15:14 -0800410 needMultiply, paraStart, chdirs, dir, easy,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800411 ok == bufEnd, includepad, trackpad,
Gilles Debunnef3fa0cd2011-02-03 14:17:05 -0800412 chs, widths, paraStart,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800413 ellipsize, ellipsizedWidth, okWidth,
Gilles Debunned434d232011-01-04 17:15:14 -0800414 paint);
415
416 here = ok;
417 } else if (fit != here) {
418 // Log.e("text", "output fit " + here + " to " +fit);
419 v = out(source,
420 here, fit,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800421 fitAscent, fitDescent,
422 fitTop, fitBottom,
Gilles Debunned434d232011-01-04 17:15:14 -0800423 v,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800424 spacingmult, spacingadd, chooseHt,
425 chooseHtv, fm, hasTabOrEmoji,
Gilles Debunned434d232011-01-04 17:15:14 -0800426 needMultiply, paraStart, chdirs, dir, easy,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800427 fit == bufEnd, includepad, trackpad,
Gilles Debunnef3fa0cd2011-02-03 14:17:05 -0800428 chs, widths, paraStart,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800429 ellipsize, ellipsizedWidth, fitWidth,
Gilles Debunned434d232011-01-04 17:15:14 -0800430 paint);
431
432 here = fit;
433 } else {
434 // Log.e("text", "output one " + here + " to " +(here + 1));
435 // XXX not sure why the existing fm wasn't ok.
436 // measureText(paint, mWorkPaint,
437 // source, here, here + 1, fm, tab,
438 // null);
439
440 v = out(source,
441 here, here+1,
442 fm.ascent, fm.descent,
443 fm.top, fm.bottom,
444 v,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800445 spacingmult, spacingadd, chooseHt,
446 chooseHtv, fm, hasTabOrEmoji,
Gilles Debunned434d232011-01-04 17:15:14 -0800447 needMultiply, paraStart, chdirs, dir, easy,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800448 here + 1 == bufEnd, includepad,
Gilles Debunned434d232011-01-04 17:15:14 -0800449 trackpad,
Gilles Debunnef3fa0cd2011-02-03 14:17:05 -0800450 chs, widths, paraStart,
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800451 ellipsize, ellipsizedWidth,
Gilles Debunned434d232011-01-04 17:15:14 -0800452 widths[here - paraStart], paint);
453
454 here = here + 1;
455 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800456
Doug Felte8e45f22010-03-29 14:58:40 -0700457 if (here < spanStart) {
Doug Felt23241882010-06-02 14:41:06 -0700458 // didn't output all the text for this span
459 // we've measured the raw widths, though, so
460 // just reset the start point
461 j = nextSpanStart = here;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800462 } else {
463 j = here - 1; // continue looping
464 }
465
466 ok = fit = here;
467 w = 0;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800468 fitAscent = fitDescent = fitTop = fitBottom = 0;
469 okAscent = okDescent = okTop = okBottom = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800470
Doug Feltc982f602010-05-25 11:51:40 -0700471 if (--firstWidthLineLimit <= 0) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800472 width = restWidth;
Mark Wagner7b5676e2009-10-16 11:44:23 -0700473 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800474 }
475 }
476 }
477
Doug Felte8e45f22010-03-29 14:58:40 -0700478 if (paraEnd != here) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800479 if ((fitTop | fitBottom | fitDescent | fitAscent) == 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800480 paint.getFontMetricsInt(fm);
481
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800482 fitTop = fm.top;
483 fitBottom = fm.bottom;
484 fitAscent = fm.ascent;
485 fitDescent = fm.descent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800486 }
487
488 // Log.e("text", "output rest " + here + " to " + end);
489
490 v = out(source,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800491 here, paraEnd, fitAscent, fitDescent,
492 fitTop, fitBottom,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800493 v,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800494 spacingmult, spacingadd, chooseHt,
495 chooseHtv, fm, hasTabOrEmoji,
Doug Felte8e45f22010-03-29 14:58:40 -0700496 needMultiply, paraStart, chdirs, dir, easy,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800497 paraEnd == bufEnd, includepad, trackpad,
Gilles Debunnef3fa0cd2011-02-03 14:17:05 -0800498 chs, widths, paraStart,
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800499 ellipsize, ellipsizedWidth, w, paint);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800500 }
501
Doug Felte8e45f22010-03-29 14:58:40 -0700502 paraStart = paraEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800503
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800504 if (paraEnd == bufEnd)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800505 break;
506 }
507
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800508 if (bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE) {
509 // Log.e("text", "output last " + bufEnd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800510
511 paint.getFontMetricsInt(fm);
512
513 v = out(source,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800514 bufEnd, bufEnd, fm.ascent, fm.descent,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800515 fm.top, fm.bottom,
516 v,
517 spacingmult, spacingadd, null,
518 null, fm, false,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800519 needMultiply, bufEnd, null, DEFAULT_DIR, true,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800520 true, includepad, trackpad,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800521 null, null, bufStart,
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800522 ellipsize, ellipsizedWidth, 0, paint);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800523 }
524 }
525
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800526 /**
527 * Returns true if the specified character is one of those specified
528 * as being Ideographic (class ID) by the Unicode Line Breaking Algorithm
529 * (http://www.unicode.org/unicode/reports/tr14/), and is therefore OK
530 * to break between a pair of.
Eric Fischer549d7242009-03-31 14:19:47 -0700531 *
532 * @param includeNonStarters also return true for category NS
533 * (non-starters), which can be broken
534 * after but not before.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800535 */
Eric Fischer549d7242009-03-31 14:19:47 -0700536 private static final boolean isIdeographic(char c, boolean includeNonStarters) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800537 if (c >= '\u2E80' && c <= '\u2FFF') {
538 return true; // CJK, KANGXI RADICALS, DESCRIPTION SYMBOLS
539 }
540 if (c == '\u3000') {
541 return true; // IDEOGRAPHIC SPACE
542 }
543 if (c >= '\u3040' && c <= '\u309F') {
Eric Fischer549d7242009-03-31 14:19:47 -0700544 if (!includeNonStarters) {
545 switch (c) {
546 case '\u3041': // # HIRAGANA LETTER SMALL A
547 case '\u3043': // # HIRAGANA LETTER SMALL I
548 case '\u3045': // # HIRAGANA LETTER SMALL U
549 case '\u3047': // # HIRAGANA LETTER SMALL E
550 case '\u3049': // # HIRAGANA LETTER SMALL O
551 case '\u3063': // # HIRAGANA LETTER SMALL TU
552 case '\u3083': // # HIRAGANA LETTER SMALL YA
553 case '\u3085': // # HIRAGANA LETTER SMALL YU
554 case '\u3087': // # HIRAGANA LETTER SMALL YO
555 case '\u308E': // # HIRAGANA LETTER SMALL WA
556 case '\u3095': // # HIRAGANA LETTER SMALL KA
557 case '\u3096': // # HIRAGANA LETTER SMALL KE
558 case '\u309B': // # KATAKANA-HIRAGANA VOICED SOUND MARK
559 case '\u309C': // # KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK
560 case '\u309D': // # HIRAGANA ITERATION MARK
561 case '\u309E': // # HIRAGANA VOICED ITERATION MARK
562 return false;
563 }
564 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800565 return true; // Hiragana (except small characters)
566 }
567 if (c >= '\u30A0' && c <= '\u30FF') {
Eric Fischer549d7242009-03-31 14:19:47 -0700568 if (!includeNonStarters) {
569 switch (c) {
570 case '\u30A0': // # KATAKANA-HIRAGANA DOUBLE HYPHEN
571 case '\u30A1': // # KATAKANA LETTER SMALL A
572 case '\u30A3': // # KATAKANA LETTER SMALL I
573 case '\u30A5': // # KATAKANA LETTER SMALL U
574 case '\u30A7': // # KATAKANA LETTER SMALL E
575 case '\u30A9': // # KATAKANA LETTER SMALL O
576 case '\u30C3': // # KATAKANA LETTER SMALL TU
577 case '\u30E3': // # KATAKANA LETTER SMALL YA
578 case '\u30E5': // # KATAKANA LETTER SMALL YU
579 case '\u30E7': // # KATAKANA LETTER SMALL YO
580 case '\u30EE': // # KATAKANA LETTER SMALL WA
581 case '\u30F5': // # KATAKANA LETTER SMALL KA
582 case '\u30F6': // # KATAKANA LETTER SMALL KE
583 case '\u30FB': // # KATAKANA MIDDLE DOT
584 case '\u30FC': // # KATAKANA-HIRAGANA PROLONGED SOUND MARK
585 case '\u30FD': // # KATAKANA ITERATION MARK
586 case '\u30FE': // # KATAKANA VOICED ITERATION MARK
587 return false;
588 }
589 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800590 return true; // Katakana (except small characters)
591 }
592 if (c >= '\u3400' && c <= '\u4DB5') {
593 return true; // CJK UNIFIED IDEOGRAPHS EXTENSION A
594 }
595 if (c >= '\u4E00' && c <= '\u9FBB') {
596 return true; // CJK UNIFIED IDEOGRAPHS
597 }
598 if (c >= '\uF900' && c <= '\uFAD9') {
599 return true; // CJK COMPATIBILITY IDEOGRAPHS
600 }
601 if (c >= '\uA000' && c <= '\uA48F') {
602 return true; // YI SYLLABLES
603 }
604 if (c >= '\uA490' && c <= '\uA4CF') {
605 return true; // YI RADICALS
606 }
607 if (c >= '\uFE62' && c <= '\uFE66') {
608 return true; // SMALL PLUS SIGN to SMALL EQUALS SIGN
609 }
610 if (c >= '\uFF10' && c <= '\uFF19') {
611 return true; // WIDE DIGITS
612 }
613
614 return false;
615 }
616
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800617 private int out(CharSequence text, int start, int end,
618 int above, int below, int top, int bottom, int v,
619 float spacingmult, float spacingadd,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800620 LineHeightSpan[] chooseHt, int[] chooseHtv,
Doug Feltc982f602010-05-25 11:51:40 -0700621 Paint.FontMetricsInt fm, boolean hasTabOrEmoji,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800622 boolean needMultiply, int pstart, byte[] chdirs,
623 int dir, boolean easy, boolean last,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800624 boolean includePad, boolean trackPad,
625 char[] chs, float[] widths, int widthStart,
626 TextUtils.TruncateAt ellipsize, float ellipsisWidth,
627 float textWidth, TextPaint paint) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800628 int j = mLineCount;
629 int off = j * mColumns;
630 int want = off + mColumns + TOP;
631 int[] lines = mLines;
632
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800633 if (want >= lines.length) {
634 int nlen = ArrayUtils.idealIntArraySize(want + 1);
635 int[] grow = new int[nlen];
636 System.arraycopy(lines, 0, grow, 0, lines.length);
637 mLines = grow;
638 lines = grow;
639
640 Directions[] grow2 = new Directions[nlen];
641 System.arraycopy(mLineDirections, 0, grow2, 0,
642 mLineDirections.length);
643 mLineDirections = grow2;
644 }
645
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800646 if (chooseHt != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800647 fm.ascent = above;
648 fm.descent = below;
649 fm.top = top;
650 fm.bottom = bottom;
651
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800652 for (int i = 0; i < chooseHt.length; i++) {
653 if (chooseHt[i] instanceof LineHeightSpan.WithDensity) {
654 ((LineHeightSpan.WithDensity) chooseHt[i]).
655 chooseHeight(text, start, end, chooseHtv[i], v, fm, paint);
Eric Fischera9f1dd02009-08-12 15:00:10 -0700656
657 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800658 chooseHt[i].chooseHeight(text, start, end, chooseHtv[i], v, fm);
Eric Fischera9f1dd02009-08-12 15:00:10 -0700659 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800660 }
661
662 above = fm.ascent;
663 below = fm.descent;
664 top = fm.top;
665 bottom = fm.bottom;
666 }
667
668 if (j == 0) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800669 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800670 mTopPadding = top - above;
671 }
672
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800673 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800674 above = top;
675 }
676 }
677 if (last) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800678 if (trackPad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800679 mBottomPadding = bottom - below;
680 }
681
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800682 if (includePad) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800683 below = bottom;
684 }
685 }
686
687 int extra;
688
689 if (needMultiply) {
Doug Felt10657582010-02-22 11:19:01 -0800690 double ex = (below - above) * (spacingmult - 1) + spacingadd;
691 if (ex >= 0) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800692 extra = (int)(ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -0800693 } else {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800694 extra = -(int)(-ex + EXTRA_ROUNDING);
Doug Felt10657582010-02-22 11:19:01 -0800695 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800696 } else {
697 extra = 0;
698 }
699
700 lines[off + START] = start;
701 lines[off + TOP] = v;
702 lines[off + DESCENT] = below + extra;
703
704 v += (below - above) + extra;
705 lines[off + mColumns + START] = end;
706 lines[off + mColumns + TOP] = v;
707
Doug Feltc982f602010-05-25 11:51:40 -0700708 if (hasTabOrEmoji)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800709 lines[off + TAB] |= TAB_MASK;
710
Doug Felt9f7a4442010-03-01 12:45:56 -0800711 lines[off + DIR] |= dir << DIR_SHIFT;
712 Directions linedirs = DIRS_ALL_LEFT_TO_RIGHT;
713 // easy means all chars < the first RTL, so no emoji, no nothing
Doug Felt4e0c5e52010-03-15 16:56:02 -0700714 // XXX a run with no text or all spaces is easy but might be an empty
Doug Felt9f7a4442010-03-01 12:45:56 -0800715 // RTL paragraph. Make sure easy is false if this is the case.
716 if (easy) {
717 mLineDirections[j] = linedirs;
718 } else {
Gilles Debunnef3fa0cd2011-02-03 14:17:05 -0800719 mLineDirections[j] = AndroidBidi.directions(dir, chdirs, start - widthStart, chs,
720 start - widthStart, end - start);
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800721 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800722
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800723 // If ellipsize is in marquee mode, do not apply ellipsis on the first line
724 if (ellipsize != null && (ellipsize != TextUtils.TruncateAt.MARQUEE || j != 0)) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800725 calculateEllipsis(start, end, widths, widthStart,
726 ellipsisWidth, ellipsize, j,
727 textWidth, paint);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800728 }
729
730 mLineCount++;
731 return v;
732 }
733
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800734 private void calculateEllipsis(int lineStart, int lineEnd,
735 float[] widths, int widthStart,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800736 float avail, TextUtils.TruncateAt where,
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800737 int line, float textWidth, TextPaint paint) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800738
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800739 if (textWidth <= avail) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800740 // Everything fits!
741 mLines[mColumns * line + ELLIPSIS_START] = 0;
742 mLines[mColumns * line + ELLIPSIS_COUNT] = 0;
743 return;
744 }
745
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800746 float ellipsisWidth = paint.measureText(HORIZONTAL_ELLIPSIS);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800747 int ellipsisStart, ellipsisCount;
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800748 int len = lineEnd - lineStart;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800749
750 if (where == TextUtils.TruncateAt.START) {
751 float sum = 0;
752 int i;
753
754 for (i = len; i >= 0; i--) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800755 float w = widths[i - 1 + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800756
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800757 if (w + sum + ellipsisWidth > avail) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800758 break;
759 }
760
761 sum += w;
762 }
763
764 ellipsisStart = 0;
765 ellipsisCount = i;
766 } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE) {
767 float sum = 0;
768 int i;
769
770 for (i = 0; i < len; i++) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800771 float w = widths[i + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800772
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800773 if (w + sum + ellipsisWidth > avail) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800774 break;
775 }
776
777 sum += w;
778 }
779
780 ellipsisStart = i;
781 ellipsisCount = len - i;
782 } else /* where = TextUtils.TruncateAt.MIDDLE */ {
783 float lsum = 0, rsum = 0;
784 int left = 0, right = len;
785
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800786 float ravail = (avail - ellipsisWidth) / 2;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800787 for (right = len; right >= 0; right--) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800788 float w = widths[right - 1 + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800789
790 if (w + rsum > ravail) {
791 break;
792 }
793
794 rsum += w;
795 }
796
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800797 float lavail = avail - ellipsisWidth - rsum;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800798 for (left = 0; left < right; left++) {
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800799 float w = widths[left + lineStart - widthStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800800
801 if (w + lsum > lavail) {
802 break;
803 }
804
805 lsum += w;
806 }
807
808 ellipsisStart = left;
809 ellipsisCount = right - left;
810 }
811
812 mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
813 mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
814 }
815
Doug Felte8e45f22010-03-29 14:58:40 -0700816 // Override the base class so we can directly access our members,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800817 // rather than relying on member functions.
818 // The logic mirrors that of Layout.getLineForVertical
819 // FIXME: It may be faster to do a linear search for layouts without many lines.
Gilles Debunne66111472010-11-19 11:04:37 -0800820 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800821 public int getLineForVertical(int vertical) {
822 int high = mLineCount;
823 int low = -1;
824 int guess;
825 int[] lines = mLines;
826 while (high - low > 1) {
827 guess = (high + low) >> 1;
828 if (lines[mColumns * guess + TOP] > vertical){
829 high = guess;
830 } else {
831 low = guess;
832 }
833 }
834 if (low < 0) {
835 return 0;
836 } else {
837 return low;
838 }
839 }
840
Gilles Debunne66111472010-11-19 11:04:37 -0800841 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800842 public int getLineCount() {
843 return mLineCount;
844 }
845
Gilles Debunne66111472010-11-19 11:04:37 -0800846 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800847 public int getLineTop(int line) {
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800848 int top = mLines[mColumns * line + TOP];
849 if (mMaximumVisibleLineCount > 0 && line >= mMaximumVisibleLineCount &&
850 line != mLineCount) {
851 top += getBottomPadding();
852 }
853 return top;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800854 }
855
Gilles Debunne66111472010-11-19 11:04:37 -0800856 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800857 public int getLineDescent(int line) {
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800858 int descent = mLines[mColumns * line + DESCENT];
Gilles Debunnef3fa0cd2011-02-03 14:17:05 -0800859 if (mMaximumVisibleLineCount > 0 && line >= mMaximumVisibleLineCount - 1 && // -1 intended
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800860 line != mLineCount) {
861 descent += getBottomPadding();
862 }
863 return descent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800864 }
865
Gilles Debunne66111472010-11-19 11:04:37 -0800866 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800867 public int getLineStart(int line) {
868 return mLines[mColumns * line + START] & START_MASK;
869 }
870
Gilles Debunne66111472010-11-19 11:04:37 -0800871 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800872 public int getParagraphDirection(int line) {
873 return mLines[mColumns * line + DIR] >> DIR_SHIFT;
874 }
875
Gilles Debunne66111472010-11-19 11:04:37 -0800876 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800877 public boolean getLineContainsTab(int line) {
878 return (mLines[mColumns * line + TAB] & TAB_MASK) != 0;
879 }
880
Gilles Debunne66111472010-11-19 11:04:37 -0800881 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800882 public final Directions getLineDirections(int line) {
883 return mLineDirections[line];
884 }
885
Gilles Debunne66111472010-11-19 11:04:37 -0800886 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800887 public int getTopPadding() {
888 return mTopPadding;
889 }
890
Gilles Debunne66111472010-11-19 11:04:37 -0800891 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800892 public int getBottomPadding() {
893 return mBottomPadding;
894 }
895
896 @Override
897 public int getEllipsisCount(int line) {
898 if (mColumns < COLUMNS_ELLIPSIZE) {
899 return 0;
900 }
901
902 return mLines[mColumns * line + ELLIPSIS_COUNT];
903 }
904
905 @Override
906 public int getEllipsisStart(int line) {
907 if (mColumns < COLUMNS_ELLIPSIZE) {
908 return 0;
909 }
910
911 return mLines[mColumns * line + ELLIPSIS_START];
912 }
913
914 @Override
915 public int getEllipsizedWidth() {
916 return mEllipsizedWidth;
917 }
918
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800919 /**
920 * @hide
921 */
922 @Override
Gilles Debunnef3fa0cd2011-02-03 14:17:05 -0800923 public void setMaximumVisibleLineCount(int lineCount) {
924 mMaximumVisibleLineCount = lineCount;
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800925 }
926
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800927 private int mLineCount;
928 private int mTopPadding, mBottomPadding;
929 private int mColumns;
930 private int mEllipsizedWidth;
931
932 private static final int COLUMNS_NORMAL = 3;
933 private static final int COLUMNS_ELLIPSIZE = 5;
934 private static final int START = 0;
935 private static final int DIR = START;
936 private static final int TAB = START;
937 private static final int TOP = 1;
938 private static final int DESCENT = 2;
939 private static final int ELLIPSIS_START = 3;
940 private static final int ELLIPSIS_COUNT = 4;
941
942 private int[] mLines;
943 private Directions[] mLineDirections;
Gilles Debunne0a4db3c2011-01-14 12:12:04 -0800944 private int mMaximumVisibleLineCount = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800945
946 private static final int START_MASK = 0x1FFFFFFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800947 private static final int DIR_SHIFT = 30;
948 private static final int TAB_MASK = 0x20000000;
949
Doug Feltc982f602010-05-25 11:51:40 -0700950 private static final int TAB_INCREMENT = 20; // same as Layout, but that's private
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800951
Fabrice Di Meglio121c82c2011-02-15 15:44:49 -0800952 private static final char CHAR_FIRST_CJK = '\u2E80';
953
954 private static final char CHAR_NEW_LINE = '\n';
955 private static final char CHAR_TAB = '\t';
956 private static final char CHAR_SPACE = ' ';
957 private static final char CHAR_DOT = '.';
958 private static final char CHAR_COMMA = ',';
959 private static final char CHAR_COLON = ':';
960 private static final char CHAR_SEMICOLON = ';';
961 private static final char CHAR_SLASH = '/';
962 private static final char CHAR_HYPHEN = '-';
963
964 private static final double EXTRA_ROUNDING = 0.5;
965 private static final String HORIZONTAL_ELLIPSIS = "\u2026"; // this is "..."
966
967 private static final int CHAR_FIRST_HIGH_SURROGATE = 0xD800;
968 private static final int CHAR_LAST_LOW_SURROGATE = 0xDFFF;
969
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800970 /*
Doug Felte8e45f22010-03-29 14:58:40 -0700971 * This is reused across calls to generate()
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800972 */
Doug Felte8e45f22010-03-29 14:58:40 -0700973 private MeasuredText mMeasured;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800974 private Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
975}