blob: 3dd1ecd34ad876fecc43bf440a7f2e745b95a634 [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;
Gilles Debunne66111472010-11-19 11:04:37 -080024import android.text.style.LeadingMarginSpan.LeadingMarginSpan2;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080025import android.text.style.LineHeightSpan;
26import android.text.style.MetricAffectingSpan;
Doug Feltc982f602010-05-25 11:51:40 -070027import android.text.style.TabStopSpan;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080028
29/**
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 */
Gilles Debunne66111472010-11-19 11:04:37 -080039public class StaticLayout extends Layout
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080040{
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
Doug Feltc982f602010-05-25 11:51:40 -0700149 int firstWidthLineLimit = mLineCount + 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) {
Eric Fischer74d31ef2010-08-05 15:29:36 -0700156 LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
Doug Felte8e45f22010-03-29 14:58:40 -0700157 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);
Doug Feltc982f602010-05-25 11:51:40 -0700162
163 // LeadingMarginSpan2 is odd. The count affects all
164 // leading margin spans, not just this particular one,
165 // and start from the top of the span, not the top of the
166 // paragraph.
167 if (lms instanceof LeadingMarginSpan2) {
168 LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
169 int lmsFirstLine = getLineForOffset(spanned.getSpanStart(lms2));
170 firstWidthLineLimit = lmsFirstLine +
171 lms2.getLeadingMarginLineCount();
Mark Wagner7b5676e2009-10-16 11:44:23 -0700172 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800173 }
174
Eric Fischer74d31ef2010-08-05 15:29:36 -0700175 chooseht = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800176
177 if (chooseht.length != 0) {
178 if (choosehtv == null ||
179 choosehtv.length < chooseht.length) {
180 choosehtv = new int[ArrayUtils.idealIntArraySize(
181 chooseht.length)];
182 }
183
184 for (int i = 0; i < chooseht.length; i++) {
185 int o = spanned.getSpanStart(chooseht[i]);
186
Doug Felte8e45f22010-03-29 14:58:40 -0700187 if (o < paraStart) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800188 // starts in this layout, before the
189 // current paragraph
190
Doug Felt4e0c5e52010-03-15 16:56:02 -0700191 choosehtv[i] = getLineTop(getLineForOffset(o));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800192 } else {
193 // starts in this paragraph
194
195 choosehtv[i] = v;
196 }
197 }
198 }
199 }
200
Doug Felte8e45f22010-03-29 14:58:40 -0700201 measured.setPara(source, paraStart, paraEnd, DIR_REQUEST_DEFAULT_LTR);
202 char[] chs = measured.mChars;
203 float[] widths = measured.mWidths;
204 byte[] chdirs = measured.mLevels;
205 int dir = measured.mDir;
206 boolean easy = measured.mEasy;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800207
Doug Felte8e45f22010-03-29 14:58:40 -0700208 CharSequence sub = source;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800209
210 int width = firstwidth;
211
212 float w = 0;
Doug Felte8e45f22010-03-29 14:58:40 -0700213 int here = paraStart;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800214
Doug Felte8e45f22010-03-29 14:58:40 -0700215 int ok = paraStart;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800216 float okwidth = w;
217 int okascent = 0, okdescent = 0, oktop = 0, okbottom = 0;
218
Doug Felte8e45f22010-03-29 14:58:40 -0700219 int fit = paraStart;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800220 float fitwidth = w;
221 int fitascent = 0, fitdescent = 0, fittop = 0, fitbottom = 0;
222
Doug Feltc982f602010-05-25 11:51:40 -0700223 boolean hasTabOrEmoji = false;
224 boolean hasTab = false;
225 TabStops tabStops = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800226
Doug Felt23241882010-06-02 14:41:06 -0700227 for (int spanStart = paraStart, spanEnd = spanStart, nextSpanStart;
228 spanStart < paraEnd; spanStart = nextSpanStart) {
Doug Felte8e45f22010-03-29 14:58:40 -0700229
Doug Felt23241882010-06-02 14:41:06 -0700230 if (spanStart == spanEnd) {
231 if (spanned == null)
232 spanEnd = paraEnd;
233 else
234 spanEnd = spanned.nextSpanTransition(spanStart, paraEnd,
235 MetricAffectingSpan.class);
236
237 int spanLen = spanEnd - spanStart;
238 if (spanned == null) {
239 measured.addStyleRun(paint, spanLen, fm);
240 } else {
241 MetricAffectingSpan[] spans =
242 spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class);
243 measured.addStyleRun(paint, spans, spanLen, fm);
244 }
245 }
246
247 nextSpanStart = spanEnd;
Doug Felte8e45f22010-03-29 14:58:40 -0700248 int startInPara = spanStart - paraStart;
249 int endInPara = spanEnd - paraStart;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800250
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800251 int fmtop = fm.top;
252 int fmbottom = fm.bottom;
253 int fmascent = fm.ascent;
254 int fmdescent = fm.descent;
255
Doug Felte8e45f22010-03-29 14:58:40 -0700256 for (int j = spanStart; j < spanEnd; j++) {
257 char c = chs[j - paraStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800258 float before = w;
259
The Android Open Source Project10592532009-03-18 17:39:46 -0700260 if (c == '\n') {
Gilles Debunne66111472010-11-19 11:04:37 -0800261 // intentionally left empty
The Android Open Source Project10592532009-03-18 17:39:46 -0700262 } else if (c == '\t') {
Doug Feltc982f602010-05-25 11:51:40 -0700263 if (hasTab == false) {
264 hasTab = true;
265 hasTabOrEmoji = true;
Kenny Root24ca4542010-06-22 23:46:35 -0700266 if (spanned != null) {
267 // First tab this para, check for tabstops
Eric Fischer74d31ef2010-08-05 15:29:36 -0700268 TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
Kenny Root24ca4542010-06-22 23:46:35 -0700269 paraEnd, TabStopSpan.class);
270 if (spans.length > 0) {
271 tabStops = new TabStops(TAB_INCREMENT, spans);
272 }
Doug Feltc982f602010-05-25 11:51:40 -0700273 }
274 }
275 if (tabStops != null) {
276 w = tabStops.nextTab(w);
277 } else {
278 w = TabStops.nextDefaultStop(w, TAB_INCREMENT);
279 }
Doug Felte8e45f22010-03-29 14:58:40 -0700280 } else if (c >= 0xD800 && c <= 0xDFFF && j + 1 < spanEnd) {
281 int emoji = Character.codePointAt(chs, j - paraStart);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800282
The Android Open Source Project10592532009-03-18 17:39:46 -0700283 if (emoji >= MIN_EMOJI && emoji <= MAX_EMOJI) {
284 Bitmap bm = EMOJI_FACTORY.
285 getBitmapFromAndroidPua(emoji);
286
287 if (bm != null) {
Eric Fischer423f0e42009-03-27 18:04:12 -0700288 Paint whichPaint;
289
290 if (spanned == null) {
291 whichPaint = paint;
292 } else {
293 whichPaint = mWorkPaint;
294 }
295
Doug Felt4e0c5e52010-03-15 16:56:02 -0700296 float wid = bm.getWidth() *
Eric Fischer423f0e42009-03-27 18:04:12 -0700297 -whichPaint.ascent() /
298 bm.getHeight();
299
300 w += wid;
Doug Feltc982f602010-05-25 11:51:40 -0700301 hasTabOrEmoji = true;
The Android Open Source Project10592532009-03-18 17:39:46 -0700302 j++;
303 } else {
Doug Felte8e45f22010-03-29 14:58:40 -0700304 w += widths[j - paraStart];
The Android Open Source Project10592532009-03-18 17:39:46 -0700305 }
306 } else {
Doug Felte8e45f22010-03-29 14:58:40 -0700307 w += widths[j - paraStart];
The Android Open Source Project10592532009-03-18 17:39:46 -0700308 }
309 } else {
Doug Felte8e45f22010-03-29 14:58:40 -0700310 w += widths[j - paraStart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800311 }
312
313 // Log.e("text", "was " + before + " now " + w + " after " + c + " within " + width);
314
315 if (w <= width) {
316 fitwidth = w;
317 fit = j + 1;
318
319 if (fmtop < fittop)
320 fittop = fmtop;
321 if (fmascent < fitascent)
322 fitascent = fmascent;
323 if (fmdescent > fitdescent)
324 fitdescent = fmdescent;
325 if (fmbottom > fitbottom)
326 fitbottom = fmbottom;
327
328 /*
329 * From the Unicode Line Breaking Algorithm:
330 * (at least approximately)
Doug Felt4e0c5e52010-03-15 16:56:02 -0700331 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800332 * .,:; are class IS: breakpoints
333 * except when adjacent to digits
334 * / is class SY: a breakpoint
335 * except when followed by a digit.
336 * - is class HY: a breakpoint
337 * except when followed by a digit.
338 *
Eric Fischer549d7242009-03-31 14:19:47 -0700339 * Ideographs are class ID: breakpoints when adjacent,
340 * except for NS (non-starters), which can be broken
341 * after but not before.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800342 */
343
344 if (c == ' ' || c == '\t' ||
345 ((c == '.' || c == ',' || c == ':' || c == ';') &&
Doug Felte8e45f22010-03-29 14:58:40 -0700346 (j - 1 < here || !Character.isDigit(chs[j - 1 - paraStart])) &&
347 (j + 1 >= spanEnd || !Character.isDigit(chs[j + 1 - paraStart]))) ||
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800348 ((c == '/' || c == '-') &&
Doug Felte8e45f22010-03-29 14:58:40 -0700349 (j + 1 >= spanEnd || !Character.isDigit(chs[j + 1 - paraStart]))) ||
Eric Fischer549d7242009-03-31 14:19:47 -0700350 (c >= FIRST_CJK && isIdeographic(c, true) &&
Doug Felte8e45f22010-03-29 14:58:40 -0700351 j + 1 < spanEnd && isIdeographic(chs[j + 1 - paraStart], false))) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800352 okwidth = w;
353 ok = j + 1;
354
355 if (fittop < oktop)
356 oktop = fittop;
357 if (fitascent < okascent)
358 okascent = fitascent;
359 if (fitdescent > okdescent)
360 okdescent = fitdescent;
361 if (fitbottom > okbottom)
362 okbottom = fitbottom;
363 }
364 } else if (breakOnlyAtSpaces) {
365 if (ok != here) {
366 // Log.e("text", "output ok " + here + " to " +ok);
367
Doug Felte8e45f22010-03-29 14:58:40 -0700368 while (ok < spanEnd && chs[ok - paraStart] == ' ') {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800369 ok++;
370 }
371
372 v = out(source,
373 here, ok,
374 okascent, okdescent, oktop, okbottom,
375 v,
376 spacingmult, spacingadd, chooseht,
Doug Feltc982f602010-05-25 11:51:40 -0700377 choosehtv, fm, hasTabOrEmoji,
Doug Felte8e45f22010-03-29 14:58:40 -0700378 needMultiply, paraStart, chdirs, dir, easy,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800379 ok == bufend, includepad, trackpad,
Doug Felte8e45f22010-03-29 14:58:40 -0700380 chs, widths, here - paraStart,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800381 where, ellipsizedWidth, okwidth,
382 paint);
383
384 here = ok;
385 } else {
386 // Act like it fit even though it didn't.
387
388 fitwidth = w;
389 fit = j + 1;
390
391 if (fmtop < fittop)
392 fittop = fmtop;
393 if (fmascent < fitascent)
394 fitascent = fmascent;
395 if (fmdescent > fitdescent)
396 fitdescent = fmdescent;
397 if (fmbottom > fitbottom)
398 fitbottom = fmbottom;
399 }
400 } else {
401 if (ok != here) {
402 // Log.e("text", "output ok " + here + " to " +ok);
403
Doug Felte8e45f22010-03-29 14:58:40 -0700404 while (ok < spanEnd && chs[ok - paraStart] == ' ') {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800405 ok++;
406 }
407
408 v = out(source,
409 here, ok,
410 okascent, okdescent, oktop, okbottom,
411 v,
412 spacingmult, spacingadd, chooseht,
Doug Feltc982f602010-05-25 11:51:40 -0700413 choosehtv, fm, hasTabOrEmoji,
Doug Felte8e45f22010-03-29 14:58:40 -0700414 needMultiply, paraStart, chdirs, dir, easy,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800415 ok == bufend, includepad, trackpad,
Doug Felte8e45f22010-03-29 14:58:40 -0700416 chs, widths, here - paraStart,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800417 where, ellipsizedWidth, okwidth,
418 paint);
419
420 here = ok;
421 } else if (fit != here) {
422 // Log.e("text", "output fit " + here + " to " +fit);
423 v = out(source,
424 here, fit,
425 fitascent, fitdescent,
426 fittop, fitbottom,
427 v,
428 spacingmult, spacingadd, chooseht,
Doug Feltc982f602010-05-25 11:51:40 -0700429 choosehtv, fm, hasTabOrEmoji,
Doug Felte8e45f22010-03-29 14:58:40 -0700430 needMultiply, paraStart, chdirs, dir, easy,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800431 fit == bufend, includepad, trackpad,
Doug Felte8e45f22010-03-29 14:58:40 -0700432 chs, widths, here - paraStart,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800433 where, ellipsizedWidth, fitwidth,
434 paint);
435
436 here = fit;
437 } else {
438 // Log.e("text", "output one " + here + " to " +(here + 1));
Doug Felte8e45f22010-03-29 14:58:40 -0700439 // XXX not sure why the existing fm wasn't ok.
440 // measureText(paint, mWorkPaint,
441 // source, here, here + 1, fm, tab,
442 // null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800443
444 v = out(source,
445 here, here+1,
446 fm.ascent, fm.descent,
447 fm.top, fm.bottom,
448 v,
449 spacingmult, spacingadd, chooseht,
Doug Feltc982f602010-05-25 11:51:40 -0700450 choosehtv, fm, hasTabOrEmoji,
Doug Felte8e45f22010-03-29 14:58:40 -0700451 needMultiply, paraStart, chdirs, dir, easy,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800452 here + 1 == bufend, includepad,
453 trackpad,
Doug Felte8e45f22010-03-29 14:58:40 -0700454 chs, widths, here - paraStart,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800455 where, ellipsizedWidth,
Doug Felte8e45f22010-03-29 14:58:40 -0700456 widths[here - paraStart], paint);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800457
458 here = here + 1;
459 }
460
Doug Felte8e45f22010-03-29 14:58:40 -0700461 if (here < spanStart) {
Doug Felt23241882010-06-02 14:41:06 -0700462 // didn't output all the text for this span
463 // we've measured the raw widths, though, so
464 // just reset the start point
465 j = nextSpanStart = here;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800466 } else {
467 j = here - 1; // continue looping
468 }
469
470 ok = fit = here;
471 w = 0;
472 fitascent = fitdescent = fittop = fitbottom = 0;
473 okascent = okdescent = oktop = okbottom = 0;
474
Doug Feltc982f602010-05-25 11:51:40 -0700475 if (--firstWidthLineLimit <= 0) {
Mark Wagner7b5676e2009-10-16 11:44:23 -0700476 width = restwidth;
477 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800478 }
479 }
480 }
481
Doug Felte8e45f22010-03-29 14:58:40 -0700482 if (paraEnd != here) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800483 if ((fittop | fitbottom | fitdescent | fitascent) == 0) {
484 paint.getFontMetricsInt(fm);
485
486 fittop = fm.top;
487 fitbottom = fm.bottom;
488 fitascent = fm.ascent;
489 fitdescent = fm.descent;
490 }
491
492 // Log.e("text", "output rest " + here + " to " + end);
493
494 v = out(source,
Doug Felte8e45f22010-03-29 14:58:40 -0700495 here, paraEnd, fitascent, fitdescent,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800496 fittop, fitbottom,
497 v,
498 spacingmult, spacingadd, chooseht,
Doug Feltc982f602010-05-25 11:51:40 -0700499 choosehtv, fm, hasTabOrEmoji,
Doug Felte8e45f22010-03-29 14:58:40 -0700500 needMultiply, paraStart, chdirs, dir, easy,
501 paraEnd == bufend, includepad, trackpad,
502 chs, widths, here - paraStart,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800503 where, ellipsizedWidth, w, paint);
504 }
505
Doug Felte8e45f22010-03-29 14:58:40 -0700506 paraStart = paraEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800507
Doug Felte8e45f22010-03-29 14:58:40 -0700508 if (paraEnd == bufend)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800509 break;
510 }
511
512 if (bufend == bufstart || source.charAt(bufend - 1) == '\n') {
513 // Log.e("text", "output last " + bufend);
514
515 paint.getFontMetricsInt(fm);
516
517 v = out(source,
518 bufend, bufend, fm.ascent, fm.descent,
519 fm.top, fm.bottom,
520 v,
521 spacingmult, spacingadd, null,
522 null, fm, false,
Doug Felte8e45f22010-03-29 14:58:40 -0700523 needMultiply, bufend, null, DEFAULT_DIR, true,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800524 true, includepad, trackpad,
Doug Felte8e45f22010-03-29 14:58:40 -0700525 null, null, bufstart,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800526 where, ellipsizedWidth, 0, paint);
527 }
528 }
529
530 private static final char FIRST_CJK = '\u2E80';
531 /**
532 * Returns true if the specified character is one of those specified
533 * as being Ideographic (class ID) by the Unicode Line Breaking Algorithm
534 * (http://www.unicode.org/unicode/reports/tr14/), and is therefore OK
535 * to break between a pair of.
Eric Fischer549d7242009-03-31 14:19:47 -0700536 *
537 * @param includeNonStarters also return true for category NS
538 * (non-starters), which can be broken
539 * after but not before.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800540 */
Eric Fischer549d7242009-03-31 14:19:47 -0700541 private static final boolean isIdeographic(char c, boolean includeNonStarters) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800542 if (c >= '\u2E80' && c <= '\u2FFF') {
543 return true; // CJK, KANGXI RADICALS, DESCRIPTION SYMBOLS
544 }
545 if (c == '\u3000') {
546 return true; // IDEOGRAPHIC SPACE
547 }
548 if (c >= '\u3040' && c <= '\u309F') {
Eric Fischer549d7242009-03-31 14:19:47 -0700549 if (!includeNonStarters) {
550 switch (c) {
551 case '\u3041': // # HIRAGANA LETTER SMALL A
552 case '\u3043': // # HIRAGANA LETTER SMALL I
553 case '\u3045': // # HIRAGANA LETTER SMALL U
554 case '\u3047': // # HIRAGANA LETTER SMALL E
555 case '\u3049': // # HIRAGANA LETTER SMALL O
556 case '\u3063': // # HIRAGANA LETTER SMALL TU
557 case '\u3083': // # HIRAGANA LETTER SMALL YA
558 case '\u3085': // # HIRAGANA LETTER SMALL YU
559 case '\u3087': // # HIRAGANA LETTER SMALL YO
560 case '\u308E': // # HIRAGANA LETTER SMALL WA
561 case '\u3095': // # HIRAGANA LETTER SMALL KA
562 case '\u3096': // # HIRAGANA LETTER SMALL KE
563 case '\u309B': // # KATAKANA-HIRAGANA VOICED SOUND MARK
564 case '\u309C': // # KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK
565 case '\u309D': // # HIRAGANA ITERATION MARK
566 case '\u309E': // # HIRAGANA VOICED ITERATION MARK
567 return false;
568 }
569 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800570 return true; // Hiragana (except small characters)
571 }
572 if (c >= '\u30A0' && c <= '\u30FF') {
Eric Fischer549d7242009-03-31 14:19:47 -0700573 if (!includeNonStarters) {
574 switch (c) {
575 case '\u30A0': // # KATAKANA-HIRAGANA DOUBLE HYPHEN
576 case '\u30A1': // # KATAKANA LETTER SMALL A
577 case '\u30A3': // # KATAKANA LETTER SMALL I
578 case '\u30A5': // # KATAKANA LETTER SMALL U
579 case '\u30A7': // # KATAKANA LETTER SMALL E
580 case '\u30A9': // # KATAKANA LETTER SMALL O
581 case '\u30C3': // # KATAKANA LETTER SMALL TU
582 case '\u30E3': // # KATAKANA LETTER SMALL YA
583 case '\u30E5': // # KATAKANA LETTER SMALL YU
584 case '\u30E7': // # KATAKANA LETTER SMALL YO
585 case '\u30EE': // # KATAKANA LETTER SMALL WA
586 case '\u30F5': // # KATAKANA LETTER SMALL KA
587 case '\u30F6': // # KATAKANA LETTER SMALL KE
588 case '\u30FB': // # KATAKANA MIDDLE DOT
589 case '\u30FC': // # KATAKANA-HIRAGANA PROLONGED SOUND MARK
590 case '\u30FD': // # KATAKANA ITERATION MARK
591 case '\u30FE': // # KATAKANA VOICED ITERATION MARK
592 return false;
593 }
594 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800595 return true; // Katakana (except small characters)
596 }
597 if (c >= '\u3400' && c <= '\u4DB5') {
598 return true; // CJK UNIFIED IDEOGRAPHS EXTENSION A
599 }
600 if (c >= '\u4E00' && c <= '\u9FBB') {
601 return true; // CJK UNIFIED IDEOGRAPHS
602 }
603 if (c >= '\uF900' && c <= '\uFAD9') {
604 return true; // CJK COMPATIBILITY IDEOGRAPHS
605 }
606 if (c >= '\uA000' && c <= '\uA48F') {
607 return true; // YI SYLLABLES
608 }
609 if (c >= '\uA490' && c <= '\uA4CF') {
610 return true; // YI RADICALS
611 }
612 if (c >= '\uFE62' && c <= '\uFE66') {
613 return true; // SMALL PLUS SIGN to SMALL EQUALS SIGN
614 }
615 if (c >= '\uFF10' && c <= '\uFF19') {
616 return true; // WIDE DIGITS
617 }
618
619 return false;
620 }
621
622/*
623 private static void dump(byte[] data, int count, String label) {
624 if (false) {
625 System.out.print(label);
626
627 for (int i = 0; i < count; i++)
628 System.out.print(" " + data[i]);
629
630 System.out.println();
631 }
632 }
633*/
634
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800635 private int out(CharSequence text, int start, int end,
636 int above, int below, int top, int bottom, int v,
637 float spacingmult, float spacingadd,
638 LineHeightSpan[] chooseht, int[] choosehtv,
Doug Feltc982f602010-05-25 11:51:40 -0700639 Paint.FontMetricsInt fm, boolean hasTabOrEmoji,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800640 boolean needMultiply, int pstart, byte[] chdirs,
641 int dir, boolean easy, boolean last,
642 boolean includepad, boolean trackpad,
Doug Felte8e45f22010-03-29 14:58:40 -0700643 char[] chs, float[] widths, int widstart,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800644 TextUtils.TruncateAt ellipsize, float ellipsiswidth,
645 float textwidth, TextPaint paint) {
646 int j = mLineCount;
647 int off = j * mColumns;
648 int want = off + mColumns + TOP;
649 int[] lines = mLines;
650
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800651 if (want >= lines.length) {
652 int nlen = ArrayUtils.idealIntArraySize(want + 1);
653 int[] grow = new int[nlen];
654 System.arraycopy(lines, 0, grow, 0, lines.length);
655 mLines = grow;
656 lines = grow;
657
658 Directions[] grow2 = new Directions[nlen];
659 System.arraycopy(mLineDirections, 0, grow2, 0,
660 mLineDirections.length);
661 mLineDirections = grow2;
662 }
663
664 if (chooseht != null) {
665 fm.ascent = above;
666 fm.descent = below;
667 fm.top = top;
668 fm.bottom = bottom;
669
670 for (int i = 0; i < chooseht.length; i++) {
Eric Fischera9f1dd02009-08-12 15:00:10 -0700671 if (chooseht[i] instanceof LineHeightSpan.WithDensity) {
672 ((LineHeightSpan.WithDensity) chooseht[i]).
673 chooseHeight(text, start, end, choosehtv[i], v, fm, paint);
674
675 } else {
676 chooseht[i].chooseHeight(text, start, end, choosehtv[i], v, fm);
677 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800678 }
679
680 above = fm.ascent;
681 below = fm.descent;
682 top = fm.top;
683 bottom = fm.bottom;
684 }
685
686 if (j == 0) {
687 if (trackpad) {
688 mTopPadding = top - above;
689 }
690
691 if (includepad) {
692 above = top;
693 }
694 }
695 if (last) {
696 if (trackpad) {
697 mBottomPadding = bottom - below;
698 }
699
700 if (includepad) {
701 below = bottom;
702 }
703 }
704
705 int extra;
706
707 if (needMultiply) {
Doug Felt10657582010-02-22 11:19:01 -0800708 double ex = (below - above) * (spacingmult - 1) + spacingadd;
709 if (ex >= 0) {
710 extra = (int)(ex + 0.5);
711 } else {
712 extra = -(int)(-ex + 0.5);
713 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800714 } else {
715 extra = 0;
716 }
717
718 lines[off + START] = start;
719 lines[off + TOP] = v;
720 lines[off + DESCENT] = below + extra;
721
722 v += (below - above) + extra;
723 lines[off + mColumns + START] = end;
724 lines[off + mColumns + TOP] = v;
725
Doug Feltc982f602010-05-25 11:51:40 -0700726 if (hasTabOrEmoji)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800727 lines[off + TAB] |= TAB_MASK;
728
Doug Felt9f7a4442010-03-01 12:45:56 -0800729 lines[off + DIR] |= dir << DIR_SHIFT;
730 Directions linedirs = DIRS_ALL_LEFT_TO_RIGHT;
731 // easy means all chars < the first RTL, so no emoji, no nothing
Doug Felt4e0c5e52010-03-15 16:56:02 -0700732 // XXX a run with no text or all spaces is easy but might be an empty
Doug Felt9f7a4442010-03-01 12:45:56 -0800733 // RTL paragraph. Make sure easy is false if this is the case.
734 if (easy) {
735 mLineDirections[j] = linedirs;
736 } else {
Doug Felte8e45f22010-03-29 14:58:40 -0700737 mLineDirections[j] = AndroidBidi.directions(dir, chdirs, widstart, chs,
738 widstart, end - start);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800739
740 // If ellipsize is in marquee mode, do not apply ellipsis on the first line
741 if (ellipsize != null && (ellipsize != TextUtils.TruncateAt.MARQUEE || j != 0)) {
Doug Felte8e45f22010-03-29 14:58:40 -0700742 calculateEllipsis(start, end, widths, widstart,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800743 ellipsiswidth, ellipsize, j,
744 textwidth, paint);
745 }
746 }
747
748 mLineCount++;
749 return v;
750 }
751
752 private void calculateEllipsis(int linestart, int lineend,
Doug Felte8e45f22010-03-29 14:58:40 -0700753 float[] widths, int widstart,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800754 float avail, TextUtils.TruncateAt where,
755 int line, float textwidth, TextPaint paint) {
756 int len = lineend - linestart;
757
758 if (textwidth <= avail) {
759 // Everything fits!
760 mLines[mColumns * line + ELLIPSIS_START] = 0;
761 mLines[mColumns * line + ELLIPSIS_COUNT] = 0;
762 return;
763 }
764
765 float ellipsiswid = paint.measureText("\u2026");
766 int ellipsisStart, ellipsisCount;
767
768 if (where == TextUtils.TruncateAt.START) {
769 float sum = 0;
770 int i;
771
772 for (i = len; i >= 0; i--) {
Doug Felte8e45f22010-03-29 14:58:40 -0700773 float w = widths[i - 1 + linestart - widstart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800774
775 if (w + sum + ellipsiswid > avail) {
776 break;
777 }
778
779 sum += w;
780 }
781
782 ellipsisStart = 0;
783 ellipsisCount = i;
784 } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE) {
785 float sum = 0;
786 int i;
787
788 for (i = 0; i < len; i++) {
Doug Felte8e45f22010-03-29 14:58:40 -0700789 float w = widths[i + linestart - widstart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800790
791 if (w + sum + ellipsiswid > avail) {
792 break;
793 }
794
795 sum += w;
796 }
797
798 ellipsisStart = i;
799 ellipsisCount = len - i;
800 } else /* where = TextUtils.TruncateAt.MIDDLE */ {
801 float lsum = 0, rsum = 0;
802 int left = 0, right = len;
803
804 float ravail = (avail - ellipsiswid) / 2;
805 for (right = len; right >= 0; right--) {
Doug Felte8e45f22010-03-29 14:58:40 -0700806 float w = widths[right - 1 + linestart - widstart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800807
808 if (w + rsum > ravail) {
809 break;
810 }
811
812 rsum += w;
813 }
814
815 float lavail = avail - ellipsiswid - rsum;
816 for (left = 0; left < right; left++) {
Doug Felte8e45f22010-03-29 14:58:40 -0700817 float w = widths[left + linestart - widstart];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800818
819 if (w + lsum > lavail) {
820 break;
821 }
822
823 lsum += w;
824 }
825
826 ellipsisStart = left;
827 ellipsisCount = right - left;
828 }
829
830 mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
831 mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
832 }
833
Doug Felte8e45f22010-03-29 14:58:40 -0700834 // Override the base class so we can directly access our members,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800835 // rather than relying on member functions.
836 // The logic mirrors that of Layout.getLineForVertical
837 // FIXME: It may be faster to do a linear search for layouts without many lines.
Gilles Debunne66111472010-11-19 11:04:37 -0800838 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800839 public int getLineForVertical(int vertical) {
840 int high = mLineCount;
841 int low = -1;
842 int guess;
843 int[] lines = mLines;
844 while (high - low > 1) {
845 guess = (high + low) >> 1;
846 if (lines[mColumns * guess + TOP] > vertical){
847 high = guess;
848 } else {
849 low = guess;
850 }
851 }
852 if (low < 0) {
853 return 0;
854 } else {
855 return low;
856 }
857 }
858
Gilles Debunne66111472010-11-19 11:04:37 -0800859 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800860 public int getLineCount() {
861 return mLineCount;
862 }
863
Gilles Debunne66111472010-11-19 11:04:37 -0800864 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800865 public int getLineTop(int line) {
Doug Felt4e0c5e52010-03-15 16:56:02 -0700866 return mLines[mColumns * line + TOP];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800867 }
868
Gilles Debunne66111472010-11-19 11:04:37 -0800869 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800870 public int getLineDescent(int line) {
Doug Felt4e0c5e52010-03-15 16:56:02 -0700871 return mLines[mColumns * line + DESCENT];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800872 }
873
Gilles Debunne66111472010-11-19 11:04:37 -0800874 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800875 public int getLineStart(int line) {
876 return mLines[mColumns * line + START] & START_MASK;
877 }
878
Gilles Debunne66111472010-11-19 11:04:37 -0800879 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800880 public int getParagraphDirection(int line) {
881 return mLines[mColumns * line + DIR] >> DIR_SHIFT;
882 }
883
Gilles Debunne66111472010-11-19 11:04:37 -0800884 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800885 public boolean getLineContainsTab(int line) {
886 return (mLines[mColumns * line + TAB] & TAB_MASK) != 0;
887 }
888
Gilles Debunne66111472010-11-19 11:04:37 -0800889 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800890 public final Directions getLineDirections(int line) {
891 return mLineDirections[line];
892 }
893
Gilles Debunne66111472010-11-19 11:04:37 -0800894 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800895 public int getTopPadding() {
896 return mTopPadding;
897 }
898
Gilles Debunne66111472010-11-19 11:04:37 -0800899 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800900 public int getBottomPadding() {
901 return mBottomPadding;
902 }
903
904 @Override
905 public int getEllipsisCount(int line) {
906 if (mColumns < COLUMNS_ELLIPSIZE) {
907 return 0;
908 }
909
910 return mLines[mColumns * line + ELLIPSIS_COUNT];
911 }
912
913 @Override
914 public int getEllipsisStart(int line) {
915 if (mColumns < COLUMNS_ELLIPSIZE) {
916 return 0;
917 }
918
919 return mLines[mColumns * line + ELLIPSIS_START];
920 }
921
922 @Override
923 public int getEllipsizedWidth() {
924 return mEllipsizedWidth;
925 }
926
927 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;
944
945 private static final int START_MASK = 0x1FFFFFFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800946 private static final int DIR_SHIFT = 30;
947 private static final int TAB_MASK = 0x20000000;
948
Doug Feltc982f602010-05-25 11:51:40 -0700949 private static final int TAB_INCREMENT = 20; // same as Layout, but that's private
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800950
951 /*
Doug Felte8e45f22010-03-29 14:58:40 -0700952 * This is reused across calls to generate()
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800953 */
Doug Felte8e45f22010-03-29 14:58:40 -0700954 private MeasuredText mMeasured;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800955 private Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
956}