blob: 6de9c65d68869b7ab5527cb3f1921622b2d41add [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;
21import com.android.internal.util.ArrayUtils;
22import android.util.Log;
23import android.text.style.LeadingMarginSpan;
24import android.text.style.LineHeightSpan;
25import android.text.style.MetricAffectingSpan;
26import android.text.style.ReplacementSpan;
27
28/**
29 * StaticLayout is a Layout for text that will not be edited after it
30 * is laid out. Use {@link DynamicLayout} for text that may change.
31 * <p>This is used by widgets to control text layout. You should not need
32 * to use this class directly unless you are implementing your own widget
33 * or custom display object, or would be tempted to call
34 * {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int, float, float, android.graphics.Paint)
35 * Canvas.drawText()} directly.</p>
36 */
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)
65 ? source
66 : (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.
75 *
76 * 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
97 generate(source, bufstart, bufend, paint, outerwidth, align,
98 spacingmult, spacingadd, includepad, includepad,
99 ellipsize != null, ellipsizedWidth, ellipsize);
100
101 mChdirs = null;
102 mChs = null;
103 mWidths = null;
104 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)];
114 }
115
116 /* package */ void generate(CharSequence source, int bufstart, int bufend,
117 TextPaint paint, int outerwidth,
118 Alignment align,
119 float spacingmult, float spacingadd,
120 boolean includepad, boolean trackpad,
121 boolean breakOnlyAtSpaces,
122 float ellipsizedWidth, TextUtils.TruncateAt where) {
123 mLineCount = 0;
124
125 int v = 0;
126 boolean needMultiply = (spacingmult != 1 || spacingadd != 0);
127
128 Paint.FontMetricsInt fm = mFontMetricsInt;
129 int[] choosehtv = null;
130
131 int end = TextUtils.indexOf(source, '\n', bufstart, bufend);
132 int bufsiz = end >= 0 ? end - bufstart : bufend - bufstart;
133 boolean first = true;
134
135 if (mChdirs == null) {
136 mChdirs = new byte[ArrayUtils.idealByteArraySize(bufsiz + 1)];
137 mChs = new char[ArrayUtils.idealCharArraySize(bufsiz + 1)];
138 mWidths = new float[ArrayUtils.idealIntArraySize((bufsiz + 1) * 2)];
139 }
140
141 byte[] chdirs = mChdirs;
142 char[] chs = mChs;
143 float[] widths = mWidths;
144
145 AlteredCharSequence alter = null;
146 Spanned spanned = null;
147
148 if (source instanceof Spanned)
149 spanned = (Spanned) source;
150
151 int DEFAULT_DIR = DIR_LEFT_TO_RIGHT; // XXX
152
153 for (int start = bufstart; start <= bufend; start = end) {
154 if (first)
155 first = false;
156 else
157 end = TextUtils.indexOf(source, '\n', start, bufend);
158
159 if (end < 0)
160 end = bufend;
161 else
162 end++;
163
Mark Wagner7b5676e2009-10-16 11:44:23 -0700164 int firstWidthLineCount = 1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800165 int firstwidth = outerwidth;
166 int restwidth = outerwidth;
167
168 LineHeightSpan[] chooseht = null;
169
170 if (spanned != null) {
171 LeadingMarginSpan[] sp;
172
173 sp = spanned.getSpans(start, end, LeadingMarginSpan.class);
174 for (int i = 0; i < sp.length; i++) {
Mark Wagner7b5676e2009-10-16 11:44:23 -0700175 LeadingMarginSpan lms = sp[i];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800176 firstwidth -= sp[i].getLeadingMargin(true);
177 restwidth -= sp[i].getLeadingMargin(false);
Mark Wagner7b5676e2009-10-16 11:44:23 -0700178 if (lms instanceof LeadingMarginSpan.LeadingMarginSpan2) {
179 firstWidthLineCount = ((LeadingMarginSpan.LeadingMarginSpan2)lms).getLeadingMarginLineCount();
180 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800181 }
182
183 chooseht = spanned.getSpans(start, end, LineHeightSpan.class);
184
185 if (chooseht.length != 0) {
186 if (choosehtv == null ||
187 choosehtv.length < chooseht.length) {
188 choosehtv = new int[ArrayUtils.idealIntArraySize(
189 chooseht.length)];
190 }
191
192 for (int i = 0; i < chooseht.length; i++) {
193 int o = spanned.getSpanStart(chooseht[i]);
194
195 if (o < start) {
196 // starts in this layout, before the
197 // current paragraph
198
199 choosehtv[i] = getLineTop(getLineForOffset(o));
200 } else {
201 // starts in this paragraph
202
203 choosehtv[i] = v;
204 }
205 }
206 }
207 }
208
209 if (end - start > chdirs.length) {
210 chdirs = new byte[ArrayUtils.idealByteArraySize(end - start)];
211 mChdirs = chdirs;
212 }
213 if (end - start > chs.length) {
214 chs = new char[ArrayUtils.idealCharArraySize(end - start)];
215 mChs = chs;
216 }
217 if ((end - start) * 2 > widths.length) {
218 widths = new float[ArrayUtils.idealIntArraySize((end - start) * 2)];
219 mWidths = widths;
220 }
221
222 TextUtils.getChars(source, start, end, chs, 0);
223 final int n = end - start;
224
225 boolean easy = true;
226 boolean altered = false;
227 int dir = DEFAULT_DIR; // XXX
228
229 for (int i = 0; i < n; i++) {
230 if (chs[i] >= FIRST_RIGHT_TO_LEFT) {
231 easy = false;
232 break;
233 }
234 }
235
236 if (!easy) {
237 AndroidCharacter.getDirectionalities(chs, chdirs, end - start);
238
239 /*
240 * Determine primary paragraph direction
241 */
242
243 for (int j = start; j < end; j++) {
244 int d = chdirs[j - start];
245
246 if (d == Character.DIRECTIONALITY_LEFT_TO_RIGHT) {
247 dir = DIR_LEFT_TO_RIGHT;
248 break;
249 }
250 if (d == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
251 dir = DIR_RIGHT_TO_LEFT;
252 break;
253 }
254 }
255
256 /*
257 * XXX Explicit overrides should go here
258 */
259
260 /*
261 * Weak type resolution
262 */
263
264 final byte SOR = dir == DIR_LEFT_TO_RIGHT ?
265 Character.DIRECTIONALITY_LEFT_TO_RIGHT :
266 Character.DIRECTIONALITY_RIGHT_TO_LEFT;
267
268 // dump(chdirs, n, "initial");
269
270 // W1 non spacing marks
271 for (int j = 0; j < n; j++) {
272 if (chdirs[j] == Character.NON_SPACING_MARK) {
273 if (j == 0)
274 chdirs[j] = SOR;
275 else
276 chdirs[j] = chdirs[j - 1];
277 }
278 }
279
280 // dump(chdirs, n, "W1");
281
282 // W2 european numbers
283 byte cur = SOR;
284 for (int j = 0; j < n; j++) {
285 byte d = chdirs[j];
286
287 if (d == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
288 d == Character.DIRECTIONALITY_RIGHT_TO_LEFT ||
289 d == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC)
290 cur = d;
291 else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER) {
292 if (cur ==
293 Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC)
294 chdirs[j] = Character.DIRECTIONALITY_ARABIC_NUMBER;
295 }
296 }
297
298 // dump(chdirs, n, "W2");
299
300 // W3 arabic letters
301 for (int j = 0; j < n; j++) {
302 if (chdirs[j] == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC)
303 chdirs[j] = Character.DIRECTIONALITY_RIGHT_TO_LEFT;
304 }
305
306 // dump(chdirs, n, "W3");
307
308 // W4 single separator between numbers
309 for (int j = 1; j < n - 1; j++) {
310 byte d = chdirs[j];
311 byte prev = chdirs[j - 1];
312 byte next = chdirs[j + 1];
313
314 if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_SEPARATOR) {
315 if (prev == Character.DIRECTIONALITY_EUROPEAN_NUMBER &&
316 next == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
317 chdirs[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
318 } else if (d == Character.DIRECTIONALITY_COMMON_NUMBER_SEPARATOR) {
319 if (prev == Character.DIRECTIONALITY_EUROPEAN_NUMBER &&
320 next == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
321 chdirs[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
322 if (prev == Character.DIRECTIONALITY_ARABIC_NUMBER &&
323 next == Character.DIRECTIONALITY_ARABIC_NUMBER)
324 chdirs[j] = Character.DIRECTIONALITY_ARABIC_NUMBER;
325 }
326 }
327
328 // dump(chdirs, n, "W4");
329
330 // W5 european number terminators
331 boolean adjacent = false;
332 for (int j = 0; j < n; j++) {
333 byte d = chdirs[j];
334
335 if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
336 adjacent = true;
337 else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR && adjacent)
338 chdirs[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
339 else
340 adjacent = false;
341 }
342
343 //dump(chdirs, n, "W5");
344
345 // W5 european number terminators part 2,
346 // W6 separators and terminators
347 adjacent = false;
348 for (int j = n - 1; j >= 0; j--) {
349 byte d = chdirs[j];
350
351 if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
352 adjacent = true;
353 else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR) {
354 if (adjacent)
355 chdirs[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
356 else
357 chdirs[j] = Character.DIRECTIONALITY_OTHER_NEUTRALS;
358 }
359 else {
360 adjacent = false;
361
362 if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_SEPARATOR ||
363 d == Character.DIRECTIONALITY_COMMON_NUMBER_SEPARATOR ||
364 d == Character.DIRECTIONALITY_PARAGRAPH_SEPARATOR ||
365 d == Character.DIRECTIONALITY_SEGMENT_SEPARATOR)
366 chdirs[j] = Character.DIRECTIONALITY_OTHER_NEUTRALS;
367 }
368 }
369
370 // dump(chdirs, n, "W6");
371
372 // W7 strong direction of european numbers
373 cur = SOR;
374 for (int j = 0; j < n; j++) {
375 byte d = chdirs[j];
376
377 if (d == SOR ||
378 d == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
379 d == Character.DIRECTIONALITY_RIGHT_TO_LEFT)
380 cur = d;
381
382 if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
383 chdirs[j] = cur;
384 }
385
386 // dump(chdirs, n, "W7");
387
388 // N1, N2 neutrals
389 cur = SOR;
390 for (int j = 0; j < n; j++) {
391 byte d = chdirs[j];
392
393 if (d == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
394 d == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
395 cur = d;
396 } else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER ||
397 d == Character.DIRECTIONALITY_ARABIC_NUMBER) {
398 cur = Character.DIRECTIONALITY_RIGHT_TO_LEFT;
399 } else {
400 byte dd = SOR;
401 int k;
402
403 for (k = j + 1; k < n; k++) {
404 dd = chdirs[k];
405
406 if (dd == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
407 dd == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
408 break;
409 }
410 if (dd == Character.DIRECTIONALITY_EUROPEAN_NUMBER ||
411 dd == Character.DIRECTIONALITY_ARABIC_NUMBER) {
412 dd = Character.DIRECTIONALITY_RIGHT_TO_LEFT;
413 break;
414 }
415 }
416
417 for (int y = j; y < k; y++) {
418 if (dd == cur)
419 chdirs[y] = cur;
420 else
421 chdirs[y] = SOR;
422 }
423
424 j = k - 1;
425 }
426 }
427
428 // dump(chdirs, n, "final");
429
The Android Open Source Project10592532009-03-18 17:39:46 -0700430 // extra: enforce that all tabs and surrogate characters go the
431 // primary direction
432 // TODO: actually do directions right for surrogates
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800433
434 for (int j = 0; j < n; j++) {
The Android Open Source Project10592532009-03-18 17:39:46 -0700435 char c = chs[j];
436
437 if (c == '\t' || (c >= 0xD800 && c <= 0xDFFF)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800438 chdirs[j] = SOR;
The Android Open Source Project10592532009-03-18 17:39:46 -0700439 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800440 }
441
442 // extra: enforce that object replacements go to the
443 // primary direction
444 // and that none of the underlying characters are treated
445 // as viable breakpoints
446
447 if (source instanceof Spanned) {
448 Spanned sp = (Spanned) source;
449 ReplacementSpan[] spans = sp.getSpans(start, end, ReplacementSpan.class);
450
451 for (int y = 0; y < spans.length; y++) {
452 int a = sp.getSpanStart(spans[y]);
453 int b = sp.getSpanEnd(spans[y]);
454
455 for (int x = a; x < b; x++) {
456 chdirs[x - start] = SOR;
457 chs[x - start] = '\uFFFC';
458 }
459 }
460 }
461
462 // Do mirroring for right-to-left segments
463
464 for (int i = 0; i < n; i++) {
465 if (chdirs[i] == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
466 int j;
467
468 for (j = i; j < n; j++) {
469 if (chdirs[j] !=
470 Character.DIRECTIONALITY_RIGHT_TO_LEFT)
471 break;
472 }
473
474 if (AndroidCharacter.mirror(chs, i, j - i))
475 altered = true;
476
477 i = j - 1;
478 }
479 }
480 }
481
482 CharSequence sub;
483
484 if (altered) {
485 if (alter == null)
486 alter = AlteredCharSequence.make(source, chs, start, end);
487 else
488 alter.update(chs, start, end);
489
490 sub = alter;
491 } else {
492 sub = source;
493 }
494
495 int width = firstwidth;
496
497 float w = 0;
498 int here = start;
499
500 int ok = start;
501 float okwidth = w;
502 int okascent = 0, okdescent = 0, oktop = 0, okbottom = 0;
503
504 int fit = start;
505 float fitwidth = w;
506 int fitascent = 0, fitdescent = 0, fittop = 0, fitbottom = 0;
507
508 boolean tab = false;
509
510 int next;
511 for (int i = start; i < end; i = next) {
512 if (spanned == null)
513 next = end;
514 else
515 next = spanned.nextSpanTransition(i, end,
516 MetricAffectingSpan.
517 class);
518
519 if (spanned == null) {
520 paint.getTextWidths(sub, i, next, widths);
521 System.arraycopy(widths, 0, widths,
522 end - start + (i - start), next - i);
523
524 paint.getFontMetricsInt(fm);
525 } else {
526 mWorkPaint.baselineShift = 0;
527
528 Styled.getTextWidths(paint, mWorkPaint,
529 spanned, i, next,
530 widths, fm);
531 System.arraycopy(widths, 0, widths,
532 end - start + (i - start), next - i);
533
534 if (mWorkPaint.baselineShift < 0) {
535 fm.ascent += mWorkPaint.baselineShift;
536 fm.top += mWorkPaint.baselineShift;
537 } else {
538 fm.descent += mWorkPaint.baselineShift;
539 fm.bottom += mWorkPaint.baselineShift;
540 }
541 }
542
543 int fmtop = fm.top;
544 int fmbottom = fm.bottom;
545 int fmascent = fm.ascent;
546 int fmdescent = fm.descent;
547
548 if (false) {
549 StringBuilder sb = new StringBuilder();
550 for (int j = i; j < next; j++) {
551 sb.append(widths[j - start + (end - start)]);
552 sb.append(' ');
553 }
554
555 Log.e("text", sb.toString());
556 }
557
558 for (int j = i; j < next; j++) {
559 char c = chs[j - start];
560 float before = w;
561
The Android Open Source Project10592532009-03-18 17:39:46 -0700562 if (c == '\n') {
563 ;
564 } else if (c == '\t') {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800565 w = Layout.nextTab(sub, start, end, w, null);
566 tab = true;
The Android Open Source Project10592532009-03-18 17:39:46 -0700567 } else if (c >= 0xD800 && c <= 0xDFFF && j + 1 < next) {
568 int emoji = Character.codePointAt(chs, j - start);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800569
The Android Open Source Project10592532009-03-18 17:39:46 -0700570 if (emoji >= MIN_EMOJI && emoji <= MAX_EMOJI) {
571 Bitmap bm = EMOJI_FACTORY.
572 getBitmapFromAndroidPua(emoji);
573
574 if (bm != null) {
Eric Fischer423f0e42009-03-27 18:04:12 -0700575 Paint whichPaint;
576
577 if (spanned == null) {
578 whichPaint = paint;
579 } else {
580 whichPaint = mWorkPaint;
581 }
582
583 float wid = (float) bm.getWidth() *
584 -whichPaint.ascent() /
585 bm.getHeight();
586
587 w += wid;
The Android Open Source Project10592532009-03-18 17:39:46 -0700588 tab = true;
589 j++;
590 } else {
591 w += widths[j - start + (end - start)];
592 }
593 } else {
594 w += widths[j - start + (end - start)];
595 }
596 } else {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800597 w += widths[j - start + (end - start)];
598 }
599
600 // Log.e("text", "was " + before + " now " + w + " after " + c + " within " + width);
601
602 if (w <= width) {
603 fitwidth = w;
604 fit = j + 1;
605
606 if (fmtop < fittop)
607 fittop = fmtop;
608 if (fmascent < fitascent)
609 fitascent = fmascent;
610 if (fmdescent > fitdescent)
611 fitdescent = fmdescent;
612 if (fmbottom > fitbottom)
613 fitbottom = fmbottom;
614
615 /*
616 * From the Unicode Line Breaking Algorithm:
617 * (at least approximately)
618 *
619 * .,:; are class IS: breakpoints
620 * except when adjacent to digits
621 * / is class SY: a breakpoint
622 * except when followed by a digit.
623 * - is class HY: a breakpoint
624 * except when followed by a digit.
625 *
Eric Fischer549d7242009-03-31 14:19:47 -0700626 * Ideographs are class ID: breakpoints when adjacent,
627 * except for NS (non-starters), which can be broken
628 * after but not before.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800629 */
630
631 if (c == ' ' || c == '\t' ||
632 ((c == '.' || c == ',' || c == ':' || c == ';') &&
633 (j - 1 < here || !Character.isDigit(chs[j - 1 - start])) &&
634 (j + 1 >= next || !Character.isDigit(chs[j + 1 - start]))) ||
635 ((c == '/' || c == '-') &&
636 (j + 1 >= next || !Character.isDigit(chs[j + 1 - start]))) ||
Eric Fischer549d7242009-03-31 14:19:47 -0700637 (c >= FIRST_CJK && isIdeographic(c, true) &&
638 j + 1 < next && isIdeographic(chs[j + 1 - start], false))) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800639 okwidth = w;
640 ok = j + 1;
641
642 if (fittop < oktop)
643 oktop = fittop;
644 if (fitascent < okascent)
645 okascent = fitascent;
646 if (fitdescent > okdescent)
647 okdescent = fitdescent;
648 if (fitbottom > okbottom)
649 okbottom = fitbottom;
650 }
651 } else if (breakOnlyAtSpaces) {
652 if (ok != here) {
653 // Log.e("text", "output ok " + here + " to " +ok);
654
655 while (ok < next && chs[ok - start] == ' ') {
656 ok++;
657 }
658
659 v = out(source,
660 here, ok,
661 okascent, okdescent, oktop, okbottom,
662 v,
663 spacingmult, spacingadd, chooseht,
664 choosehtv, fm, tab,
665 needMultiply, start, chdirs, dir, easy,
666 ok == bufend, includepad, trackpad,
667 widths, start, end - start,
668 where, ellipsizedWidth, okwidth,
669 paint);
670
671 here = ok;
672 } else {
673 // Act like it fit even though it didn't.
674
675 fitwidth = w;
676 fit = j + 1;
677
678 if (fmtop < fittop)
679 fittop = fmtop;
680 if (fmascent < fitascent)
681 fitascent = fmascent;
682 if (fmdescent > fitdescent)
683 fitdescent = fmdescent;
684 if (fmbottom > fitbottom)
685 fitbottom = fmbottom;
686 }
687 } else {
688 if (ok != here) {
689 // Log.e("text", "output ok " + here + " to " +ok);
690
691 while (ok < next && chs[ok - start] == ' ') {
692 ok++;
693 }
694
695 v = out(source,
696 here, ok,
697 okascent, okdescent, oktop, okbottom,
698 v,
699 spacingmult, spacingadd, chooseht,
700 choosehtv, fm, tab,
701 needMultiply, start, chdirs, dir, easy,
702 ok == bufend, includepad, trackpad,
703 widths, start, end - start,
704 where, ellipsizedWidth, okwidth,
705 paint);
706
707 here = ok;
708 } else if (fit != here) {
709 // Log.e("text", "output fit " + here + " to " +fit);
710 v = out(source,
711 here, fit,
712 fitascent, fitdescent,
713 fittop, fitbottom,
714 v,
715 spacingmult, spacingadd, chooseht,
716 choosehtv, fm, tab,
717 needMultiply, start, chdirs, dir, easy,
718 fit == bufend, includepad, trackpad,
719 widths, start, end - start,
720 where, ellipsizedWidth, fitwidth,
721 paint);
722
723 here = fit;
724 } else {
725 // Log.e("text", "output one " + here + " to " +(here + 1));
726 measureText(paint, mWorkPaint,
727 source, here, here + 1, fm, tab,
728 null);
729
730 v = out(source,
731 here, here+1,
732 fm.ascent, fm.descent,
733 fm.top, fm.bottom,
734 v,
735 spacingmult, spacingadd, chooseht,
736 choosehtv, fm, tab,
737 needMultiply, start, chdirs, dir, easy,
738 here + 1 == bufend, includepad,
739 trackpad,
740 widths, start, end - start,
741 where, ellipsizedWidth,
742 widths[here - start], paint);
743
744 here = here + 1;
745 }
746
747 if (here < i) {
748 j = next = here; // must remeasure
749 } else {
750 j = here - 1; // continue looping
751 }
752
753 ok = fit = here;
754 w = 0;
755 fitascent = fitdescent = fittop = fitbottom = 0;
756 okascent = okdescent = oktop = okbottom = 0;
757
Mark Wagner7b5676e2009-10-16 11:44:23 -0700758 if (--firstWidthLineCount <= 0) {
759 width = restwidth;
760 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800761 }
762 }
763 }
764
765 if (end != here) {
766 if ((fittop | fitbottom | fitdescent | fitascent) == 0) {
767 paint.getFontMetricsInt(fm);
768
769 fittop = fm.top;
770 fitbottom = fm.bottom;
771 fitascent = fm.ascent;
772 fitdescent = fm.descent;
773 }
774
775 // Log.e("text", "output rest " + here + " to " + end);
776
777 v = out(source,
778 here, end, fitascent, fitdescent,
779 fittop, fitbottom,
780 v,
781 spacingmult, spacingadd, chooseht,
782 choosehtv, fm, tab,
783 needMultiply, start, chdirs, dir, easy,
784 end == bufend, includepad, trackpad,
785 widths, start, end - start,
786 where, ellipsizedWidth, w, paint);
787 }
788
789 start = end;
790
791 if (end == bufend)
792 break;
793 }
794
795 if (bufend == bufstart || source.charAt(bufend - 1) == '\n') {
796 // Log.e("text", "output last " + bufend);
797
798 paint.getFontMetricsInt(fm);
799
800 v = out(source,
801 bufend, bufend, fm.ascent, fm.descent,
802 fm.top, fm.bottom,
803 v,
804 spacingmult, spacingadd, null,
805 null, fm, false,
806 needMultiply, bufend, chdirs, DEFAULT_DIR, true,
807 true, includepad, trackpad,
808 widths, bufstart, 0,
809 where, ellipsizedWidth, 0, paint);
810 }
811 }
812
813 private static final char FIRST_CJK = '\u2E80';
814 /**
815 * Returns true if the specified character is one of those specified
816 * as being Ideographic (class ID) by the Unicode Line Breaking Algorithm
817 * (http://www.unicode.org/unicode/reports/tr14/), and is therefore OK
818 * to break between a pair of.
Eric Fischer549d7242009-03-31 14:19:47 -0700819 *
820 * @param includeNonStarters also return true for category NS
821 * (non-starters), which can be broken
822 * after but not before.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800823 */
Eric Fischer549d7242009-03-31 14:19:47 -0700824 private static final boolean isIdeographic(char c, boolean includeNonStarters) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800825 if (c >= '\u2E80' && c <= '\u2FFF') {
826 return true; // CJK, KANGXI RADICALS, DESCRIPTION SYMBOLS
827 }
828 if (c == '\u3000') {
829 return true; // IDEOGRAPHIC SPACE
830 }
831 if (c >= '\u3040' && c <= '\u309F') {
Eric Fischer549d7242009-03-31 14:19:47 -0700832 if (!includeNonStarters) {
833 switch (c) {
834 case '\u3041': // # HIRAGANA LETTER SMALL A
835 case '\u3043': // # HIRAGANA LETTER SMALL I
836 case '\u3045': // # HIRAGANA LETTER SMALL U
837 case '\u3047': // # HIRAGANA LETTER SMALL E
838 case '\u3049': // # HIRAGANA LETTER SMALL O
839 case '\u3063': // # HIRAGANA LETTER SMALL TU
840 case '\u3083': // # HIRAGANA LETTER SMALL YA
841 case '\u3085': // # HIRAGANA LETTER SMALL YU
842 case '\u3087': // # HIRAGANA LETTER SMALL YO
843 case '\u308E': // # HIRAGANA LETTER SMALL WA
844 case '\u3095': // # HIRAGANA LETTER SMALL KA
845 case '\u3096': // # HIRAGANA LETTER SMALL KE
846 case '\u309B': // # KATAKANA-HIRAGANA VOICED SOUND MARK
847 case '\u309C': // # KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK
848 case '\u309D': // # HIRAGANA ITERATION MARK
849 case '\u309E': // # HIRAGANA VOICED ITERATION MARK
850 return false;
851 }
852 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800853 return true; // Hiragana (except small characters)
854 }
855 if (c >= '\u30A0' && c <= '\u30FF') {
Eric Fischer549d7242009-03-31 14:19:47 -0700856 if (!includeNonStarters) {
857 switch (c) {
858 case '\u30A0': // # KATAKANA-HIRAGANA DOUBLE HYPHEN
859 case '\u30A1': // # KATAKANA LETTER SMALL A
860 case '\u30A3': // # KATAKANA LETTER SMALL I
861 case '\u30A5': // # KATAKANA LETTER SMALL U
862 case '\u30A7': // # KATAKANA LETTER SMALL E
863 case '\u30A9': // # KATAKANA LETTER SMALL O
864 case '\u30C3': // # KATAKANA LETTER SMALL TU
865 case '\u30E3': // # KATAKANA LETTER SMALL YA
866 case '\u30E5': // # KATAKANA LETTER SMALL YU
867 case '\u30E7': // # KATAKANA LETTER SMALL YO
868 case '\u30EE': // # KATAKANA LETTER SMALL WA
869 case '\u30F5': // # KATAKANA LETTER SMALL KA
870 case '\u30F6': // # KATAKANA LETTER SMALL KE
871 case '\u30FB': // # KATAKANA MIDDLE DOT
872 case '\u30FC': // # KATAKANA-HIRAGANA PROLONGED SOUND MARK
873 case '\u30FD': // # KATAKANA ITERATION MARK
874 case '\u30FE': // # KATAKANA VOICED ITERATION MARK
875 return false;
876 }
877 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800878 return true; // Katakana (except small characters)
879 }
880 if (c >= '\u3400' && c <= '\u4DB5') {
881 return true; // CJK UNIFIED IDEOGRAPHS EXTENSION A
882 }
883 if (c >= '\u4E00' && c <= '\u9FBB') {
884 return true; // CJK UNIFIED IDEOGRAPHS
885 }
886 if (c >= '\uF900' && c <= '\uFAD9') {
887 return true; // CJK COMPATIBILITY IDEOGRAPHS
888 }
889 if (c >= '\uA000' && c <= '\uA48F') {
890 return true; // YI SYLLABLES
891 }
892 if (c >= '\uA490' && c <= '\uA4CF') {
893 return true; // YI RADICALS
894 }
895 if (c >= '\uFE62' && c <= '\uFE66') {
896 return true; // SMALL PLUS SIGN to SMALL EQUALS SIGN
897 }
898 if (c >= '\uFF10' && c <= '\uFF19') {
899 return true; // WIDE DIGITS
900 }
901
902 return false;
903 }
904
905/*
906 private static void dump(byte[] data, int count, String label) {
907 if (false) {
908 System.out.print(label);
909
910 for (int i = 0; i < count; i++)
911 System.out.print(" " + data[i]);
912
913 System.out.println();
914 }
915 }
916*/
917
918 private static int getFit(TextPaint paint,
919 TextPaint workPaint,
920 CharSequence text, int start, int end,
921 float wid) {
922 int high = end + 1, low = start - 1, guess;
923
924 while (high - low > 1) {
925 guess = (high + low) / 2;
926
927 if (measureText(paint, workPaint,
928 text, start, guess, null, true, null) > wid)
929 high = guess;
930 else
931 low = guess;
932 }
933
934 if (low < start)
935 return start;
936 else
937 return low;
938 }
939
940 private int out(CharSequence text, int start, int end,
941 int above, int below, int top, int bottom, int v,
942 float spacingmult, float spacingadd,
943 LineHeightSpan[] chooseht, int[] choosehtv,
944 Paint.FontMetricsInt fm, boolean tab,
945 boolean needMultiply, int pstart, byte[] chdirs,
946 int dir, boolean easy, boolean last,
947 boolean includepad, boolean trackpad,
948 float[] widths, int widstart, int widoff,
949 TextUtils.TruncateAt ellipsize, float ellipsiswidth,
950 float textwidth, TextPaint paint) {
951 int j = mLineCount;
952 int off = j * mColumns;
953 int want = off + mColumns + TOP;
954 int[] lines = mLines;
955
956 // Log.e("text", "line " + start + " to " + end + (last ? "===" : ""));
957
958 if (want >= lines.length) {
959 int nlen = ArrayUtils.idealIntArraySize(want + 1);
960 int[] grow = new int[nlen];
961 System.arraycopy(lines, 0, grow, 0, lines.length);
962 mLines = grow;
963 lines = grow;
964
965 Directions[] grow2 = new Directions[nlen];
966 System.arraycopy(mLineDirections, 0, grow2, 0,
967 mLineDirections.length);
968 mLineDirections = grow2;
969 }
970
971 if (chooseht != null) {
972 fm.ascent = above;
973 fm.descent = below;
974 fm.top = top;
975 fm.bottom = bottom;
976
977 for (int i = 0; i < chooseht.length; i++) {
Eric Fischera9f1dd02009-08-12 15:00:10 -0700978 if (chooseht[i] instanceof LineHeightSpan.WithDensity) {
979 ((LineHeightSpan.WithDensity) chooseht[i]).
980 chooseHeight(text, start, end, choosehtv[i], v, fm, paint);
981
982 } else {
983 chooseht[i].chooseHeight(text, start, end, choosehtv[i], v, fm);
984 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800985 }
986
987 above = fm.ascent;
988 below = fm.descent;
989 top = fm.top;
990 bottom = fm.bottom;
991 }
992
993 if (j == 0) {
994 if (trackpad) {
995 mTopPadding = top - above;
996 }
997
998 if (includepad) {
999 above = top;
1000 }
1001 }
1002 if (last) {
1003 if (trackpad) {
1004 mBottomPadding = bottom - below;
1005 }
1006
1007 if (includepad) {
1008 below = bottom;
1009 }
1010 }
1011
1012 int extra;
1013
1014 if (needMultiply) {
Doug Felt10657582010-02-22 11:19:01 -08001015 double ex = (below - above) * (spacingmult - 1) + spacingadd;
1016 if (ex >= 0) {
1017 extra = (int)(ex + 0.5);
1018 } else {
1019 extra = -(int)(-ex + 0.5);
1020 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001021 } else {
1022 extra = 0;
1023 }
1024
1025 lines[off + START] = start;
1026 lines[off + TOP] = v;
1027 lines[off + DESCENT] = below + extra;
1028
1029 v += (below - above) + extra;
1030 lines[off + mColumns + START] = end;
1031 lines[off + mColumns + TOP] = v;
1032
1033 if (tab)
1034 lines[off + TAB] |= TAB_MASK;
1035
1036 {
1037 lines[off + DIR] |= dir << DIR_SHIFT;
1038
1039 int cur = Character.DIRECTIONALITY_LEFT_TO_RIGHT;
1040 int count = 0;
1041
1042 if (!easy) {
1043 for (int k = start; k < end; k++) {
1044 if (chdirs[k - pstart] != cur) {
1045 count++;
1046 cur = chdirs[k - pstart];
1047 }
1048 }
1049 }
1050
1051 Directions linedirs;
1052
1053 if (count == 0) {
1054 linedirs = DIRS_ALL_LEFT_TO_RIGHT;
1055 } else {
1056 short[] ld = new short[count + 1];
1057
1058 cur = Character.DIRECTIONALITY_LEFT_TO_RIGHT;
1059 count = 0;
1060 int here = start;
1061
1062 for (int k = start; k < end; k++) {
1063 if (chdirs[k - pstart] != cur) {
1064 // XXX check to make sure we don't
1065 // overflow short
1066 ld[count++] = (short) (k - here);
1067 cur = chdirs[k - pstart];
1068 here = k;
1069 }
1070 }
1071
1072 ld[count] = (short) (end - here);
1073
1074 if (count == 1 && ld[0] == 0) {
1075 linedirs = DIRS_ALL_RIGHT_TO_LEFT;
1076 } else {
1077 linedirs = new Directions(ld);
1078 }
1079 }
1080
1081 mLineDirections[j] = linedirs;
1082
1083 // If ellipsize is in marquee mode, do not apply ellipsis on the first line
1084 if (ellipsize != null && (ellipsize != TextUtils.TruncateAt.MARQUEE || j != 0)) {
1085 calculateEllipsis(start, end, widths, widstart, widoff,
1086 ellipsiswidth, ellipsize, j,
1087 textwidth, paint);
1088 }
1089 }
1090
1091 mLineCount++;
1092 return v;
1093 }
1094
1095 private void calculateEllipsis(int linestart, int lineend,
1096 float[] widths, int widstart, int widoff,
1097 float avail, TextUtils.TruncateAt where,
1098 int line, float textwidth, TextPaint paint) {
1099 int len = lineend - linestart;
1100
1101 if (textwidth <= avail) {
1102 // Everything fits!
1103 mLines[mColumns * line + ELLIPSIS_START] = 0;
1104 mLines[mColumns * line + ELLIPSIS_COUNT] = 0;
1105 return;
1106 }
1107
1108 float ellipsiswid = paint.measureText("\u2026");
1109 int ellipsisStart, ellipsisCount;
1110
1111 if (where == TextUtils.TruncateAt.START) {
1112 float sum = 0;
1113 int i;
1114
1115 for (i = len; i >= 0; i--) {
1116 float w = widths[i - 1 + linestart - widstart + widoff];
1117
1118 if (w + sum + ellipsiswid > avail) {
1119 break;
1120 }
1121
1122 sum += w;
1123 }
1124
1125 ellipsisStart = 0;
1126 ellipsisCount = i;
1127 } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE) {
1128 float sum = 0;
1129 int i;
1130
1131 for (i = 0; i < len; i++) {
1132 float w = widths[i + linestart - widstart + widoff];
1133
1134 if (w + sum + ellipsiswid > avail) {
1135 break;
1136 }
1137
1138 sum += w;
1139 }
1140
1141 ellipsisStart = i;
1142 ellipsisCount = len - i;
1143 } else /* where = TextUtils.TruncateAt.MIDDLE */ {
1144 float lsum = 0, rsum = 0;
1145 int left = 0, right = len;
1146
1147 float ravail = (avail - ellipsiswid) / 2;
1148 for (right = len; right >= 0; right--) {
1149 float w = widths[right - 1 + linestart - widstart + widoff];
1150
1151 if (w + rsum > ravail) {
1152 break;
1153 }
1154
1155 rsum += w;
1156 }
1157
1158 float lavail = avail - ellipsiswid - rsum;
1159 for (left = 0; left < right; left++) {
1160 float w = widths[left + linestart - widstart + widoff];
1161
1162 if (w + lsum > lavail) {
1163 break;
1164 }
1165
1166 lsum += w;
1167 }
1168
1169 ellipsisStart = left;
1170 ellipsisCount = right - left;
1171 }
1172
1173 mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
1174 mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
1175 }
1176
1177 // Override the baseclass so we can directly access our members,
1178 // rather than relying on member functions.
1179 // The logic mirrors that of Layout.getLineForVertical
1180 // FIXME: It may be faster to do a linear search for layouts without many lines.
1181 public int getLineForVertical(int vertical) {
1182 int high = mLineCount;
1183 int low = -1;
1184 int guess;
1185 int[] lines = mLines;
1186 while (high - low > 1) {
1187 guess = (high + low) >> 1;
1188 if (lines[mColumns * guess + TOP] > vertical){
1189 high = guess;
1190 } else {
1191 low = guess;
1192 }
1193 }
1194 if (low < 0) {
1195 return 0;
1196 } else {
1197 return low;
1198 }
1199 }
1200
1201 public int getLineCount() {
1202 return mLineCount;
1203 }
1204
1205 public int getLineTop(int line) {
1206 return mLines[mColumns * line + TOP];
1207 }
1208
1209 public int getLineDescent(int line) {
1210 return mLines[mColumns * line + DESCENT];
1211 }
1212
1213 public int getLineStart(int line) {
1214 return mLines[mColumns * line + START] & START_MASK;
1215 }
1216
1217 public int getParagraphDirection(int line) {
1218 return mLines[mColumns * line + DIR] >> DIR_SHIFT;
1219 }
1220
1221 public boolean getLineContainsTab(int line) {
1222 return (mLines[mColumns * line + TAB] & TAB_MASK) != 0;
1223 }
1224
1225 public final Directions getLineDirections(int line) {
1226 return mLineDirections[line];
1227 }
1228
1229 public int getTopPadding() {
1230 return mTopPadding;
1231 }
1232
1233 public int getBottomPadding() {
1234 return mBottomPadding;
1235 }
1236
1237 @Override
1238 public int getEllipsisCount(int line) {
1239 if (mColumns < COLUMNS_ELLIPSIZE) {
1240 return 0;
1241 }
1242
1243 return mLines[mColumns * line + ELLIPSIS_COUNT];
1244 }
1245
1246 @Override
1247 public int getEllipsisStart(int line) {
1248 if (mColumns < COLUMNS_ELLIPSIZE) {
1249 return 0;
1250 }
1251
1252 return mLines[mColumns * line + ELLIPSIS_START];
1253 }
1254
1255 @Override
1256 public int getEllipsizedWidth() {
1257 return mEllipsizedWidth;
1258 }
1259
1260 private int mLineCount;
1261 private int mTopPadding, mBottomPadding;
1262 private int mColumns;
1263 private int mEllipsizedWidth;
1264
1265 private static final int COLUMNS_NORMAL = 3;
1266 private static final int COLUMNS_ELLIPSIZE = 5;
1267 private static final int START = 0;
1268 private static final int DIR = START;
1269 private static final int TAB = START;
1270 private static final int TOP = 1;
1271 private static final int DESCENT = 2;
1272 private static final int ELLIPSIS_START = 3;
1273 private static final int ELLIPSIS_COUNT = 4;
1274
1275 private int[] mLines;
1276 private Directions[] mLineDirections;
1277
1278 private static final int START_MASK = 0x1FFFFFFF;
1279 private static final int DIR_MASK = 0xC0000000;
1280 private static final int DIR_SHIFT = 30;
1281 private static final int TAB_MASK = 0x20000000;
1282
1283 private static final char FIRST_RIGHT_TO_LEFT = '\u0590';
1284
1285 /*
1286 * These are reused across calls to generate()
1287 */
1288 private byte[] mChdirs;
1289 private char[] mChs;
1290 private float[] mWidths;
1291 private Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
1292}