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