blob: 0c6c545dfb46b75d123c5c9b3ecac1ff19492202 [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
Doug Felt4e0c5e52010-03-15 16:56:02 -070019import com.android.internal.util.ArrayUtils;
20
The Android Open Source Project10592532009-03-18 17:39:46 -070021import android.graphics.Bitmap;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080022import android.graphics.Paint;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080023import android.text.style.LeadingMarginSpan;
24import android.text.style.LineHeightSpan;
25import android.text.style.MetricAffectingSpan;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080026
27/**
28 * StaticLayout is a Layout for text that will not be edited after it
29 * is laid out. Use {@link DynamicLayout} for text that may change.
30 * <p>This is used by widgets to control text layout. You should not need
31 * to use this class directly unless you are implementing your own widget
32 * or custom display object, or would be tempted to call
Doug Felt4e0c5e52010-03-15 16:56:02 -070033 * {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int,
34 * float, float, android.graphics.Paint)
35 * Canvas.drawText()} directly.</p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080036 */
37public class
38StaticLayout
39extends Layout
40{
41 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
49 public StaticLayout(CharSequence source, int bufstart, int bufend,
50 TextPaint paint, int outerwidth,
51 Alignment align,
52 float spacingmult, float spacingadd,
53 boolean includepad) {
54 this(source, bufstart, bufend, paint, outerwidth, align,
55 spacingmult, spacingadd, includepad, null, 0);
56 }
57
58 public StaticLayout(CharSequence source, int bufstart, int bufend,
59 TextPaint paint, int outerwidth,
60 Alignment align,
61 float spacingmult, float spacingadd,
62 boolean includepad,
63 TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
64 super((ellipsize == null)
Doug Felt4e0c5e52010-03-15 16:56:02 -070065 ? source
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080066 : (source instanceof Spanned)
67 ? new SpannedEllipsizer(source)
68 : new Ellipsizer(source),
69 paint, outerwidth, align, spacingmult, spacingadd);
70
71 /*
72 * This is annoying, but we can't refer to the layout until
73 * superclass construction is finished, and the superclass
74 * constructor wants the reference to the display text.
Doug Felt4e0c5e52010-03-15 16:56:02 -070075 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080076 * This will break if the superclass constructor ever actually
77 * cares about the content instead of just holding the reference.
78 */
79 if (ellipsize != null) {
80 Ellipsizer e = (Ellipsizer) getText();
81
82 e.mLayout = this;
83 e.mWidth = ellipsizedWidth;
84 e.mMethod = ellipsize;
85 mEllipsizedWidth = ellipsizedWidth;
86
87 mColumns = COLUMNS_ELLIPSIZE;
88 } else {
89 mColumns = COLUMNS_NORMAL;
90 mEllipsizedWidth = outerwidth;
91 }
92
93 mLines = new int[ArrayUtils.idealIntArraySize(2 * mColumns)];
94 mLineDirections = new Directions[
95 ArrayUtils.idealIntArraySize(2 * mColumns)];
96
Doug Felte8e45f22010-03-29 14:58:40 -070097 mMeasured = MeasuredText.obtain();
98
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080099 generate(source, bufstart, bufend, paint, outerwidth, align,
100 spacingmult, spacingadd, includepad, includepad,
101 ellipsize != null, ellipsizedWidth, ellipsize);
102
Doug Felte8e45f22010-03-29 14:58:40 -0700103 mMeasured = MeasuredText.recycle(mMeasured);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800104 mFontMetricsInt = null;
105 }
106
107 /* package */ StaticLayout(boolean ellipsize) {
108 super(null, null, 0, null, 0, 0);
109
110 mColumns = COLUMNS_ELLIPSIZE;
111 mLines = new int[ArrayUtils.idealIntArraySize(2 * mColumns)];
112 mLineDirections = new Directions[
113 ArrayUtils.idealIntArraySize(2 * mColumns)];
Doug Felte8e45f22010-03-29 14:58:40 -0700114 mMeasured = MeasuredText.obtain();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800115 }
116
117 /* package */ void generate(CharSequence source, int bufstart, int bufend,
118 TextPaint paint, int outerwidth,
119 Alignment align,
120 float spacingmult, float spacingadd,
121 boolean includepad, boolean trackpad,
122 boolean breakOnlyAtSpaces,
123 float ellipsizedWidth, TextUtils.TruncateAt where) {
124 mLineCount = 0;
125
126 int v = 0;
127 boolean needMultiply = (spacingmult != 1 || spacingadd != 0);
128
129 Paint.FontMetricsInt fm = mFontMetricsInt;
130 int[] choosehtv = null;
131
Doug Felte8e45f22010-03-29 14:58:40 -0700132 MeasuredText measured = mMeasured;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800133
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800134 Spanned spanned = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800135 if (source instanceof Spanned)
136 spanned = (Spanned) source;
137
138 int DEFAULT_DIR = DIR_LEFT_TO_RIGHT; // XXX
139
Doug Felte8e45f22010-03-29 14:58:40 -0700140 int paraEnd;
141 for (int paraStart = bufstart; paraStart <= bufend; paraStart = paraEnd) {
142 paraEnd = TextUtils.indexOf(source, '\n', paraStart, bufend);
143 if (paraEnd < 0)
144 paraEnd = bufend;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800145 else
Doug Felte8e45f22010-03-29 14:58:40 -0700146 paraEnd++;
147 int paraLen = paraEnd - paraStart;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800148
Mark Wagner7b5676e2009-10-16 11:44:23 -0700149 int firstWidthLineCount = 1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800150 int firstwidth = outerwidth;
151 int restwidth = outerwidth;
152
153 LineHeightSpan[] chooseht = null;
154
155 if (spanned != null) {
Doug Felte8e45f22010-03-29 14:58:40 -0700156 LeadingMarginSpan[] sp = spanned.getSpans(paraStart, paraEnd,
157 LeadingMarginSpan.class);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800158 for (int i = 0; i < sp.length; i++) {
Mark Wagner7b5676e2009-10-16 11:44:23 -0700159 LeadingMarginSpan lms = sp[i];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800160 firstwidth -= sp[i].getLeadingMargin(true);
161 restwidth -= sp[i].getLeadingMargin(false);
Mark Wagner7b5676e2009-10-16 11:44:23 -0700162 if (lms instanceof LeadingMarginSpan.LeadingMarginSpan2) {
Doug Felte8e45f22010-03-29 14:58:40 -0700163 firstWidthLineCount =
164 ((LeadingMarginSpan.LeadingMarginSpan2)lms)
165 .getLeadingMarginLineCount();
Mark Wagner7b5676e2009-10-16 11:44:23 -0700166 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800167 }
168
Doug Felte8e45f22010-03-29 14:58:40 -0700169 chooseht = spanned.getSpans(paraStart, paraEnd, LineHeightSpan.class);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800170
171 if (chooseht.length != 0) {
172 if (choosehtv == null ||
173 choosehtv.length < chooseht.length) {
174 choosehtv = new int[ArrayUtils.idealIntArraySize(
175 chooseht.length)];
176 }
177
178 for (int i = 0; i < chooseht.length; i++) {
179 int o = spanned.getSpanStart(chooseht[i]);
180
Doug Felte8e45f22010-03-29 14:58:40 -0700181 if (o < paraStart) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800182 // starts in this layout, before the
183 // current paragraph
184
Doug Felt4e0c5e52010-03-15 16:56:02 -0700185 choosehtv[i] = getLineTop(getLineForOffset(o));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800186 } else {
187 // starts in this paragraph
188
189 choosehtv[i] = v;
190 }
191 }
192 }
193 }
194
Doug Felte8e45f22010-03-29 14:58:40 -0700195 measured.setPara(source, paraStart, paraEnd, DIR_REQUEST_DEFAULT_LTR);
196 char[] chs = measured.mChars;
197 float[] widths = measured.mWidths;
198 byte[] chdirs = measured.mLevels;
199 int dir = measured.mDir;
200 boolean easy = measured.mEasy;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800201
Doug Felte8e45f22010-03-29 14:58:40 -0700202 CharSequence sub = source;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800203
204 int width = firstwidth;
205
206 float w = 0;
Doug Felte8e45f22010-03-29 14:58:40 -0700207 int here = paraStart;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800208
Doug Felte8e45f22010-03-29 14:58:40 -0700209 int ok = paraStart;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800210 float okwidth = w;
211 int okascent = 0, okdescent = 0, oktop = 0, okbottom = 0;
212
Doug Felte8e45f22010-03-29 14:58:40 -0700213 int fit = paraStart;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800214 float fitwidth = w;
215 int fitascent = 0, fitdescent = 0, fittop = 0, fitbottom = 0;
216
217 boolean tab = false;
218
Doug Felte8e45f22010-03-29 14:58:40 -0700219 int spanEnd;
220 for (int spanStart = paraStart; spanStart < paraEnd; spanStart = spanEnd) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800221 if (spanned == null)
Doug Felte8e45f22010-03-29 14:58:40 -0700222 spanEnd = paraEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800223 else
Doug Felte8e45f22010-03-29 14:58:40 -0700224 spanEnd = spanned.nextSpanTransition(spanStart, paraEnd,
225 MetricAffectingSpan.class);
226
227 int spanLen = spanEnd - spanStart;
228 int startInPara = spanStart - paraStart;
229 int endInPara = spanEnd - paraStart;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800230
231 if (spanned == null) {
Doug Felte8e45f22010-03-29 14:58:40 -0700232 measured.addStyleRun(paint, spanLen, fm);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800233 } else {
Doug Felte8e45f22010-03-29 14:58:40 -0700234 MetricAffectingSpan[] spans =
235 spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class);
236 measured.addStyleRun(paint, spans, spanLen, fm);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800237 }
238
239 int fmtop = fm.top;
240 int fmbottom = fm.bottom;
241 int fmascent = fm.ascent;
242 int fmdescent = fm.descent;
243
Doug Felte8e45f22010-03-29 14:58:40 -0700244 for (int j = spanStart; j < spanEnd; j++) {
245 char c = chs[j - paraStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800246 float before = w;
247
The Android Open Source Project10592532009-03-18 17:39:46 -0700248 if (c == '\n') {
249 ;
250 } else if (c == '\t') {
Doug Felte8e45f22010-03-29 14:58:40 -0700251 w = Layout.nextTab(sub, paraStart, paraEnd, w, null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800252 tab = true;
Doug Felte8e45f22010-03-29 14:58:40 -0700253 } else if (c >= 0xD800 && c <= 0xDFFF && j + 1 < spanEnd) {
254 int emoji = Character.codePointAt(chs, j - paraStart);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800255
The Android Open Source Project10592532009-03-18 17:39:46 -0700256 if (emoji >= MIN_EMOJI && emoji <= MAX_EMOJI) {
257 Bitmap bm = EMOJI_FACTORY.
258 getBitmapFromAndroidPua(emoji);
259
260 if (bm != null) {
Eric Fischer423f0e42009-03-27 18:04:12 -0700261 Paint whichPaint;
262
263 if (spanned == null) {
264 whichPaint = paint;
265 } else {
266 whichPaint = mWorkPaint;
267 }
268
Doug Felt4e0c5e52010-03-15 16:56:02 -0700269 float wid = bm.getWidth() *
Eric Fischer423f0e42009-03-27 18:04:12 -0700270 -whichPaint.ascent() /
271 bm.getHeight();
272
273 w += wid;
The Android Open Source Project10592532009-03-18 17:39:46 -0700274 tab = true;
275 j++;
276 } else {
Doug Felte8e45f22010-03-29 14:58:40 -0700277 w += widths[j - paraStart];
The Android Open Source Project10592532009-03-18 17:39:46 -0700278 }
279 } else {
Doug Felte8e45f22010-03-29 14:58:40 -0700280 w += widths[j - paraStart];
The Android Open Source Project10592532009-03-18 17:39:46 -0700281 }
282 } else {
Doug Felte8e45f22010-03-29 14:58:40 -0700283 w += widths[j - paraStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800284 }
285
286 // Log.e("text", "was " + before + " now " + w + " after " + c + " within " + width);
287
288 if (w <= width) {
289 fitwidth = w;
290 fit = j + 1;
291
292 if (fmtop < fittop)
293 fittop = fmtop;
294 if (fmascent < fitascent)
295 fitascent = fmascent;
296 if (fmdescent > fitdescent)
297 fitdescent = fmdescent;
298 if (fmbottom > fitbottom)
299 fitbottom = fmbottom;
300
301 /*
302 * From the Unicode Line Breaking Algorithm:
303 * (at least approximately)
Doug Felt4e0c5e52010-03-15 16:56:02 -0700304 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800305 * .,:; are class IS: breakpoints
306 * except when adjacent to digits
307 * / is class SY: a breakpoint
308 * except when followed by a digit.
309 * - is class HY: a breakpoint
310 * except when followed by a digit.
311 *
Eric Fischer549d7242009-03-31 14:19:47 -0700312 * Ideographs are class ID: breakpoints when adjacent,
313 * except for NS (non-starters), which can be broken
314 * after but not before.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800315 */
316
317 if (c == ' ' || c == '\t' ||
318 ((c == '.' || c == ',' || c == ':' || c == ';') &&
Doug Felte8e45f22010-03-29 14:58:40 -0700319 (j - 1 < here || !Character.isDigit(chs[j - 1 - paraStart])) &&
320 (j + 1 >= spanEnd || !Character.isDigit(chs[j + 1 - paraStart]))) ||
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800321 ((c == '/' || c == '-') &&
Doug Felte8e45f22010-03-29 14:58:40 -0700322 (j + 1 >= spanEnd || !Character.isDigit(chs[j + 1 - paraStart]))) ||
Eric Fischer549d7242009-03-31 14:19:47 -0700323 (c >= FIRST_CJK && isIdeographic(c, true) &&
Doug Felte8e45f22010-03-29 14:58:40 -0700324 j + 1 < spanEnd && isIdeographic(chs[j + 1 - paraStart], false))) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800325 okwidth = w;
326 ok = j + 1;
327
328 if (fittop < oktop)
329 oktop = fittop;
330 if (fitascent < okascent)
331 okascent = fitascent;
332 if (fitdescent > okdescent)
333 okdescent = fitdescent;
334 if (fitbottom > okbottom)
335 okbottom = fitbottom;
336 }
337 } else if (breakOnlyAtSpaces) {
338 if (ok != here) {
339 // Log.e("text", "output ok " + here + " to " +ok);
340
Doug Felte8e45f22010-03-29 14:58:40 -0700341 while (ok < spanEnd && chs[ok - paraStart] == ' ') {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800342 ok++;
343 }
344
345 v = out(source,
346 here, ok,
347 okascent, okdescent, oktop, okbottom,
348 v,
349 spacingmult, spacingadd, chooseht,
350 choosehtv, fm, tab,
Doug Felte8e45f22010-03-29 14:58:40 -0700351 needMultiply, paraStart, chdirs, dir, easy,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800352 ok == bufend, includepad, trackpad,
Doug Felte8e45f22010-03-29 14:58:40 -0700353 chs, widths, here - paraStart,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800354 where, ellipsizedWidth, okwidth,
355 paint);
356
357 here = ok;
358 } else {
359 // Act like it fit even though it didn't.
360
361 fitwidth = w;
362 fit = j + 1;
363
364 if (fmtop < fittop)
365 fittop = fmtop;
366 if (fmascent < fitascent)
367 fitascent = fmascent;
368 if (fmdescent > fitdescent)
369 fitdescent = fmdescent;
370 if (fmbottom > fitbottom)
371 fitbottom = fmbottom;
372 }
373 } else {
374 if (ok != here) {
375 // Log.e("text", "output ok " + here + " to " +ok);
376
Doug Felte8e45f22010-03-29 14:58:40 -0700377 while (ok < spanEnd && chs[ok - paraStart] == ' ') {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800378 ok++;
379 }
380
381 v = out(source,
382 here, ok,
383 okascent, okdescent, oktop, okbottom,
384 v,
385 spacingmult, spacingadd, chooseht,
386 choosehtv, fm, tab,
Doug Felte8e45f22010-03-29 14:58:40 -0700387 needMultiply, paraStart, chdirs, dir, easy,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800388 ok == bufend, includepad, trackpad,
Doug Felte8e45f22010-03-29 14:58:40 -0700389 chs, widths, here - paraStart,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800390 where, ellipsizedWidth, okwidth,
391 paint);
392
393 here = ok;
394 } else if (fit != here) {
395 // Log.e("text", "output fit " + here + " to " +fit);
396 v = out(source,
397 here, fit,
398 fitascent, fitdescent,
399 fittop, fitbottom,
400 v,
401 spacingmult, spacingadd, chooseht,
402 choosehtv, fm, tab,
Doug Felte8e45f22010-03-29 14:58:40 -0700403 needMultiply, paraStart, chdirs, dir, easy,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800404 fit == bufend, includepad, trackpad,
Doug Felte8e45f22010-03-29 14:58:40 -0700405 chs, widths, here - paraStart,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800406 where, ellipsizedWidth, fitwidth,
407 paint);
408
409 here = fit;
410 } else {
411 // Log.e("text", "output one " + here + " to " +(here + 1));
Doug Felte8e45f22010-03-29 14:58:40 -0700412 // XXX not sure why the existing fm wasn't ok.
413 // measureText(paint, mWorkPaint,
414 // source, here, here + 1, fm, tab,
415 // null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800416
417 v = out(source,
418 here, here+1,
419 fm.ascent, fm.descent,
420 fm.top, fm.bottom,
421 v,
422 spacingmult, spacingadd, chooseht,
423 choosehtv, fm, tab,
Doug Felte8e45f22010-03-29 14:58:40 -0700424 needMultiply, paraStart, chdirs, dir, easy,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800425 here + 1 == bufend, includepad,
426 trackpad,
Doug Felte8e45f22010-03-29 14:58:40 -0700427 chs, widths, here - paraStart,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800428 where, ellipsizedWidth,
Doug Felte8e45f22010-03-29 14:58:40 -0700429 widths[here - paraStart], paint);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800430
431 here = here + 1;
432 }
433
Doug Felte8e45f22010-03-29 14:58:40 -0700434 if (here < spanStart) {
435 j = spanEnd = here; // must remeasure
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800436 } else {
437 j = here - 1; // continue looping
438 }
439
440 ok = fit = here;
441 w = 0;
442 fitascent = fitdescent = fittop = fitbottom = 0;
443 okascent = okdescent = oktop = okbottom = 0;
444
Mark Wagner7b5676e2009-10-16 11:44:23 -0700445 if (--firstWidthLineCount <= 0) {
446 width = restwidth;
447 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800448 }
449 }
450 }
451
Doug Felte8e45f22010-03-29 14:58:40 -0700452 if (paraEnd != here) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800453 if ((fittop | fitbottom | fitdescent | fitascent) == 0) {
454 paint.getFontMetricsInt(fm);
455
456 fittop = fm.top;
457 fitbottom = fm.bottom;
458 fitascent = fm.ascent;
459 fitdescent = fm.descent;
460 }
461
462 // Log.e("text", "output rest " + here + " to " + end);
463
464 v = out(source,
Doug Felte8e45f22010-03-29 14:58:40 -0700465 here, paraEnd, fitascent, fitdescent,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800466 fittop, fitbottom,
467 v,
468 spacingmult, spacingadd, chooseht,
469 choosehtv, fm, tab,
Doug Felte8e45f22010-03-29 14:58:40 -0700470 needMultiply, paraStart, chdirs, dir, easy,
471 paraEnd == bufend, includepad, trackpad,
472 chs, widths, here - paraStart,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800473 where, ellipsizedWidth, w, paint);
474 }
475
Doug Felte8e45f22010-03-29 14:58:40 -0700476 paraStart = paraEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800477
Doug Felte8e45f22010-03-29 14:58:40 -0700478 if (paraEnd == bufend)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800479 break;
480 }
481
482 if (bufend == bufstart || source.charAt(bufend - 1) == '\n') {
483 // Log.e("text", "output last " + bufend);
484
485 paint.getFontMetricsInt(fm);
486
487 v = out(source,
488 bufend, bufend, fm.ascent, fm.descent,
489 fm.top, fm.bottom,
490 v,
491 spacingmult, spacingadd, null,
492 null, fm, false,
Doug Felte8e45f22010-03-29 14:58:40 -0700493 needMultiply, bufend, null, DEFAULT_DIR, true,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800494 true, includepad, trackpad,
Doug Felte8e45f22010-03-29 14:58:40 -0700495 null, null, bufstart,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800496 where, ellipsizedWidth, 0, paint);
497 }
498 }
499
500 private static final char FIRST_CJK = '\u2E80';
501 /**
502 * Returns true if the specified character is one of those specified
503 * as being Ideographic (class ID) by the Unicode Line Breaking Algorithm
504 * (http://www.unicode.org/unicode/reports/tr14/), and is therefore OK
505 * to break between a pair of.
Eric Fischer549d7242009-03-31 14:19:47 -0700506 *
507 * @param includeNonStarters also return true for category NS
508 * (non-starters), which can be broken
509 * after but not before.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800510 */
Eric Fischer549d7242009-03-31 14:19:47 -0700511 private static final boolean isIdeographic(char c, boolean includeNonStarters) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800512 if (c >= '\u2E80' && c <= '\u2FFF') {
513 return true; // CJK, KANGXI RADICALS, DESCRIPTION SYMBOLS
514 }
515 if (c == '\u3000') {
516 return true; // IDEOGRAPHIC SPACE
517 }
518 if (c >= '\u3040' && c <= '\u309F') {
Eric Fischer549d7242009-03-31 14:19:47 -0700519 if (!includeNonStarters) {
520 switch (c) {
521 case '\u3041': // # HIRAGANA LETTER SMALL A
522 case '\u3043': // # HIRAGANA LETTER SMALL I
523 case '\u3045': // # HIRAGANA LETTER SMALL U
524 case '\u3047': // # HIRAGANA LETTER SMALL E
525 case '\u3049': // # HIRAGANA LETTER SMALL O
526 case '\u3063': // # HIRAGANA LETTER SMALL TU
527 case '\u3083': // # HIRAGANA LETTER SMALL YA
528 case '\u3085': // # HIRAGANA LETTER SMALL YU
529 case '\u3087': // # HIRAGANA LETTER SMALL YO
530 case '\u308E': // # HIRAGANA LETTER SMALL WA
531 case '\u3095': // # HIRAGANA LETTER SMALL KA
532 case '\u3096': // # HIRAGANA LETTER SMALL KE
533 case '\u309B': // # KATAKANA-HIRAGANA VOICED SOUND MARK
534 case '\u309C': // # KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK
535 case '\u309D': // # HIRAGANA ITERATION MARK
536 case '\u309E': // # HIRAGANA VOICED ITERATION MARK
537 return false;
538 }
539 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800540 return true; // Hiragana (except small characters)
541 }
542 if (c >= '\u30A0' && c <= '\u30FF') {
Eric Fischer549d7242009-03-31 14:19:47 -0700543 if (!includeNonStarters) {
544 switch (c) {
545 case '\u30A0': // # KATAKANA-HIRAGANA DOUBLE HYPHEN
546 case '\u30A1': // # KATAKANA LETTER SMALL A
547 case '\u30A3': // # KATAKANA LETTER SMALL I
548 case '\u30A5': // # KATAKANA LETTER SMALL U
549 case '\u30A7': // # KATAKANA LETTER SMALL E
550 case '\u30A9': // # KATAKANA LETTER SMALL O
551 case '\u30C3': // # KATAKANA LETTER SMALL TU
552 case '\u30E3': // # KATAKANA LETTER SMALL YA
553 case '\u30E5': // # KATAKANA LETTER SMALL YU
554 case '\u30E7': // # KATAKANA LETTER SMALL YO
555 case '\u30EE': // # KATAKANA LETTER SMALL WA
556 case '\u30F5': // # KATAKANA LETTER SMALL KA
557 case '\u30F6': // # KATAKANA LETTER SMALL KE
558 case '\u30FB': // # KATAKANA MIDDLE DOT
559 case '\u30FC': // # KATAKANA-HIRAGANA PROLONGED SOUND MARK
560 case '\u30FD': // # KATAKANA ITERATION MARK
561 case '\u30FE': // # KATAKANA VOICED ITERATION MARK
562 return false;
563 }
564 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800565 return true; // Katakana (except small characters)
566 }
567 if (c >= '\u3400' && c <= '\u4DB5') {
568 return true; // CJK UNIFIED IDEOGRAPHS EXTENSION A
569 }
570 if (c >= '\u4E00' && c <= '\u9FBB') {
571 return true; // CJK UNIFIED IDEOGRAPHS
572 }
573 if (c >= '\uF900' && c <= '\uFAD9') {
574 return true; // CJK COMPATIBILITY IDEOGRAPHS
575 }
576 if (c >= '\uA000' && c <= '\uA48F') {
577 return true; // YI SYLLABLES
578 }
579 if (c >= '\uA490' && c <= '\uA4CF') {
580 return true; // YI RADICALS
581 }
582 if (c >= '\uFE62' && c <= '\uFE66') {
583 return true; // SMALL PLUS SIGN to SMALL EQUALS SIGN
584 }
585 if (c >= '\uFF10' && c <= '\uFF19') {
586 return true; // WIDE DIGITS
587 }
588
589 return false;
590 }
591
592/*
593 private static void dump(byte[] data, int count, String label) {
594 if (false) {
595 System.out.print(label);
596
597 for (int i = 0; i < count; i++)
598 System.out.print(" " + data[i]);
599
600 System.out.println();
601 }
602 }
603*/
604
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800605 private int out(CharSequence text, int start, int end,
606 int above, int below, int top, int bottom, int v,
607 float spacingmult, float spacingadd,
608 LineHeightSpan[] chooseht, int[] choosehtv,
609 Paint.FontMetricsInt fm, boolean tab,
610 boolean needMultiply, int pstart, byte[] chdirs,
611 int dir, boolean easy, boolean last,
612 boolean includepad, boolean trackpad,
Doug Felte8e45f22010-03-29 14:58:40 -0700613 char[] chs, float[] widths, int widstart,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800614 TextUtils.TruncateAt ellipsize, float ellipsiswidth,
615 float textwidth, TextPaint paint) {
616 int j = mLineCount;
617 int off = j * mColumns;
618 int want = off + mColumns + TOP;
619 int[] lines = mLines;
620
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800621 if (want >= lines.length) {
622 int nlen = ArrayUtils.idealIntArraySize(want + 1);
623 int[] grow = new int[nlen];
624 System.arraycopy(lines, 0, grow, 0, lines.length);
625 mLines = grow;
626 lines = grow;
627
628 Directions[] grow2 = new Directions[nlen];
629 System.arraycopy(mLineDirections, 0, grow2, 0,
630 mLineDirections.length);
631 mLineDirections = grow2;
632 }
633
634 if (chooseht != null) {
635 fm.ascent = above;
636 fm.descent = below;
637 fm.top = top;
638 fm.bottom = bottom;
639
640 for (int i = 0; i < chooseht.length; i++) {
Eric Fischera9f1dd02009-08-12 15:00:10 -0700641 if (chooseht[i] instanceof LineHeightSpan.WithDensity) {
642 ((LineHeightSpan.WithDensity) chooseht[i]).
643 chooseHeight(text, start, end, choosehtv[i], v, fm, paint);
644
645 } else {
646 chooseht[i].chooseHeight(text, start, end, choosehtv[i], v, fm);
647 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800648 }
649
650 above = fm.ascent;
651 below = fm.descent;
652 top = fm.top;
653 bottom = fm.bottom;
654 }
655
656 if (j == 0) {
657 if (trackpad) {
658 mTopPadding = top - above;
659 }
660
661 if (includepad) {
662 above = top;
663 }
664 }
665 if (last) {
666 if (trackpad) {
667 mBottomPadding = bottom - below;
668 }
669
670 if (includepad) {
671 below = bottom;
672 }
673 }
674
675 int extra;
676
677 if (needMultiply) {
Doug Felt10657582010-02-22 11:19:01 -0800678 double ex = (below - above) * (spacingmult - 1) + spacingadd;
679 if (ex >= 0) {
680 extra = (int)(ex + 0.5);
681 } else {
682 extra = -(int)(-ex + 0.5);
683 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800684 } else {
685 extra = 0;
686 }
687
688 lines[off + START] = start;
689 lines[off + TOP] = v;
690 lines[off + DESCENT] = below + extra;
691
692 v += (below - above) + extra;
693 lines[off + mColumns + START] = end;
694 lines[off + mColumns + TOP] = v;
695
696 if (tab)
697 lines[off + TAB] |= TAB_MASK;
698
Doug Felt9f7a4442010-03-01 12:45:56 -0800699 lines[off + DIR] |= dir << DIR_SHIFT;
700 Directions linedirs = DIRS_ALL_LEFT_TO_RIGHT;
701 // easy means all chars < the first RTL, so no emoji, no nothing
Doug Felt4e0c5e52010-03-15 16:56:02 -0700702 // XXX a run with no text or all spaces is easy but might be an empty
Doug Felt9f7a4442010-03-01 12:45:56 -0800703 // RTL paragraph. Make sure easy is false if this is the case.
704 if (easy) {
705 mLineDirections[j] = linedirs;
706 } else {
Doug Felte8e45f22010-03-29 14:58:40 -0700707 mLineDirections[j] = AndroidBidi.directions(dir, chdirs, widstart, chs,
708 widstart, end - start);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800709
710 // If ellipsize is in marquee mode, do not apply ellipsis on the first line
711 if (ellipsize != null && (ellipsize != TextUtils.TruncateAt.MARQUEE || j != 0)) {
Doug Felte8e45f22010-03-29 14:58:40 -0700712 calculateEllipsis(start, end, widths, widstart,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800713 ellipsiswidth, ellipsize, j,
714 textwidth, paint);
715 }
716 }
717
718 mLineCount++;
719 return v;
720 }
721
722 private void calculateEllipsis(int linestart, int lineend,
Doug Felte8e45f22010-03-29 14:58:40 -0700723 float[] widths, int widstart,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800724 float avail, TextUtils.TruncateAt where,
725 int line, float textwidth, TextPaint paint) {
726 int len = lineend - linestart;
727
728 if (textwidth <= avail) {
729 // Everything fits!
730 mLines[mColumns * line + ELLIPSIS_START] = 0;
731 mLines[mColumns * line + ELLIPSIS_COUNT] = 0;
732 return;
733 }
734
735 float ellipsiswid = paint.measureText("\u2026");
736 int ellipsisStart, ellipsisCount;
737
738 if (where == TextUtils.TruncateAt.START) {
739 float sum = 0;
740 int i;
741
742 for (i = len; i >= 0; i--) {
Doug Felte8e45f22010-03-29 14:58:40 -0700743 float w = widths[i - 1 + linestart - widstart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800744
745 if (w + sum + ellipsiswid > avail) {
746 break;
747 }
748
749 sum += w;
750 }
751
752 ellipsisStart = 0;
753 ellipsisCount = i;
754 } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE) {
755 float sum = 0;
756 int i;
757
758 for (i = 0; i < len; i++) {
Doug Felte8e45f22010-03-29 14:58:40 -0700759 float w = widths[i + linestart - widstart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800760
761 if (w + sum + ellipsiswid > avail) {
762 break;
763 }
764
765 sum += w;
766 }
767
768 ellipsisStart = i;
769 ellipsisCount = len - i;
770 } else /* where = TextUtils.TruncateAt.MIDDLE */ {
771 float lsum = 0, rsum = 0;
772 int left = 0, right = len;
773
774 float ravail = (avail - ellipsiswid) / 2;
775 for (right = len; right >= 0; right--) {
Doug Felte8e45f22010-03-29 14:58:40 -0700776 float w = widths[right - 1 + linestart - widstart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800777
778 if (w + rsum > ravail) {
779 break;
780 }
781
782 rsum += w;
783 }
784
785 float lavail = avail - ellipsiswid - rsum;
786 for (left = 0; left < right; left++) {
Doug Felte8e45f22010-03-29 14:58:40 -0700787 float w = widths[left + linestart - widstart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800788
789 if (w + lsum > lavail) {
790 break;
791 }
792
793 lsum += w;
794 }
795
796 ellipsisStart = left;
797 ellipsisCount = right - left;
798 }
799
800 mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
801 mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
802 }
803
Doug Felte8e45f22010-03-29 14:58:40 -0700804 // Override the base class so we can directly access our members,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800805 // rather than relying on member functions.
806 // The logic mirrors that of Layout.getLineForVertical
807 // FIXME: It may be faster to do a linear search for layouts without many lines.
808 public int getLineForVertical(int vertical) {
809 int high = mLineCount;
810 int low = -1;
811 int guess;
812 int[] lines = mLines;
813 while (high - low > 1) {
814 guess = (high + low) >> 1;
815 if (lines[mColumns * guess + TOP] > vertical){
816 high = guess;
817 } else {
818 low = guess;
819 }
820 }
821 if (low < 0) {
822 return 0;
823 } else {
824 return low;
825 }
826 }
827
828 public int getLineCount() {
829 return mLineCount;
830 }
831
832 public int getLineTop(int line) {
Doug Felt4e0c5e52010-03-15 16:56:02 -0700833 return mLines[mColumns * line + TOP];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800834 }
835
836 public int getLineDescent(int line) {
Doug Felt4e0c5e52010-03-15 16:56:02 -0700837 return mLines[mColumns * line + DESCENT];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800838 }
839
840 public int getLineStart(int line) {
841 return mLines[mColumns * line + START] & START_MASK;
842 }
843
844 public int getParagraphDirection(int line) {
845 return mLines[mColumns * line + DIR] >> DIR_SHIFT;
846 }
847
848 public boolean getLineContainsTab(int line) {
849 return (mLines[mColumns * line + TAB] & TAB_MASK) != 0;
850 }
851
852 public final Directions getLineDirections(int line) {
853 return mLineDirections[line];
854 }
855
856 public int getTopPadding() {
857 return mTopPadding;
858 }
859
860 public int getBottomPadding() {
861 return mBottomPadding;
862 }
863
864 @Override
865 public int getEllipsisCount(int line) {
866 if (mColumns < COLUMNS_ELLIPSIZE) {
867 return 0;
868 }
869
870 return mLines[mColumns * line + ELLIPSIS_COUNT];
871 }
872
873 @Override
874 public int getEllipsisStart(int line) {
875 if (mColumns < COLUMNS_ELLIPSIZE) {
876 return 0;
877 }
878
879 return mLines[mColumns * line + ELLIPSIS_START];
880 }
881
882 @Override
883 public int getEllipsizedWidth() {
884 return mEllipsizedWidth;
885 }
886
887 private int mLineCount;
888 private int mTopPadding, mBottomPadding;
889 private int mColumns;
890 private int mEllipsizedWidth;
891
892 private static final int COLUMNS_NORMAL = 3;
893 private static final int COLUMNS_ELLIPSIZE = 5;
894 private static final int START = 0;
895 private static final int DIR = START;
896 private static final int TAB = START;
897 private static final int TOP = 1;
898 private static final int DESCENT = 2;
899 private static final int ELLIPSIS_START = 3;
900 private static final int ELLIPSIS_COUNT = 4;
901
902 private int[] mLines;
903 private Directions[] mLineDirections;
904
905 private static final int START_MASK = 0x1FFFFFFF;
906 private static final int DIR_MASK = 0xC0000000;
907 private static final int DIR_SHIFT = 30;
908 private static final int TAB_MASK = 0x20000000;
909
910 private static final char FIRST_RIGHT_TO_LEFT = '\u0590';
911
912 /*
Doug Felte8e45f22010-03-29 14:58:40 -0700913 * This is reused across calls to generate()
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800914 */
Doug Felte8e45f22010-03-29 14:58:40 -0700915 private MeasuredText mMeasured;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800916 private Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
917}