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