blob: 0fef40beb89f9c076aa94a5e2f7575a4b82167a6 [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
19import android.graphics.Paint;
20import com.android.internal.util.ArrayUtils;
21import android.util.Log;
22import android.text.style.LeadingMarginSpan;
23import android.text.style.LineHeightSpan;
24import android.text.style.MetricAffectingSpan;
25import android.text.style.ReplacementSpan;
26
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
33 * {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int, float, float, android.graphics.Paint)
34 * Canvas.drawText()} directly.</p>
35 */
36public class
37StaticLayout
38extends Layout
39{
40 public StaticLayout(CharSequence source, TextPaint paint,
41 int width,
42 Alignment align, float spacingmult, float spacingadd,
43 boolean includepad) {
44 this(source, 0, source.length(), paint, width, align,
45 spacingmult, spacingadd, includepad);
46 }
47
48 public StaticLayout(CharSequence source, int bufstart, int bufend,
49 TextPaint paint, int outerwidth,
50 Alignment align,
51 float spacingmult, float spacingadd,
52 boolean includepad) {
53 this(source, bufstart, bufend, paint, outerwidth, align,
54 spacingmult, spacingadd, includepad, null, 0);
55 }
56
57 public StaticLayout(CharSequence source, int bufstart, int bufend,
58 TextPaint paint, int outerwidth,
59 Alignment align,
60 float spacingmult, float spacingadd,
61 boolean includepad,
62 TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
63 super((ellipsize == null)
64 ? source
65 : (source instanceof Spanned)
66 ? new SpannedEllipsizer(source)
67 : new Ellipsizer(source),
68 paint, outerwidth, align, spacingmult, spacingadd);
69
70 /*
71 * This is annoying, but we can't refer to the layout until
72 * superclass construction is finished, and the superclass
73 * constructor wants the reference to the display text.
74 *
75 * This will break if the superclass constructor ever actually
76 * cares about the content instead of just holding the reference.
77 */
78 if (ellipsize != null) {
79 Ellipsizer e = (Ellipsizer) getText();
80
81 e.mLayout = this;
82 e.mWidth = ellipsizedWidth;
83 e.mMethod = ellipsize;
84 mEllipsizedWidth = ellipsizedWidth;
85
86 mColumns = COLUMNS_ELLIPSIZE;
87 } else {
88 mColumns = COLUMNS_NORMAL;
89 mEllipsizedWidth = outerwidth;
90 }
91
92 mLines = new int[ArrayUtils.idealIntArraySize(2 * mColumns)];
93 mLineDirections = new Directions[
94 ArrayUtils.idealIntArraySize(2 * mColumns)];
95
96 generate(source, bufstart, bufend, paint, outerwidth, align,
97 spacingmult, spacingadd, includepad, includepad,
98 ellipsize != null, ellipsizedWidth, ellipsize);
99
100 mChdirs = null;
101 mChs = null;
102 mWidths = null;
103 mFontMetricsInt = null;
104 }
105
106 /* package */ StaticLayout(boolean ellipsize) {
107 super(null, null, 0, null, 0, 0);
108
109 mColumns = COLUMNS_ELLIPSIZE;
110 mLines = new int[ArrayUtils.idealIntArraySize(2 * mColumns)];
111 mLineDirections = new Directions[
112 ArrayUtils.idealIntArraySize(2 * mColumns)];
113 }
114
115 /* package */ void generate(CharSequence source, int bufstart, int bufend,
116 TextPaint paint, int outerwidth,
117 Alignment align,
118 float spacingmult, float spacingadd,
119 boolean includepad, boolean trackpad,
120 boolean breakOnlyAtSpaces,
121 float ellipsizedWidth, TextUtils.TruncateAt where) {
122 mLineCount = 0;
123
124 int v = 0;
125 boolean needMultiply = (spacingmult != 1 || spacingadd != 0);
126
127 Paint.FontMetricsInt fm = mFontMetricsInt;
128 int[] choosehtv = null;
129
130 int end = TextUtils.indexOf(source, '\n', bufstart, bufend);
131 int bufsiz = end >= 0 ? end - bufstart : bufend - bufstart;
132 boolean first = true;
133
134 if (mChdirs == null) {
135 mChdirs = new byte[ArrayUtils.idealByteArraySize(bufsiz + 1)];
136 mChs = new char[ArrayUtils.idealCharArraySize(bufsiz + 1)];
137 mWidths = new float[ArrayUtils.idealIntArraySize((bufsiz + 1) * 2)];
138 }
139
140 byte[] chdirs = mChdirs;
141 char[] chs = mChs;
142 float[] widths = mWidths;
143
144 AlteredCharSequence alter = null;
145 Spanned spanned = null;
146
147 if (source instanceof Spanned)
148 spanned = (Spanned) source;
149
150 int DEFAULT_DIR = DIR_LEFT_TO_RIGHT; // XXX
151
152 for (int start = bufstart; start <= bufend; start = end) {
153 if (first)
154 first = false;
155 else
156 end = TextUtils.indexOf(source, '\n', start, bufend);
157
158 if (end < 0)
159 end = bufend;
160 else
161 end++;
162
163 int firstwidth = outerwidth;
164 int restwidth = outerwidth;
165
166 LineHeightSpan[] chooseht = null;
167
168 if (spanned != null) {
169 LeadingMarginSpan[] sp;
170
171 sp = spanned.getSpans(start, end, LeadingMarginSpan.class);
172 for (int i = 0; i < sp.length; i++) {
173 firstwidth -= sp[i].getLeadingMargin(true);
174 restwidth -= sp[i].getLeadingMargin(false);
175 }
176
177 chooseht = spanned.getSpans(start, end, LineHeightSpan.class);
178
179 if (chooseht.length != 0) {
180 if (choosehtv == null ||
181 choosehtv.length < chooseht.length) {
182 choosehtv = new int[ArrayUtils.idealIntArraySize(
183 chooseht.length)];
184 }
185
186 for (int i = 0; i < chooseht.length; i++) {
187 int o = spanned.getSpanStart(chooseht[i]);
188
189 if (o < start) {
190 // starts in this layout, before the
191 // current paragraph
192
193 choosehtv[i] = getLineTop(getLineForOffset(o));
194 } else {
195 // starts in this paragraph
196
197 choosehtv[i] = v;
198 }
199 }
200 }
201 }
202
203 if (end - start > chdirs.length) {
204 chdirs = new byte[ArrayUtils.idealByteArraySize(end - start)];
205 mChdirs = chdirs;
206 }
207 if (end - start > chs.length) {
208 chs = new char[ArrayUtils.idealCharArraySize(end - start)];
209 mChs = chs;
210 }
211 if ((end - start) * 2 > widths.length) {
212 widths = new float[ArrayUtils.idealIntArraySize((end - start) * 2)];
213 mWidths = widths;
214 }
215
216 TextUtils.getChars(source, start, end, chs, 0);
217 final int n = end - start;
218
219 boolean easy = true;
220 boolean altered = false;
221 int dir = DEFAULT_DIR; // XXX
222
223 for (int i = 0; i < n; i++) {
224 if (chs[i] >= FIRST_RIGHT_TO_LEFT) {
225 easy = false;
226 break;
227 }
228 }
229
230 if (!easy) {
231 AndroidCharacter.getDirectionalities(chs, chdirs, end - start);
232
233 /*
234 * Determine primary paragraph direction
235 */
236
237 for (int j = start; j < end; j++) {
238 int d = chdirs[j - start];
239
240 if (d == Character.DIRECTIONALITY_LEFT_TO_RIGHT) {
241 dir = DIR_LEFT_TO_RIGHT;
242 break;
243 }
244 if (d == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
245 dir = DIR_RIGHT_TO_LEFT;
246 break;
247 }
248 }
249
250 /*
251 * XXX Explicit overrides should go here
252 */
253
254 /*
255 * Weak type resolution
256 */
257
258 final byte SOR = dir == DIR_LEFT_TO_RIGHT ?
259 Character.DIRECTIONALITY_LEFT_TO_RIGHT :
260 Character.DIRECTIONALITY_RIGHT_TO_LEFT;
261
262 // dump(chdirs, n, "initial");
263
264 // W1 non spacing marks
265 for (int j = 0; j < n; j++) {
266 if (chdirs[j] == Character.NON_SPACING_MARK) {
267 if (j == 0)
268 chdirs[j] = SOR;
269 else
270 chdirs[j] = chdirs[j - 1];
271 }
272 }
273
274 // dump(chdirs, n, "W1");
275
276 // W2 european numbers
277 byte cur = SOR;
278 for (int j = 0; j < n; j++) {
279 byte d = chdirs[j];
280
281 if (d == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
282 d == Character.DIRECTIONALITY_RIGHT_TO_LEFT ||
283 d == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC)
284 cur = d;
285 else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER) {
286 if (cur ==
287 Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC)
288 chdirs[j] = Character.DIRECTIONALITY_ARABIC_NUMBER;
289 }
290 }
291
292 // dump(chdirs, n, "W2");
293
294 // W3 arabic letters
295 for (int j = 0; j < n; j++) {
296 if (chdirs[j] == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC)
297 chdirs[j] = Character.DIRECTIONALITY_RIGHT_TO_LEFT;
298 }
299
300 // dump(chdirs, n, "W3");
301
302 // W4 single separator between numbers
303 for (int j = 1; j < n - 1; j++) {
304 byte d = chdirs[j];
305 byte prev = chdirs[j - 1];
306 byte next = chdirs[j + 1];
307
308 if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_SEPARATOR) {
309 if (prev == Character.DIRECTIONALITY_EUROPEAN_NUMBER &&
310 next == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
311 chdirs[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
312 } else if (d == Character.DIRECTIONALITY_COMMON_NUMBER_SEPARATOR) {
313 if (prev == Character.DIRECTIONALITY_EUROPEAN_NUMBER &&
314 next == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
315 chdirs[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
316 if (prev == Character.DIRECTIONALITY_ARABIC_NUMBER &&
317 next == Character.DIRECTIONALITY_ARABIC_NUMBER)
318 chdirs[j] = Character.DIRECTIONALITY_ARABIC_NUMBER;
319 }
320 }
321
322 // dump(chdirs, n, "W4");
323
324 // W5 european number terminators
325 boolean adjacent = false;
326 for (int j = 0; j < n; j++) {
327 byte d = chdirs[j];
328
329 if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
330 adjacent = true;
331 else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR && adjacent)
332 chdirs[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
333 else
334 adjacent = false;
335 }
336
337 //dump(chdirs, n, "W5");
338
339 // W5 european number terminators part 2,
340 // W6 separators and terminators
341 adjacent = false;
342 for (int j = n - 1; j >= 0; j--) {
343 byte d = chdirs[j];
344
345 if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
346 adjacent = true;
347 else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR) {
348 if (adjacent)
349 chdirs[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
350 else
351 chdirs[j] = Character.DIRECTIONALITY_OTHER_NEUTRALS;
352 }
353 else {
354 adjacent = false;
355
356 if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_SEPARATOR ||
357 d == Character.DIRECTIONALITY_COMMON_NUMBER_SEPARATOR ||
358 d == Character.DIRECTIONALITY_PARAGRAPH_SEPARATOR ||
359 d == Character.DIRECTIONALITY_SEGMENT_SEPARATOR)
360 chdirs[j] = Character.DIRECTIONALITY_OTHER_NEUTRALS;
361 }
362 }
363
364 // dump(chdirs, n, "W6");
365
366 // W7 strong direction of european numbers
367 cur = SOR;
368 for (int j = 0; j < n; j++) {
369 byte d = chdirs[j];
370
371 if (d == SOR ||
372 d == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
373 d == Character.DIRECTIONALITY_RIGHT_TO_LEFT)
374 cur = d;
375
376 if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
377 chdirs[j] = cur;
378 }
379
380 // dump(chdirs, n, "W7");
381
382 // N1, N2 neutrals
383 cur = SOR;
384 for (int j = 0; j < n; j++) {
385 byte d = chdirs[j];
386
387 if (d == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
388 d == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
389 cur = d;
390 } else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER ||
391 d == Character.DIRECTIONALITY_ARABIC_NUMBER) {
392 cur = Character.DIRECTIONALITY_RIGHT_TO_LEFT;
393 } else {
394 byte dd = SOR;
395 int k;
396
397 for (k = j + 1; k < n; k++) {
398 dd = chdirs[k];
399
400 if (dd == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
401 dd == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
402 break;
403 }
404 if (dd == Character.DIRECTIONALITY_EUROPEAN_NUMBER ||
405 dd == Character.DIRECTIONALITY_ARABIC_NUMBER) {
406 dd = Character.DIRECTIONALITY_RIGHT_TO_LEFT;
407 break;
408 }
409 }
410
411 for (int y = j; y < k; y++) {
412 if (dd == cur)
413 chdirs[y] = cur;
414 else
415 chdirs[y] = SOR;
416 }
417
418 j = k - 1;
419 }
420 }
421
422 // dump(chdirs, n, "final");
423
424 // extra: enforce that all tabs go the primary direction
425
426 for (int j = 0; j < n; j++) {
427 if (chs[j] == '\t')
428 chdirs[j] = SOR;
429 }
430
431 // extra: enforce that object replacements go to the
432 // primary direction
433 // and that none of the underlying characters are treated
434 // as viable breakpoints
435
436 if (source instanceof Spanned) {
437 Spanned sp = (Spanned) source;
438 ReplacementSpan[] spans = sp.getSpans(start, end, ReplacementSpan.class);
439
440 for (int y = 0; y < spans.length; y++) {
441 int a = sp.getSpanStart(spans[y]);
442 int b = sp.getSpanEnd(spans[y]);
443
444 for (int x = a; x < b; x++) {
445 chdirs[x - start] = SOR;
446 chs[x - start] = '\uFFFC';
447 }
448 }
449 }
450
451 // Do mirroring for right-to-left segments
452
453 for (int i = 0; i < n; i++) {
454 if (chdirs[i] == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
455 int j;
456
457 for (j = i; j < n; j++) {
458 if (chdirs[j] !=
459 Character.DIRECTIONALITY_RIGHT_TO_LEFT)
460 break;
461 }
462
463 if (AndroidCharacter.mirror(chs, i, j - i))
464 altered = true;
465
466 i = j - 1;
467 }
468 }
469 }
470
471 CharSequence sub;
472
473 if (altered) {
474 if (alter == null)
475 alter = AlteredCharSequence.make(source, chs, start, end);
476 else
477 alter.update(chs, start, end);
478
479 sub = alter;
480 } else {
481 sub = source;
482 }
483
484 int width = firstwidth;
485
486 float w = 0;
487 int here = start;
488
489 int ok = start;
490 float okwidth = w;
491 int okascent = 0, okdescent = 0, oktop = 0, okbottom = 0;
492
493 int fit = start;
494 float fitwidth = w;
495 int fitascent = 0, fitdescent = 0, fittop = 0, fitbottom = 0;
496
497 boolean tab = false;
498
499 int next;
500 for (int i = start; i < end; i = next) {
501 if (spanned == null)
502 next = end;
503 else
504 next = spanned.nextSpanTransition(i, end,
505 MetricAffectingSpan.
506 class);
507
508 if (spanned == null) {
509 paint.getTextWidths(sub, i, next, widths);
510 System.arraycopy(widths, 0, widths,
511 end - start + (i - start), next - i);
512
513 paint.getFontMetricsInt(fm);
514 } else {
515 mWorkPaint.baselineShift = 0;
516
517 Styled.getTextWidths(paint, mWorkPaint,
518 spanned, i, next,
519 widths, fm);
520 System.arraycopy(widths, 0, widths,
521 end - start + (i - start), next - i);
522
523 if (mWorkPaint.baselineShift < 0) {
524 fm.ascent += mWorkPaint.baselineShift;
525 fm.top += mWorkPaint.baselineShift;
526 } else {
527 fm.descent += mWorkPaint.baselineShift;
528 fm.bottom += mWorkPaint.baselineShift;
529 }
530 }
531
532 int fmtop = fm.top;
533 int fmbottom = fm.bottom;
534 int fmascent = fm.ascent;
535 int fmdescent = fm.descent;
536
537 if (false) {
538 StringBuilder sb = new StringBuilder();
539 for (int j = i; j < next; j++) {
540 sb.append(widths[j - start + (end - start)]);
541 sb.append(' ');
542 }
543
544 Log.e("text", sb.toString());
545 }
546
547 for (int j = i; j < next; j++) {
548 char c = chs[j - start];
549 float before = w;
550
551 switch (c) {
552 case '\n':
553 break;
554
555 case '\t':
556 w = Layout.nextTab(sub, start, end, w, null);
557 tab = true;
558 break;
559
560 default:
561 w += widths[j - start + (end - start)];
562 }
563
564 // Log.e("text", "was " + before + " now " + w + " after " + c + " within " + width);
565
566 if (w <= width) {
567 fitwidth = w;
568 fit = j + 1;
569
570 if (fmtop < fittop)
571 fittop = fmtop;
572 if (fmascent < fitascent)
573 fitascent = fmascent;
574 if (fmdescent > fitdescent)
575 fitdescent = fmdescent;
576 if (fmbottom > fitbottom)
577 fitbottom = fmbottom;
578
579 /*
580 * From the Unicode Line Breaking Algorithm:
581 * (at least approximately)
582 *
583 * .,:; are class IS: breakpoints
584 * except when adjacent to digits
585 * / is class SY: a breakpoint
586 * except when followed by a digit.
587 * - is class HY: a breakpoint
588 * except when followed by a digit.
589 *
590 * Ideographs are class ID: breakpoints when adjacent.
591 */
592
593 if (c == ' ' || c == '\t' ||
594 ((c == '.' || c == ',' || c == ':' || c == ';') &&
595 (j - 1 < here || !Character.isDigit(chs[j - 1 - start])) &&
596 (j + 1 >= next || !Character.isDigit(chs[j + 1 - start]))) ||
597 ((c == '/' || c == '-') &&
598 (j + 1 >= next || !Character.isDigit(chs[j + 1 - start]))) ||
599 (c >= FIRST_CJK && isIdeographic(c) &&
600 j + 1 < next && isIdeographic(chs[j + 1 - start]))) {
601 okwidth = w;
602 ok = j + 1;
603
604 if (fittop < oktop)
605 oktop = fittop;
606 if (fitascent < okascent)
607 okascent = fitascent;
608 if (fitdescent > okdescent)
609 okdescent = fitdescent;
610 if (fitbottom > okbottom)
611 okbottom = fitbottom;
612 }
613 } else if (breakOnlyAtSpaces) {
614 if (ok != here) {
615 // Log.e("text", "output ok " + here + " to " +ok);
616
617 while (ok < next && chs[ok - start] == ' ') {
618 ok++;
619 }
620
621 v = out(source,
622 here, ok,
623 okascent, okdescent, oktop, okbottom,
624 v,
625 spacingmult, spacingadd, chooseht,
626 choosehtv, fm, tab,
627 needMultiply, start, chdirs, dir, easy,
628 ok == bufend, includepad, trackpad,
629 widths, start, end - start,
630 where, ellipsizedWidth, okwidth,
631 paint);
632
633 here = ok;
634 } else {
635 // Act like it fit even though it didn't.
636
637 fitwidth = w;
638 fit = j + 1;
639
640 if (fmtop < fittop)
641 fittop = fmtop;
642 if (fmascent < fitascent)
643 fitascent = fmascent;
644 if (fmdescent > fitdescent)
645 fitdescent = fmdescent;
646 if (fmbottom > fitbottom)
647 fitbottom = fmbottom;
648 }
649 } else {
650 if (ok != here) {
651 // Log.e("text", "output ok " + here + " to " +ok);
652
653 while (ok < next && chs[ok - start] == ' ') {
654 ok++;
655 }
656
657 v = out(source,
658 here, ok,
659 okascent, okdescent, oktop, okbottom,
660 v,
661 spacingmult, spacingadd, chooseht,
662 choosehtv, fm, tab,
663 needMultiply, start, chdirs, dir, easy,
664 ok == bufend, includepad, trackpad,
665 widths, start, end - start,
666 where, ellipsizedWidth, okwidth,
667 paint);
668
669 here = ok;
670 } else if (fit != here) {
671 // Log.e("text", "output fit " + here + " to " +fit);
672 v = out(source,
673 here, fit,
674 fitascent, fitdescent,
675 fittop, fitbottom,
676 v,
677 spacingmult, spacingadd, chooseht,
678 choosehtv, fm, tab,
679 needMultiply, start, chdirs, dir, easy,
680 fit == bufend, includepad, trackpad,
681 widths, start, end - start,
682 where, ellipsizedWidth, fitwidth,
683 paint);
684
685 here = fit;
686 } else {
687 // Log.e("text", "output one " + here + " to " +(here + 1));
688 measureText(paint, mWorkPaint,
689 source, here, here + 1, fm, tab,
690 null);
691
692 v = out(source,
693 here, here+1,
694 fm.ascent, fm.descent,
695 fm.top, fm.bottom,
696 v,
697 spacingmult, spacingadd, chooseht,
698 choosehtv, fm, tab,
699 needMultiply, start, chdirs, dir, easy,
700 here + 1 == bufend, includepad,
701 trackpad,
702 widths, start, end - start,
703 where, ellipsizedWidth,
704 widths[here - start], paint);
705
706 here = here + 1;
707 }
708
709 if (here < i) {
710 j = next = here; // must remeasure
711 } else {
712 j = here - 1; // continue looping
713 }
714
715 ok = fit = here;
716 w = 0;
717 fitascent = fitdescent = fittop = fitbottom = 0;
718 okascent = okdescent = oktop = okbottom = 0;
719
720 width = restwidth;
721 }
722 }
723 }
724
725 if (end != here) {
726 if ((fittop | fitbottom | fitdescent | fitascent) == 0) {
727 paint.getFontMetricsInt(fm);
728
729 fittop = fm.top;
730 fitbottom = fm.bottom;
731 fitascent = fm.ascent;
732 fitdescent = fm.descent;
733 }
734
735 // Log.e("text", "output rest " + here + " to " + end);
736
737 v = out(source,
738 here, end, fitascent, fitdescent,
739 fittop, fitbottom,
740 v,
741 spacingmult, spacingadd, chooseht,
742 choosehtv, fm, tab,
743 needMultiply, start, chdirs, dir, easy,
744 end == bufend, includepad, trackpad,
745 widths, start, end - start,
746 where, ellipsizedWidth, w, paint);
747 }
748
749 start = end;
750
751 if (end == bufend)
752 break;
753 }
754
755 if (bufend == bufstart || source.charAt(bufend - 1) == '\n') {
756 // Log.e("text", "output last " + bufend);
757
758 paint.getFontMetricsInt(fm);
759
760 v = out(source,
761 bufend, bufend, fm.ascent, fm.descent,
762 fm.top, fm.bottom,
763 v,
764 spacingmult, spacingadd, null,
765 null, fm, false,
766 needMultiply, bufend, chdirs, DEFAULT_DIR, true,
767 true, includepad, trackpad,
768 widths, bufstart, 0,
769 where, ellipsizedWidth, 0, paint);
770 }
771 }
772
773 private static final char FIRST_CJK = '\u2E80';
774 /**
775 * Returns true if the specified character is one of those specified
776 * as being Ideographic (class ID) by the Unicode Line Breaking Algorithm
777 * (http://www.unicode.org/unicode/reports/tr14/), and is therefore OK
778 * to break between a pair of.
779 */
780 private static final boolean isIdeographic(char c) {
781 if (c >= '\u2E80' && c <= '\u2FFF') {
782 return true; // CJK, KANGXI RADICALS, DESCRIPTION SYMBOLS
783 }
784 if (c == '\u3000') {
785 return true; // IDEOGRAPHIC SPACE
786 }
787 if (c >= '\u3040' && c <= '\u309F') {
788 return true; // Hiragana (except small characters)
789 }
790 if (c >= '\u30A0' && c <= '\u30FF') {
791 return true; // Katakana (except small characters)
792 }
793 if (c >= '\u3400' && c <= '\u4DB5') {
794 return true; // CJK UNIFIED IDEOGRAPHS EXTENSION A
795 }
796 if (c >= '\u4E00' && c <= '\u9FBB') {
797 return true; // CJK UNIFIED IDEOGRAPHS
798 }
799 if (c >= '\uF900' && c <= '\uFAD9') {
800 return true; // CJK COMPATIBILITY IDEOGRAPHS
801 }
802 if (c >= '\uA000' && c <= '\uA48F') {
803 return true; // YI SYLLABLES
804 }
805 if (c >= '\uA490' && c <= '\uA4CF') {
806 return true; // YI RADICALS
807 }
808 if (c >= '\uFE62' && c <= '\uFE66') {
809 return true; // SMALL PLUS SIGN to SMALL EQUALS SIGN
810 }
811 if (c >= '\uFF10' && c <= '\uFF19') {
812 return true; // WIDE DIGITS
813 }
814
815 return false;
816 }
817
818/*
819 private static void dump(byte[] data, int count, String label) {
820 if (false) {
821 System.out.print(label);
822
823 for (int i = 0; i < count; i++)
824 System.out.print(" " + data[i]);
825
826 System.out.println();
827 }
828 }
829*/
830
831 private static int getFit(TextPaint paint,
832 TextPaint workPaint,
833 CharSequence text, int start, int end,
834 float wid) {
835 int high = end + 1, low = start - 1, guess;
836
837 while (high - low > 1) {
838 guess = (high + low) / 2;
839
840 if (measureText(paint, workPaint,
841 text, start, guess, null, true, null) > wid)
842 high = guess;
843 else
844 low = guess;
845 }
846
847 if (low < start)
848 return start;
849 else
850 return low;
851 }
852
853 private int out(CharSequence text, int start, int end,
854 int above, int below, int top, int bottom, int v,
855 float spacingmult, float spacingadd,
856 LineHeightSpan[] chooseht, int[] choosehtv,
857 Paint.FontMetricsInt fm, boolean tab,
858 boolean needMultiply, int pstart, byte[] chdirs,
859 int dir, boolean easy, boolean last,
860 boolean includepad, boolean trackpad,
861 float[] widths, int widstart, int widoff,
862 TextUtils.TruncateAt ellipsize, float ellipsiswidth,
863 float textwidth, TextPaint paint) {
864 int j = mLineCount;
865 int off = j * mColumns;
866 int want = off + mColumns + TOP;
867 int[] lines = mLines;
868
869 // Log.e("text", "line " + start + " to " + end + (last ? "===" : ""));
870
871 if (want >= lines.length) {
872 int nlen = ArrayUtils.idealIntArraySize(want + 1);
873 int[] grow = new int[nlen];
874 System.arraycopy(lines, 0, grow, 0, lines.length);
875 mLines = grow;
876 lines = grow;
877
878 Directions[] grow2 = new Directions[nlen];
879 System.arraycopy(mLineDirections, 0, grow2, 0,
880 mLineDirections.length);
881 mLineDirections = grow2;
882 }
883
884 if (chooseht != null) {
885 fm.ascent = above;
886 fm.descent = below;
887 fm.top = top;
888 fm.bottom = bottom;
889
890 for (int i = 0; i < chooseht.length; i++) {
891 chooseht[i].chooseHeight(text, start, end, choosehtv[i], v, fm);
892 }
893
894 above = fm.ascent;
895 below = fm.descent;
896 top = fm.top;
897 bottom = fm.bottom;
898 }
899
900 if (j == 0) {
901 if (trackpad) {
902 mTopPadding = top - above;
903 }
904
905 if (includepad) {
906 above = top;
907 }
908 }
909 if (last) {
910 if (trackpad) {
911 mBottomPadding = bottom - below;
912 }
913
914 if (includepad) {
915 below = bottom;
916 }
917 }
918
919 int extra;
920
921 if (needMultiply) {
922 extra = (int) ((below - above) * (spacingmult - 1)
923 + spacingadd + 0.5);
924 } else {
925 extra = 0;
926 }
927
928 lines[off + START] = start;
929 lines[off + TOP] = v;
930 lines[off + DESCENT] = below + extra;
931
932 v += (below - above) + extra;
933 lines[off + mColumns + START] = end;
934 lines[off + mColumns + TOP] = v;
935
936 if (tab)
937 lines[off + TAB] |= TAB_MASK;
938
939 {
940 lines[off + DIR] |= dir << DIR_SHIFT;
941
942 int cur = Character.DIRECTIONALITY_LEFT_TO_RIGHT;
943 int count = 0;
944
945 if (!easy) {
946 for (int k = start; k < end; k++) {
947 if (chdirs[k - pstart] != cur) {
948 count++;
949 cur = chdirs[k - pstart];
950 }
951 }
952 }
953
954 Directions linedirs;
955
956 if (count == 0) {
957 linedirs = DIRS_ALL_LEFT_TO_RIGHT;
958 } else {
959 short[] ld = new short[count + 1];
960
961 cur = Character.DIRECTIONALITY_LEFT_TO_RIGHT;
962 count = 0;
963 int here = start;
964
965 for (int k = start; k < end; k++) {
966 if (chdirs[k - pstart] != cur) {
967 // XXX check to make sure we don't
968 // overflow short
969 ld[count++] = (short) (k - here);
970 cur = chdirs[k - pstart];
971 here = k;
972 }
973 }
974
975 ld[count] = (short) (end - here);
976
977 if (count == 1 && ld[0] == 0) {
978 linedirs = DIRS_ALL_RIGHT_TO_LEFT;
979 } else {
980 linedirs = new Directions(ld);
981 }
982 }
983
984 mLineDirections[j] = linedirs;
985
986 // If ellipsize is in marquee mode, do not apply ellipsis on the first line
987 if (ellipsize != null && (ellipsize != TextUtils.TruncateAt.MARQUEE || j != 0)) {
988 calculateEllipsis(start, end, widths, widstart, widoff,
989 ellipsiswidth, ellipsize, j,
990 textwidth, paint);
991 }
992 }
993
994 mLineCount++;
995 return v;
996 }
997
998 private void calculateEllipsis(int linestart, int lineend,
999 float[] widths, int widstart, int widoff,
1000 float avail, TextUtils.TruncateAt where,
1001 int line, float textwidth, TextPaint paint) {
1002 int len = lineend - linestart;
1003
1004 if (textwidth <= avail) {
1005 // Everything fits!
1006 mLines[mColumns * line + ELLIPSIS_START] = 0;
1007 mLines[mColumns * line + ELLIPSIS_COUNT] = 0;
1008 return;
1009 }
1010
1011 float ellipsiswid = paint.measureText("\u2026");
1012 int ellipsisStart, ellipsisCount;
1013
1014 if (where == TextUtils.TruncateAt.START) {
1015 float sum = 0;
1016 int i;
1017
1018 for (i = len; i >= 0; i--) {
1019 float w = widths[i - 1 + linestart - widstart + widoff];
1020
1021 if (w + sum + ellipsiswid > avail) {
1022 break;
1023 }
1024
1025 sum += w;
1026 }
1027
1028 ellipsisStart = 0;
1029 ellipsisCount = i;
1030 } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE) {
1031 float sum = 0;
1032 int i;
1033
1034 for (i = 0; i < len; i++) {
1035 float w = widths[i + linestart - widstart + widoff];
1036
1037 if (w + sum + ellipsiswid > avail) {
1038 break;
1039 }
1040
1041 sum += w;
1042 }
1043
1044 ellipsisStart = i;
1045 ellipsisCount = len - i;
1046 } else /* where = TextUtils.TruncateAt.MIDDLE */ {
1047 float lsum = 0, rsum = 0;
1048 int left = 0, right = len;
1049
1050 float ravail = (avail - ellipsiswid) / 2;
1051 for (right = len; right >= 0; right--) {
1052 float w = widths[right - 1 + linestart - widstart + widoff];
1053
1054 if (w + rsum > ravail) {
1055 break;
1056 }
1057
1058 rsum += w;
1059 }
1060
1061 float lavail = avail - ellipsiswid - rsum;
1062 for (left = 0; left < right; left++) {
1063 float w = widths[left + linestart - widstart + widoff];
1064
1065 if (w + lsum > lavail) {
1066 break;
1067 }
1068
1069 lsum += w;
1070 }
1071
1072 ellipsisStart = left;
1073 ellipsisCount = right - left;
1074 }
1075
1076 mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
1077 mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
1078 }
1079
1080 // Override the baseclass so we can directly access our members,
1081 // rather than relying on member functions.
1082 // The logic mirrors that of Layout.getLineForVertical
1083 // FIXME: It may be faster to do a linear search for layouts without many lines.
1084 public int getLineForVertical(int vertical) {
1085 int high = mLineCount;
1086 int low = -1;
1087 int guess;
1088 int[] lines = mLines;
1089 while (high - low > 1) {
1090 guess = (high + low) >> 1;
1091 if (lines[mColumns * guess + TOP] > vertical){
1092 high = guess;
1093 } else {
1094 low = guess;
1095 }
1096 }
1097 if (low < 0) {
1098 return 0;
1099 } else {
1100 return low;
1101 }
1102 }
1103
1104 public int getLineCount() {
1105 return mLineCount;
1106 }
1107
1108 public int getLineTop(int line) {
1109 return mLines[mColumns * line + TOP];
1110 }
1111
1112 public int getLineDescent(int line) {
1113 return mLines[mColumns * line + DESCENT];
1114 }
1115
1116 public int getLineStart(int line) {
1117 return mLines[mColumns * line + START] & START_MASK;
1118 }
1119
1120 public int getParagraphDirection(int line) {
1121 return mLines[mColumns * line + DIR] >> DIR_SHIFT;
1122 }
1123
1124 public boolean getLineContainsTab(int line) {
1125 return (mLines[mColumns * line + TAB] & TAB_MASK) != 0;
1126 }
1127
1128 public final Directions getLineDirections(int line) {
1129 return mLineDirections[line];
1130 }
1131
1132 public int getTopPadding() {
1133 return mTopPadding;
1134 }
1135
1136 public int getBottomPadding() {
1137 return mBottomPadding;
1138 }
1139
1140 @Override
1141 public int getEllipsisCount(int line) {
1142 if (mColumns < COLUMNS_ELLIPSIZE) {
1143 return 0;
1144 }
1145
1146 return mLines[mColumns * line + ELLIPSIS_COUNT];
1147 }
1148
1149 @Override
1150 public int getEllipsisStart(int line) {
1151 if (mColumns < COLUMNS_ELLIPSIZE) {
1152 return 0;
1153 }
1154
1155 return mLines[mColumns * line + ELLIPSIS_START];
1156 }
1157
1158 @Override
1159 public int getEllipsizedWidth() {
1160 return mEllipsizedWidth;
1161 }
1162
1163 private int mLineCount;
1164 private int mTopPadding, mBottomPadding;
1165 private int mColumns;
1166 private int mEllipsizedWidth;
1167
1168 private static final int COLUMNS_NORMAL = 3;
1169 private static final int COLUMNS_ELLIPSIZE = 5;
1170 private static final int START = 0;
1171 private static final int DIR = START;
1172 private static final int TAB = START;
1173 private static final int TOP = 1;
1174 private static final int DESCENT = 2;
1175 private static final int ELLIPSIS_START = 3;
1176 private static final int ELLIPSIS_COUNT = 4;
1177
1178 private int[] mLines;
1179 private Directions[] mLineDirections;
1180
1181 private static final int START_MASK = 0x1FFFFFFF;
1182 private static final int DIR_MASK = 0xC0000000;
1183 private static final int DIR_SHIFT = 30;
1184 private static final int TAB_MASK = 0x20000000;
1185
1186 private static final char FIRST_RIGHT_TO_LEFT = '\u0590';
1187
1188 /*
1189 * These are reused across calls to generate()
1190 */
1191 private byte[] mChdirs;
1192 private char[] mChs;
1193 private float[] mWidths;
1194 private Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
1195}