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