2 * Copyright (C) 2006 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 import android.graphics.Bitmap;
20 import android.graphics.Paint;
21 import com.android.internal.util.ArrayUtils;
22 import android.util.Log;
23 import android.text.style.LeadingMarginSpan;
24 import android.text.style.LineHeightSpan;
25 import android.text.style.MetricAffectingSpan;
26 import android.text.style.ReplacementSpan;
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>
41 public StaticLayout(CharSequence source, TextPaint paint,
43 Alignment align, float spacingmult, float spacingadd,
45 this(source, 0, source.length(), paint, width, align,
46 spacingmult, spacingadd, includepad);
49 public StaticLayout(CharSequence source, int bufstart, int bufend,
50 TextPaint paint, int outerwidth,
52 float spacingmult, float spacingadd,
54 this(source, bufstart, bufend, paint, outerwidth, align,
55 spacingmult, spacingadd, includepad, null, 0);
58 public StaticLayout(CharSequence source, int bufstart, int bufend,
59 TextPaint paint, int outerwidth,
61 float spacingmult, float spacingadd,
63 TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
64 super((ellipsize == null)
66 : (source instanceof Spanned)
67 ? new SpannedEllipsizer(source)
68 : new Ellipsizer(source),
69 paint, outerwidth, align, spacingmult, spacingadd);
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.
76 * This will break if the superclass constructor ever actually
77 * cares about the content instead of just holding the reference.
79 if (ellipsize != null) {
80 Ellipsizer e = (Ellipsizer) getText();
83 e.mWidth = ellipsizedWidth;
84 e.mMethod = ellipsize;
85 mEllipsizedWidth = ellipsizedWidth;
87 mColumns = COLUMNS_ELLIPSIZE;
89 mColumns = COLUMNS_NORMAL;
90 mEllipsizedWidth = outerwidth;
93 mLines = new int[ArrayUtils.idealIntArraySize(2 * mColumns)];
94 mLineDirections = new Directions[
95 ArrayUtils.idealIntArraySize(2 * mColumns)];
97 generate(source, bufstart, bufend, paint, outerwidth, align,
98 spacingmult, spacingadd, includepad, includepad,
99 ellipsize != null, ellipsizedWidth, ellipsize);
104 mFontMetricsInt = null;
107 /* package */ StaticLayout(boolean ellipsize) {
108 super(null, null, 0, null, 0, 0);
110 mColumns = COLUMNS_ELLIPSIZE;
111 mLines = new int[ArrayUtils.idealIntArraySize(2 * mColumns)];
112 mLineDirections = new Directions[
113 ArrayUtils.idealIntArraySize(2 * mColumns)];
116 /* package */ void generate(CharSequence source, int bufstart, int bufend,
117 TextPaint paint, int outerwidth,
119 float spacingmult, float spacingadd,
120 boolean includepad, boolean trackpad,
121 boolean breakOnlyAtSpaces,
122 float ellipsizedWidth, TextUtils.TruncateAt where) {
126 boolean needMultiply = (spacingmult != 1 || spacingadd != 0);
128 Paint.FontMetricsInt fm = mFontMetricsInt;
129 int[] choosehtv = null;
131 int end = TextUtils.indexOf(source, '\n', bufstart, bufend);
132 int bufsiz = end >= 0 ? end - bufstart : bufend - bufstart;
133 boolean first = true;
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)];
141 byte[] chdirs = mChdirs;
143 float[] widths = mWidths;
145 AlteredCharSequence alter = null;
146 Spanned spanned = null;
148 if (source instanceof Spanned)
149 spanned = (Spanned) source;
151 int DEFAULT_DIR = DIR_LEFT_TO_RIGHT; // XXX
153 for (int start = bufstart; start <= bufend; start = end) {
157 end = TextUtils.indexOf(source, '\n', start, bufend);
164 int firstWidthLineCount = 1;
165 int firstwidth = outerwidth;
166 int restwidth = outerwidth;
168 LineHeightSpan[] chooseht = null;
170 if (spanned != null) {
171 LeadingMarginSpan[] sp;
173 sp = spanned.getSpans(start, end, LeadingMarginSpan.class);
174 for (int i = 0; i < sp.length; i++) {
175 LeadingMarginSpan lms = sp[i];
176 firstwidth -= sp[i].getLeadingMargin(true);
177 restwidth -= sp[i].getLeadingMargin(false);
178 if (lms instanceof LeadingMarginSpan.LeadingMarginSpan2) {
179 firstWidthLineCount = ((LeadingMarginSpan.LeadingMarginSpan2)lms).getLeadingMarginLineCount();
183 chooseht = spanned.getSpans(start, end, LineHeightSpan.class);
185 if (chooseht.length != 0) {
186 if (choosehtv == null ||
187 choosehtv.length < chooseht.length) {
188 choosehtv = new int[ArrayUtils.idealIntArraySize(
192 for (int i = 0; i < chooseht.length; i++) {
193 int o = spanned.getSpanStart(chooseht[i]);
196 // starts in this layout, before the
199 choosehtv[i] = getLineTop(getLineForOffset(o));
201 // starts in this paragraph
209 if (end - start > chdirs.length) {
210 chdirs = new byte[ArrayUtils.idealByteArraySize(end - start)];
213 if (end - start > chs.length) {
214 chs = new char[ArrayUtils.idealCharArraySize(end - start)];
217 if ((end - start) * 2 > widths.length) {
218 widths = new float[ArrayUtils.idealIntArraySize((end - start) * 2)];
222 TextUtils.getChars(source, start, end, chs, 0);
223 final int n = end - start;
226 boolean altered = false;
227 int dir = DEFAULT_DIR; // XXX
229 for (int i = 0; i < n; i++) {
230 if (chs[i] >= FIRST_RIGHT_TO_LEFT) {
236 // Ensure that none of the underlying characters are treated
237 // as viable breakpoints, and that the entire run gets the
238 // same bidi direction.
240 if (source instanceof Spanned) {
241 Spanned sp = (Spanned) source;
242 ReplacementSpan[] spans = sp.getSpans(start, end, ReplacementSpan.class);
244 for (int y = 0; y < spans.length; y++) {
245 int a = sp.getSpanStart(spans[y]);
246 int b = sp.getSpanEnd(spans[y]);
248 for (int x = a; x < b; x++) {
249 chs[x - start] = '\uFFFC';
255 // XXX put override flags, etc. into chdirs
256 dir = bidi(dir, chs, chdirs, n, false);
258 // Do mirroring for right-to-left segments
260 for (int i = 0; i < n; i++) {
261 if (chdirs[i] == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
264 for (j = i; j < n; j++) {
266 Character.DIRECTIONALITY_RIGHT_TO_LEFT)
270 if (AndroidCharacter.mirror(chs, i, j - i))
282 alter = AlteredCharSequence.make(source, chs, start, end);
284 alter.update(chs, start, end);
291 int width = firstwidth;
298 int okascent = 0, okdescent = 0, oktop = 0, okbottom = 0;
302 int fitascent = 0, fitdescent = 0, fittop = 0, fitbottom = 0;
307 for (int i = start; i < end; i = next) {
311 next = spanned.nextSpanTransition(i, end,
315 if (spanned == null) {
316 paint.getTextWidths(sub, i, next, widths);
317 System.arraycopy(widths, 0, widths,
318 end - start + (i - start), next - i);
320 paint.getFontMetricsInt(fm);
322 mWorkPaint.baselineShift = 0;
324 Styled.getTextWidths(paint, mWorkPaint,
327 System.arraycopy(widths, 0, widths,
328 end - start + (i - start), next - i);
330 if (mWorkPaint.baselineShift < 0) {
331 fm.ascent += mWorkPaint.baselineShift;
332 fm.top += mWorkPaint.baselineShift;
334 fm.descent += mWorkPaint.baselineShift;
335 fm.bottom += mWorkPaint.baselineShift;
340 int fmbottom = fm.bottom;
341 int fmascent = fm.ascent;
342 int fmdescent = fm.descent;
345 StringBuilder sb = new StringBuilder();
346 for (int j = i; j < next; j++) {
347 sb.append(widths[j - start + (end - start)]);
351 Log.e("text", sb.toString());
354 for (int j = i; j < next; j++) {
355 char c = chs[j - start];
360 } else if (c == '\t') {
361 w = Layout.nextTab(sub, start, end, w, null);
363 } else if (c >= 0xD800 && c <= 0xDFFF && j + 1 < next) {
364 int emoji = Character.codePointAt(chs, j - start);
366 if (emoji >= MIN_EMOJI && emoji <= MAX_EMOJI) {
367 Bitmap bm = EMOJI_FACTORY.
368 getBitmapFromAndroidPua(emoji);
373 if (spanned == null) {
376 whichPaint = mWorkPaint;
379 float wid = (float) bm.getWidth() *
380 -whichPaint.ascent() /
387 w += widths[j - start + (end - start)];
390 w += widths[j - start + (end - start)];
393 w += widths[j - start + (end - start)];
396 // Log.e("text", "was " + before + " now " + w + " after " + c + " within " + width);
404 if (fmascent < fitascent)
405 fitascent = fmascent;
406 if (fmdescent > fitdescent)
407 fitdescent = fmdescent;
408 if (fmbottom > fitbottom)
409 fitbottom = fmbottom;
412 * From the Unicode Line Breaking Algorithm:
413 * (at least approximately)
415 * .,:; are class IS: breakpoints
416 * except when adjacent to digits
417 * / is class SY: a breakpoint
418 * except when followed by a digit.
419 * - is class HY: a breakpoint
420 * except when followed by a digit.
422 * Ideographs are class ID: breakpoints when adjacent,
423 * except for NS (non-starters), which can be broken
424 * after but not before.
427 if (c == ' ' || c == '\t' ||
428 ((c == '.' || c == ',' || c == ':' || c == ';') &&
429 (j - 1 < here || !Character.isDigit(chs[j - 1 - start])) &&
430 (j + 1 >= next || !Character.isDigit(chs[j + 1 - start]))) ||
431 ((c == '/' || c == '-') &&
432 (j + 1 >= next || !Character.isDigit(chs[j + 1 - start]))) ||
433 (c >= FIRST_CJK && isIdeographic(c, true) &&
434 j + 1 < next && isIdeographic(chs[j + 1 - start], false))) {
440 if (fitascent < okascent)
441 okascent = fitascent;
442 if (fitdescent > okdescent)
443 okdescent = fitdescent;
444 if (fitbottom > okbottom)
445 okbottom = fitbottom;
447 } else if (breakOnlyAtSpaces) {
449 // Log.e("text", "output ok " + here + " to " +ok);
451 while (ok < next && chs[ok - start] == ' ') {
457 okascent, okdescent, oktop, okbottom,
459 spacingmult, spacingadd, chooseht,
461 needMultiply, start, chdirs, dir, easy,
462 ok == bufend, includepad, trackpad,
463 widths, start, end - start,
464 where, ellipsizedWidth, okwidth,
469 // Act like it fit even though it didn't.
476 if (fmascent < fitascent)
477 fitascent = fmascent;
478 if (fmdescent > fitdescent)
479 fitdescent = fmdescent;
480 if (fmbottom > fitbottom)
481 fitbottom = fmbottom;
485 // Log.e("text", "output ok " + here + " to " +ok);
487 while (ok < next && chs[ok - start] == ' ') {
493 okascent, okdescent, oktop, okbottom,
495 spacingmult, spacingadd, chooseht,
497 needMultiply, start, chdirs, dir, easy,
498 ok == bufend, includepad, trackpad,
499 widths, start, end - start,
500 where, ellipsizedWidth, okwidth,
504 } else if (fit != here) {
505 // Log.e("text", "output fit " + here + " to " +fit);
508 fitascent, fitdescent,
511 spacingmult, spacingadd, chooseht,
513 needMultiply, start, chdirs, dir, easy,
514 fit == bufend, includepad, trackpad,
515 widths, start, end - start,
516 where, ellipsizedWidth, fitwidth,
521 // Log.e("text", "output one " + here + " to " +(here + 1));
522 measureText(paint, mWorkPaint,
523 source, here, here + 1, fm, tab,
528 fm.ascent, fm.descent,
531 spacingmult, spacingadd, chooseht,
533 needMultiply, start, chdirs, dir, easy,
534 here + 1 == bufend, includepad,
536 widths, start, end - start,
537 where, ellipsizedWidth,
538 widths[here - start], paint);
544 j = next = here; // must remeasure
546 j = here - 1; // continue looping
551 fitascent = fitdescent = fittop = fitbottom = 0;
552 okascent = okdescent = oktop = okbottom = 0;
554 if (--firstWidthLineCount <= 0) {
562 if ((fittop | fitbottom | fitdescent | fitascent) == 0) {
563 paint.getFontMetricsInt(fm);
566 fitbottom = fm.bottom;
567 fitascent = fm.ascent;
568 fitdescent = fm.descent;
571 // Log.e("text", "output rest " + here + " to " + end);
574 here, end, fitascent, fitdescent,
577 spacingmult, spacingadd, chooseht,
579 needMultiply, start, chdirs, dir, easy,
580 end == bufend, includepad, trackpad,
581 widths, start, end - start,
582 where, ellipsizedWidth, w, paint);
591 if (bufend == bufstart || source.charAt(bufend - 1) == '\n') {
592 // Log.e("text", "output last " + bufend);
594 paint.getFontMetricsInt(fm);
597 bufend, bufend, fm.ascent, fm.descent,
600 spacingmult, spacingadd, null,
602 needMultiply, bufend, chdirs, DEFAULT_DIR, true,
603 true, includepad, trackpad,
605 where, ellipsizedWidth, 0, paint);
610 * Runs the unicode bidi algorithm on the first n chars in chs, returning
611 * the char dirs in chInfo and the base line direction of the first
614 * XXX change result from dirs to levels
616 * @param dir the direction flag, either DIR_REQUEST_LTR,
617 * DIR_REQUEST_RTL, DIR_REQUEST_DEFAULT_LTR, or DIR_REQUEST_DEFAULT_RTL.
618 * @param chs the text to examine
619 * @param chInfo on input, if hasInfo is true, override and other flags
620 * representing out-of-band embedding information. On output, the generated
622 * @param n the length of the text/information in chs and chInfo
623 * @param hasInfo true if chInfo has input information, otherwise the
624 * input data in chInfo is ignored.
625 * @return the resolved direction level of the first paragraph, either
626 * DIR_LEFT_TO_RIGHT or DIR_RIGHT_TO_LEFT.
628 /* package */ static int bidi(int dir, char[] chs, byte[] chInfo, int n,
631 AndroidCharacter.getDirectionalities(chs, chInfo, n);
634 * Determine primary paragraph direction if not specified
636 if (dir != DIR_REQUEST_LTR && dir != DIR_REQUEST_RTL) {
638 dir = dir >= 0 ? DIR_LEFT_TO_RIGHT : DIR_RIGHT_TO_LEFT;
639 for (int j = 0; j < n; j++) {
642 if (d == Character.DIRECTIONALITY_LEFT_TO_RIGHT) {
643 dir = DIR_LEFT_TO_RIGHT;
646 if (d == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
647 dir = DIR_RIGHT_TO_LEFT;
653 final byte SOR = dir == DIR_LEFT_TO_RIGHT ?
654 Character.DIRECTIONALITY_LEFT_TO_RIGHT :
655 Character.DIRECTIONALITY_RIGHT_TO_LEFT;
658 * XXX Explicit overrides should go here
662 * Weak type resolution
665 // dump(chdirs, n, "initial");
667 // W1 non spacing marks
668 for (int j = 0; j < n; j++) {
669 if (chInfo[j] == Character.NON_SPACING_MARK) {
673 chInfo[j] = chInfo[j - 1];
677 // dump(chdirs, n, "W1");
679 // W2 european numbers
681 for (int j = 0; j < n; j++) {
684 if (d == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
685 d == Character.DIRECTIONALITY_RIGHT_TO_LEFT ||
686 d == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC)
688 else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER) {
690 Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC)
691 chInfo[j] = Character.DIRECTIONALITY_ARABIC_NUMBER;
695 // dump(chdirs, n, "W2");
698 for (int j = 0; j < n; j++) {
699 if (chInfo[j] == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC)
700 chInfo[j] = Character.DIRECTIONALITY_RIGHT_TO_LEFT;
703 // dump(chdirs, n, "W3");
705 // W4 single separator between numbers
706 for (int j = 1; j < n - 1; j++) {
708 byte prev = chInfo[j - 1];
709 byte next = chInfo[j + 1];
711 if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_SEPARATOR) {
712 if (prev == Character.DIRECTIONALITY_EUROPEAN_NUMBER &&
713 next == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
714 chInfo[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
715 } else if (d == Character.DIRECTIONALITY_COMMON_NUMBER_SEPARATOR) {
716 if (prev == Character.DIRECTIONALITY_EUROPEAN_NUMBER &&
717 next == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
718 chInfo[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
719 if (prev == Character.DIRECTIONALITY_ARABIC_NUMBER &&
720 next == Character.DIRECTIONALITY_ARABIC_NUMBER)
721 chInfo[j] = Character.DIRECTIONALITY_ARABIC_NUMBER;
725 // dump(chdirs, n, "W4");
727 // W5 european number terminators
728 boolean adjacent = false;
729 for (int j = 0; j < n; j++) {
732 if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
734 else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR && adjacent)
735 chInfo[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
740 //dump(chdirs, n, "W5");
742 // W5 european number terminators part 2,
743 // W6 separators and terminators
745 for (int j = n - 1; j >= 0; j--) {
748 if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
750 else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR) {
752 chInfo[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
754 chInfo[j] = Character.DIRECTIONALITY_OTHER_NEUTRALS;
759 if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_SEPARATOR ||
760 d == Character.DIRECTIONALITY_COMMON_NUMBER_SEPARATOR ||
761 d == Character.DIRECTIONALITY_PARAGRAPH_SEPARATOR ||
762 d == Character.DIRECTIONALITY_SEGMENT_SEPARATOR)
763 chInfo[j] = Character.DIRECTIONALITY_OTHER_NEUTRALS;
767 // dump(chdirs, n, "W6");
769 // W7 strong direction of european numbers
771 for (int j = 0; j < n; j++) {
775 d == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
776 d == Character.DIRECTIONALITY_RIGHT_TO_LEFT)
779 if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
783 // dump(chdirs, n, "W7");
787 for (int j = 0; j < n; j++) {
790 if (d == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
791 d == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
793 } else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER ||
794 d == Character.DIRECTIONALITY_ARABIC_NUMBER) {
795 cur = Character.DIRECTIONALITY_RIGHT_TO_LEFT;
800 for (k = j + 1; k < n; k++) {
803 if (dd == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
804 dd == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
807 if (dd == Character.DIRECTIONALITY_EUROPEAN_NUMBER ||
808 dd == Character.DIRECTIONALITY_ARABIC_NUMBER) {
809 dd = Character.DIRECTIONALITY_RIGHT_TO_LEFT;
814 for (int y = j; y < k; y++) {
825 // dump(chdirs, n, "final");
827 // extra: enforce that all tabs and surrogate characters go the
829 // TODO: actually do directions right for surrogates
831 for (int j = 0; j < n; j++) {
834 if (c == '\t' || (c >= 0xD800 && c <= 0xDFFF)) {
842 private static final char FIRST_CJK = '\u2E80';
844 * Returns true if the specified character is one of those specified
845 * as being Ideographic (class ID) by the Unicode Line Breaking Algorithm
846 * (http://www.unicode.org/unicode/reports/tr14/), and is therefore OK
847 * to break between a pair of.
849 * @param includeNonStarters also return true for category NS
850 * (non-starters), which can be broken
851 * after but not before.
853 private static final boolean isIdeographic(char c, boolean includeNonStarters) {
854 if (c >= '\u2E80' && c <= '\u2FFF') {
855 return true; // CJK, KANGXI RADICALS, DESCRIPTION SYMBOLS
858 return true; // IDEOGRAPHIC SPACE
860 if (c >= '\u3040' && c <= '\u309F') {
861 if (!includeNonStarters) {
863 case '\u3041': // # HIRAGANA LETTER SMALL A
864 case '\u3043': // # HIRAGANA LETTER SMALL I
865 case '\u3045': // # HIRAGANA LETTER SMALL U
866 case '\u3047': // # HIRAGANA LETTER SMALL E
867 case '\u3049': // # HIRAGANA LETTER SMALL O
868 case '\u3063': // # HIRAGANA LETTER SMALL TU
869 case '\u3083': // # HIRAGANA LETTER SMALL YA
870 case '\u3085': // # HIRAGANA LETTER SMALL YU
871 case '\u3087': // # HIRAGANA LETTER SMALL YO
872 case '\u308E': // # HIRAGANA LETTER SMALL WA
873 case '\u3095': // # HIRAGANA LETTER SMALL KA
874 case '\u3096': // # HIRAGANA LETTER SMALL KE
875 case '\u309B': // # KATAKANA-HIRAGANA VOICED SOUND MARK
876 case '\u309C': // # KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK
877 case '\u309D': // # HIRAGANA ITERATION MARK
878 case '\u309E': // # HIRAGANA VOICED ITERATION MARK
882 return true; // Hiragana (except small characters)
884 if (c >= '\u30A0' && c <= '\u30FF') {
885 if (!includeNonStarters) {
887 case '\u30A0': // # KATAKANA-HIRAGANA DOUBLE HYPHEN
888 case '\u30A1': // # KATAKANA LETTER SMALL A
889 case '\u30A3': // # KATAKANA LETTER SMALL I
890 case '\u30A5': // # KATAKANA LETTER SMALL U
891 case '\u30A7': // # KATAKANA LETTER SMALL E
892 case '\u30A9': // # KATAKANA LETTER SMALL O
893 case '\u30C3': // # KATAKANA LETTER SMALL TU
894 case '\u30E3': // # KATAKANA LETTER SMALL YA
895 case '\u30E5': // # KATAKANA LETTER SMALL YU
896 case '\u30E7': // # KATAKANA LETTER SMALL YO
897 case '\u30EE': // # KATAKANA LETTER SMALL WA
898 case '\u30F5': // # KATAKANA LETTER SMALL KA
899 case '\u30F6': // # KATAKANA LETTER SMALL KE
900 case '\u30FB': // # KATAKANA MIDDLE DOT
901 case '\u30FC': // # KATAKANA-HIRAGANA PROLONGED SOUND MARK
902 case '\u30FD': // # KATAKANA ITERATION MARK
903 case '\u30FE': // # KATAKANA VOICED ITERATION MARK
907 return true; // Katakana (except small characters)
909 if (c >= '\u3400' && c <= '\u4DB5') {
910 return true; // CJK UNIFIED IDEOGRAPHS EXTENSION A
912 if (c >= '\u4E00' && c <= '\u9FBB') {
913 return true; // CJK UNIFIED IDEOGRAPHS
915 if (c >= '\uF900' && c <= '\uFAD9') {
916 return true; // CJK COMPATIBILITY IDEOGRAPHS
918 if (c >= '\uA000' && c <= '\uA48F') {
919 return true; // YI SYLLABLES
921 if (c >= '\uA490' && c <= '\uA4CF') {
922 return true; // YI RADICALS
924 if (c >= '\uFE62' && c <= '\uFE66') {
925 return true; // SMALL PLUS SIGN to SMALL EQUALS SIGN
927 if (c >= '\uFF10' && c <= '\uFF19') {
928 return true; // WIDE DIGITS
935 private static void dump(byte[] data, int count, String label) {
937 System.out.print(label);
939 for (int i = 0; i < count; i++)
940 System.out.print(" " + data[i]);
942 System.out.println();
947 private static int getFit(TextPaint paint,
949 CharSequence text, int start, int end,
951 int high = end + 1, low = start - 1, guess;
953 while (high - low > 1) {
954 guess = (high + low) / 2;
956 if (measureText(paint, workPaint,
957 text, start, guess, null, true, null) > wid)
969 private int out(CharSequence text, int start, int end,
970 int above, int below, int top, int bottom, int v,
971 float spacingmult, float spacingadd,
972 LineHeightSpan[] chooseht, int[] choosehtv,
973 Paint.FontMetricsInt fm, boolean tab,
974 boolean needMultiply, int pstart, byte[] chdirs,
975 int dir, boolean easy, boolean last,
976 boolean includepad, boolean trackpad,
977 float[] widths, int widstart, int widoff,
978 TextUtils.TruncateAt ellipsize, float ellipsiswidth,
979 float textwidth, TextPaint paint) {
981 int off = j * mColumns;
982 int want = off + mColumns + TOP;
983 int[] lines = mLines;
985 // Log.e("text", "line " + start + " to " + end + (last ? "===" : ""));
987 if (want >= lines.length) {
988 int nlen = ArrayUtils.idealIntArraySize(want + 1);
989 int[] grow = new int[nlen];
990 System.arraycopy(lines, 0, grow, 0, lines.length);
994 Directions[] grow2 = new Directions[nlen];
995 System.arraycopy(mLineDirections, 0, grow2, 0,
996 mLineDirections.length);
997 mLineDirections = grow2;
1000 if (chooseht != null) {
1006 for (int i = 0; i < chooseht.length; i++) {
1007 if (chooseht[i] instanceof LineHeightSpan.WithDensity) {
1008 ((LineHeightSpan.WithDensity) chooseht[i]).
1009 chooseHeight(text, start, end, choosehtv[i], v, fm, paint);
1012 chooseht[i].chooseHeight(text, start, end, choosehtv[i], v, fm);
1024 mTopPadding = top - above;
1033 mBottomPadding = bottom - below;
1044 double ex = (below - above) * (spacingmult - 1) + spacingadd;
1046 extra = (int)(ex + 0.5);
1048 extra = -(int)(-ex + 0.5);
1054 lines[off + START] = start;
1055 lines[off + TOP] = v;
1056 lines[off + DESCENT] = below + extra;
1058 v += (below - above) + extra;
1059 lines[off + mColumns + START] = end;
1060 lines[off + mColumns + TOP] = v;
1063 lines[off + TAB] |= TAB_MASK;
1066 lines[off + DIR] |= dir << DIR_SHIFT;
1068 int cur = Character.DIRECTIONALITY_LEFT_TO_RIGHT;
1072 for (int k = start; k < end; k++) {
1073 if (chdirs[k - pstart] != cur) {
1075 cur = chdirs[k - pstart];
1080 Directions linedirs;
1083 linedirs = DIRS_ALL_LEFT_TO_RIGHT;
1085 short[] ld = new short[count + 1];
1087 cur = Character.DIRECTIONALITY_LEFT_TO_RIGHT;
1091 for (int k = start; k < end; k++) {
1092 if (chdirs[k - pstart] != cur) {
1093 // XXX check to make sure we don't
1095 ld[count++] = (short) (k - here);
1096 cur = chdirs[k - pstart];
1101 ld[count] = (short) (end - here);
1103 if (count == 1 && ld[0] == 0) {
1104 linedirs = DIRS_ALL_RIGHT_TO_LEFT;
1106 linedirs = new Directions(ld);
1110 mLineDirections[j] = linedirs;
1112 // If ellipsize is in marquee mode, do not apply ellipsis on the first line
1113 if (ellipsize != null && (ellipsize != TextUtils.TruncateAt.MARQUEE || j != 0)) {
1114 calculateEllipsis(start, end, widths, widstart, widoff,
1115 ellipsiswidth, ellipsize, j,
1124 private void calculateEllipsis(int linestart, int lineend,
1125 float[] widths, int widstart, int widoff,
1126 float avail, TextUtils.TruncateAt where,
1127 int line, float textwidth, TextPaint paint) {
1128 int len = lineend - linestart;
1130 if (textwidth <= avail) {
1132 mLines[mColumns * line + ELLIPSIS_START] = 0;
1133 mLines[mColumns * line + ELLIPSIS_COUNT] = 0;
1137 float ellipsiswid = paint.measureText("\u2026");
1138 int ellipsisStart, ellipsisCount;
1140 if (where == TextUtils.TruncateAt.START) {
1144 for (i = len; i >= 0; i--) {
1145 float w = widths[i - 1 + linestart - widstart + widoff];
1147 if (w + sum + ellipsiswid > avail) {
1156 } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE) {
1160 for (i = 0; i < len; i++) {
1161 float w = widths[i + linestart - widstart + widoff];
1163 if (w + sum + ellipsiswid > avail) {
1171 ellipsisCount = len - i;
1172 } else /* where = TextUtils.TruncateAt.MIDDLE */ {
1173 float lsum = 0, rsum = 0;
1174 int left = 0, right = len;
1176 float ravail = (avail - ellipsiswid) / 2;
1177 for (right = len; right >= 0; right--) {
1178 float w = widths[right - 1 + linestart - widstart + widoff];
1180 if (w + rsum > ravail) {
1187 float lavail = avail - ellipsiswid - rsum;
1188 for (left = 0; left < right; left++) {
1189 float w = widths[left + linestart - widstart + widoff];
1191 if (w + lsum > lavail) {
1198 ellipsisStart = left;
1199 ellipsisCount = right - left;
1202 mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
1203 mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
1206 // Override the baseclass so we can directly access our members,
1207 // rather than relying on member functions.
1208 // The logic mirrors that of Layout.getLineForVertical
1209 // FIXME: It may be faster to do a linear search for layouts without many lines.
1210 public int getLineForVertical(int vertical) {
1211 int high = mLineCount;
1214 int[] lines = mLines;
1215 while (high - low > 1) {
1216 guess = (high + low) >> 1;
1217 if (lines[mColumns * guess + TOP] > vertical){
1230 public int getLineCount() {
1234 public int getLineTop(int line) {
1235 return mLines[mColumns * line + TOP];
1238 public int getLineDescent(int line) {
1239 return mLines[mColumns * line + DESCENT];
1242 public int getLineStart(int line) {
1243 return mLines[mColumns * line + START] & START_MASK;
1246 public int getParagraphDirection(int line) {
1247 return mLines[mColumns * line + DIR] >> DIR_SHIFT;
1250 public boolean getLineContainsTab(int line) {
1251 return (mLines[mColumns * line + TAB] & TAB_MASK) != 0;
1254 public final Directions getLineDirections(int line) {
1255 return mLineDirections[line];
1258 public int getTopPadding() {
1262 public int getBottomPadding() {
1263 return mBottomPadding;
1267 public int getEllipsisCount(int line) {
1268 if (mColumns < COLUMNS_ELLIPSIZE) {
1272 return mLines[mColumns * line + ELLIPSIS_COUNT];
1276 public int getEllipsisStart(int line) {
1277 if (mColumns < COLUMNS_ELLIPSIZE) {
1281 return mLines[mColumns * line + ELLIPSIS_START];
1285 public int getEllipsizedWidth() {
1286 return mEllipsizedWidth;
1289 private int mLineCount;
1290 private int mTopPadding, mBottomPadding;
1291 private int mColumns;
1292 private int mEllipsizedWidth;
1294 private static final int COLUMNS_NORMAL = 3;
1295 private static final int COLUMNS_ELLIPSIZE = 5;
1296 private static final int START = 0;
1297 private static final int DIR = START;
1298 private static final int TAB = START;
1299 private static final int TOP = 1;
1300 private static final int DESCENT = 2;
1301 private static final int ELLIPSIS_START = 3;
1302 private static final int ELLIPSIS_COUNT = 4;
1304 private int[] mLines;
1305 private Directions[] mLineDirections;
1307 private static final int START_MASK = 0x1FFFFFFF;
1308 private static final int DIR_MASK = 0xC0000000;
1309 private static final int DIR_SHIFT = 30;
1310 private static final int TAB_MASK = 0x20000000;
1312 private static final char FIRST_RIGHT_TO_LEFT = '\u0590';
1315 * These are reused across calls to generate()
1317 private byte[] mChdirs;
1318 private char[] mChs;
1319 private float[] mWidths;
1320 private Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();