StaticLayout.java revision 74d31ef2b2c42b54fa1f7cf94ea955ea67ab69a0
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.TabStopSpan;
27import android.text.style.LeadingMarginSpan.LeadingMarginSpan2;
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        mMeasured = MeasuredText.obtain();
100
101        generate(source, bufstart, bufend, paint, outerwidth, align,
102                 spacingmult, spacingadd, includepad, includepad,
103                 ellipsize != null, ellipsizedWidth, ellipsize);
104
105        mMeasured = MeasuredText.recycle(mMeasured);
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        mMeasured = MeasuredText.obtain();
117    }
118
119    /* package */ void generate(CharSequence source, int bufstart, int bufend,
120                        TextPaint paint, int outerwidth,
121                        Alignment align,
122                        float spacingmult, float spacingadd,
123                        boolean includepad, boolean trackpad,
124                        boolean breakOnlyAtSpaces,
125                        float ellipsizedWidth, TextUtils.TruncateAt where) {
126        mLineCount = 0;
127
128        int v = 0;
129        boolean needMultiply = (spacingmult != 1 || spacingadd != 0);
130
131        Paint.FontMetricsInt fm = mFontMetricsInt;
132        int[] choosehtv = null;
133
134        MeasuredText measured = mMeasured;
135
136        Spanned spanned = null;
137        if (source instanceof Spanned)
138            spanned = (Spanned) source;
139
140        int DEFAULT_DIR = DIR_LEFT_TO_RIGHT; // XXX
141
142        int paraEnd;
143        for (int paraStart = bufstart; paraStart <= bufend; paraStart = paraEnd) {
144            paraEnd = TextUtils.indexOf(source, '\n', paraStart, bufend);
145            if (paraEnd < 0)
146                paraEnd = bufend;
147            else
148                paraEnd++;
149            int paraLen = paraEnd - paraStart;
150
151            int firstWidthLineLimit = mLineCount + 1;
152            int firstwidth = outerwidth;
153            int restwidth = outerwidth;
154
155            LineHeightSpan[] chooseht = null;
156
157            if (spanned != null) {
158                LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
159                        LeadingMarginSpan.class);
160                for (int i = 0; i < sp.length; i++) {
161                    LeadingMarginSpan lms = sp[i];
162                    firstwidth -= sp[i].getLeadingMargin(true);
163                    restwidth -= sp[i].getLeadingMargin(false);
164
165                    // LeadingMarginSpan2 is odd.  The count affects all
166                    // leading margin spans, not just this particular one,
167                    // and start from the top of the span, not the top of the
168                    // paragraph.
169                    if (lms instanceof LeadingMarginSpan2) {
170                        LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
171                        int lmsFirstLine = getLineForOffset(spanned.getSpanStart(lms2));
172                        firstWidthLineLimit = lmsFirstLine +
173                            lms2.getLeadingMarginLineCount();
174                    }
175                }
176
177                chooseht = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
178
179                if (chooseht.length != 0) {
180                    if (choosehtv == null ||
181                        choosehtv.length < chooseht.length) {
182                        choosehtv = new int[ArrayUtils.idealIntArraySize(
183                                            chooseht.length)];
184                    }
185
186                    for (int i = 0; i < chooseht.length; i++) {
187                        int o = spanned.getSpanStart(chooseht[i]);
188
189                        if (o < paraStart) {
190                            // starts in this layout, before the
191                            // current paragraph
192
193                            choosehtv[i] = getLineTop(getLineForOffset(o));
194                        } else {
195                            // starts in this paragraph
196
197                            choosehtv[i] = v;
198                        }
199                    }
200                }
201            }
202
203            measured.setPara(source, paraStart, paraEnd, DIR_REQUEST_DEFAULT_LTR);
204            char[] chs = measured.mChars;
205            float[] widths = measured.mWidths;
206            byte[] chdirs = measured.mLevels;
207            int dir = measured.mDir;
208            boolean easy = measured.mEasy;
209
210            CharSequence sub = source;
211
212            int width = firstwidth;
213
214            float w = 0;
215            int here = paraStart;
216
217            int ok = paraStart;
218            float okwidth = w;
219            int okascent = 0, okdescent = 0, oktop = 0, okbottom = 0;
220
221            int fit = paraStart;
222            float fitwidth = w;
223            int fitascent = 0, fitdescent = 0, fittop = 0, fitbottom = 0;
224
225            boolean hasTabOrEmoji = false;
226            boolean hasTab = false;
227            TabStops tabStops = null;
228
229            for (int spanStart = paraStart, spanEnd = spanStart, nextSpanStart;
230                    spanStart < paraEnd; spanStart = nextSpanStart) {
231
232                if (spanStart == spanEnd) {
233                    if (spanned == null)
234                        spanEnd = paraEnd;
235                    else
236                        spanEnd = spanned.nextSpanTransition(spanStart, paraEnd,
237                                MetricAffectingSpan.class);
238
239                    int spanLen = spanEnd - spanStart;
240                    if (spanned == null) {
241                        measured.addStyleRun(paint, spanLen, fm);
242                    } else {
243                        MetricAffectingSpan[] spans =
244                            spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class);
245                        measured.addStyleRun(paint, spans, spanLen, fm);
246                    }
247                }
248
249                nextSpanStart = spanEnd;
250                int startInPara = spanStart - paraStart;
251                int endInPara = spanEnd - paraStart;
252
253                int fmtop = fm.top;
254                int fmbottom = fm.bottom;
255                int fmascent = fm.ascent;
256                int fmdescent = fm.descent;
257
258                for (int j = spanStart; j < spanEnd; j++) {
259                    char c = chs[j - paraStart];
260                    float before = w;
261
262                    if (c == '\n') {
263                        ;
264                    } else if (c == '\t') {
265                        if (hasTab == false) {
266                            hasTab = true;
267                            hasTabOrEmoji = true;
268                            if (spanned != null) {
269                                // First tab this para, check for tabstops
270                                TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
271                                        paraEnd, TabStopSpan.class);
272                                if (spans.length > 0) {
273                                    tabStops = new TabStops(TAB_INCREMENT, spans);
274                                }
275                            }
276                        }
277                        if (tabStops != null) {
278                            w = tabStops.nextTab(w);
279                        } else {
280                            w = TabStops.nextDefaultStop(w, TAB_INCREMENT);
281                        }
282                    } else if (c >= 0xD800 && c <= 0xDFFF && j + 1 < spanEnd) {
283                        int emoji = Character.codePointAt(chs, j - paraStart);
284
285                        if (emoji >= MIN_EMOJI && emoji <= MAX_EMOJI) {
286                            Bitmap bm = EMOJI_FACTORY.
287                                getBitmapFromAndroidPua(emoji);
288
289                            if (bm != null) {
290                                Paint whichPaint;
291
292                                if (spanned == null) {
293                                    whichPaint = paint;
294                                } else {
295                                    whichPaint = mWorkPaint;
296                                }
297
298                                float wid = bm.getWidth() *
299                                            -whichPaint.ascent() /
300                                            bm.getHeight();
301
302                                w += wid;
303                                hasTabOrEmoji = true;
304                                j++;
305                            } else {
306                                w += widths[j - paraStart];
307                            }
308                        } else {
309                            w += widths[j - paraStart];
310                        }
311                    } else {
312                        w += widths[j - paraStart];
313                    }
314
315                    // Log.e("text", "was " + before + " now " + w + " after " + c + " within " + width);
316
317                    if (w <= width) {
318                        fitwidth = w;
319                        fit = j + 1;
320
321                        if (fmtop < fittop)
322                            fittop = fmtop;
323                        if (fmascent < fitascent)
324                            fitascent = fmascent;
325                        if (fmdescent > fitdescent)
326                            fitdescent = fmdescent;
327                        if (fmbottom > fitbottom)
328                            fitbottom = fmbottom;
329
330                        /*
331                         * From the Unicode Line Breaking Algorithm:
332                         * (at least approximately)
333                         *
334                         * .,:; are class IS: breakpoints
335                         *      except when adjacent to digits
336                         * /    is class SY: a breakpoint
337                         *      except when followed by a digit.
338                         * -    is class HY: a breakpoint
339                         *      except when followed by a digit.
340                         *
341                         * Ideographs are class ID: breakpoints when adjacent,
342                         * except for NS (non-starters), which can be broken
343                         * after but not before.
344                         */
345
346                        if (c == ' ' || c == '\t' ||
347                            ((c == '.'  || c == ',' || c == ':' || c == ';') &&
348                             (j - 1 < here || !Character.isDigit(chs[j - 1 - paraStart])) &&
349                             (j + 1 >= spanEnd || !Character.isDigit(chs[j + 1 - paraStart]))) ||
350                            ((c == '/' || c == '-') &&
351                             (j + 1 >= spanEnd || !Character.isDigit(chs[j + 1 - paraStart]))) ||
352                            (c >= FIRST_CJK && isIdeographic(c, true) &&
353                             j + 1 < spanEnd && isIdeographic(chs[j + 1 - paraStart], false))) {
354                            okwidth = w;
355                            ok = j + 1;
356
357                            if (fittop < oktop)
358                                oktop = fittop;
359                            if (fitascent < okascent)
360                                okascent = fitascent;
361                            if (fitdescent > okdescent)
362                                okdescent = fitdescent;
363                            if (fitbottom > okbottom)
364                                okbottom = fitbottom;
365                        }
366                    } else if (breakOnlyAtSpaces) {
367                        if (ok != here) {
368                            // Log.e("text", "output ok " + here + " to " +ok);
369
370                            while (ok < spanEnd && chs[ok - paraStart] == ' ') {
371                                ok++;
372                            }
373
374                            v = out(source,
375                                    here, ok,
376                                    okascent, okdescent, oktop, okbottom,
377                                    v,
378                                    spacingmult, spacingadd, chooseht,
379                                    choosehtv, fm, hasTabOrEmoji,
380                                    needMultiply, paraStart, chdirs, dir, easy,
381                                    ok == bufend, includepad, trackpad,
382                                    chs, widths, here - paraStart,
383                                    where, ellipsizedWidth, okwidth,
384                                    paint);
385
386                            here = ok;
387                        } else {
388                            // Act like it fit even though it didn't.
389
390                            fitwidth = w;
391                            fit = j + 1;
392
393                            if (fmtop < fittop)
394                                fittop = fmtop;
395                            if (fmascent < fitascent)
396                                fitascent = fmascent;
397                            if (fmdescent > fitdescent)
398                                fitdescent = fmdescent;
399                            if (fmbottom > fitbottom)
400                                fitbottom = fmbottom;
401                        }
402                    } else {
403                        if (ok != here) {
404                            // Log.e("text", "output ok " + here + " to " +ok);
405
406                            while (ok < spanEnd && chs[ok - paraStart] == ' ') {
407                                ok++;
408                            }
409
410                            v = out(source,
411                                    here, ok,
412                                    okascent, okdescent, oktop, okbottom,
413                                    v,
414                                    spacingmult, spacingadd, chooseht,
415                                    choosehtv, fm, hasTabOrEmoji,
416                                    needMultiply, paraStart, chdirs, dir, easy,
417                                    ok == bufend, includepad, trackpad,
418                                    chs, widths, here - paraStart,
419                                    where, ellipsizedWidth, okwidth,
420                                    paint);
421
422                            here = ok;
423                        } else if (fit != here) {
424                            // Log.e("text", "output fit " + here + " to " +fit);
425                            v = out(source,
426                                    here, fit,
427                                    fitascent, fitdescent,
428                                    fittop, fitbottom,
429                                    v,
430                                    spacingmult, spacingadd, chooseht,
431                                    choosehtv, fm, hasTabOrEmoji,
432                                    needMultiply, paraStart, chdirs, dir, easy,
433                                    fit == bufend, includepad, trackpad,
434                                    chs, widths, here - paraStart,
435                                    where, ellipsizedWidth, fitwidth,
436                                    paint);
437
438                            here = fit;
439                        } else {
440                            // Log.e("text", "output one " + here + " to " +(here + 1));
441                            // XXX not sure why the existing fm wasn't ok.
442                            // measureText(paint, mWorkPaint,
443                            //             source, here, here + 1, fm, tab,
444                            //             null);
445
446                            v = out(source,
447                                    here, here+1,
448                                    fm.ascent, fm.descent,
449                                    fm.top, fm.bottom,
450                                    v,
451                                    spacingmult, spacingadd, chooseht,
452                                    choosehtv, fm, hasTabOrEmoji,
453                                    needMultiply, paraStart, chdirs, dir, easy,
454                                    here + 1 == bufend, includepad,
455                                    trackpad,
456                                    chs, widths, here - paraStart,
457                                    where, ellipsizedWidth,
458                                    widths[here - paraStart], paint);
459
460                            here = here + 1;
461                        }
462
463                        if (here < spanStart) {
464                            // didn't output all the text for this span
465                            // we've measured the raw widths, though, so
466                            // just reset the start point
467                            j = nextSpanStart = here;
468                        } else {
469                            j = here - 1;    // continue looping
470                        }
471
472                        ok = fit = here;
473                        w = 0;
474                        fitascent = fitdescent = fittop = fitbottom = 0;
475                        okascent = okdescent = oktop = okbottom = 0;
476
477                        if (--firstWidthLineLimit <= 0) {
478                            width = restwidth;
479                        }
480                    }
481                }
482            }
483
484            if (paraEnd != here) {
485                if ((fittop | fitbottom | fitdescent | fitascent) == 0) {
486                    paint.getFontMetricsInt(fm);
487
488                    fittop = fm.top;
489                    fitbottom = fm.bottom;
490                    fitascent = fm.ascent;
491                    fitdescent = fm.descent;
492                }
493
494                // Log.e("text", "output rest " + here + " to " + end);
495
496                v = out(source,
497                        here, paraEnd, fitascent, fitdescent,
498                        fittop, fitbottom,
499                        v,
500                        spacingmult, spacingadd, chooseht,
501                        choosehtv, fm, hasTabOrEmoji,
502                        needMultiply, paraStart, chdirs, dir, easy,
503                        paraEnd == bufend, includepad, trackpad,
504                        chs, widths, here - paraStart,
505                        where, ellipsizedWidth, w, paint);
506            }
507
508            paraStart = paraEnd;
509
510            if (paraEnd == bufend)
511                break;
512        }
513
514        if (bufend == bufstart || source.charAt(bufend - 1) == '\n') {
515            // Log.e("text", "output last " + bufend);
516
517            paint.getFontMetricsInt(fm);
518
519            v = out(source,
520                    bufend, bufend, fm.ascent, fm.descent,
521                    fm.top, fm.bottom,
522                    v,
523                    spacingmult, spacingadd, null,
524                    null, fm, false,
525                    needMultiply, bufend, null, DEFAULT_DIR, true,
526                    true, includepad, trackpad,
527                    null, null, bufstart,
528                    where, ellipsizedWidth, 0, paint);
529        }
530    }
531
532    private static final char FIRST_CJK = '\u2E80';
533    /**
534     * Returns true if the specified character is one of those specified
535     * as being Ideographic (class ID) by the Unicode Line Breaking Algorithm
536     * (http://www.unicode.org/unicode/reports/tr14/), and is therefore OK
537     * to break between a pair of.
538     *
539     * @param includeNonStarters also return true for category NS
540     *                           (non-starters), which can be broken
541     *                           after but not before.
542     */
543    private static final boolean isIdeographic(char c, boolean includeNonStarters) {
544        if (c >= '\u2E80' && c <= '\u2FFF') {
545            return true; // CJK, KANGXI RADICALS, DESCRIPTION SYMBOLS
546        }
547        if (c == '\u3000') {
548            return true; // IDEOGRAPHIC SPACE
549        }
550        if (c >= '\u3040' && c <= '\u309F') {
551            if (!includeNonStarters) {
552                switch (c) {
553                case '\u3041': //  # HIRAGANA LETTER SMALL A
554                case '\u3043': //  # HIRAGANA LETTER SMALL I
555                case '\u3045': //  # HIRAGANA LETTER SMALL U
556                case '\u3047': //  # HIRAGANA LETTER SMALL E
557                case '\u3049': //  # HIRAGANA LETTER SMALL O
558                case '\u3063': //  # HIRAGANA LETTER SMALL TU
559                case '\u3083': //  # HIRAGANA LETTER SMALL YA
560                case '\u3085': //  # HIRAGANA LETTER SMALL YU
561                case '\u3087': //  # HIRAGANA LETTER SMALL YO
562                case '\u308E': //  # HIRAGANA LETTER SMALL WA
563                case '\u3095': //  # HIRAGANA LETTER SMALL KA
564                case '\u3096': //  # HIRAGANA LETTER SMALL KE
565                case '\u309B': //  # KATAKANA-HIRAGANA VOICED SOUND MARK
566                case '\u309C': //  # KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK
567                case '\u309D': //  # HIRAGANA ITERATION MARK
568                case '\u309E': //  # HIRAGANA VOICED ITERATION MARK
569                    return false;
570                }
571            }
572            return true; // Hiragana (except small characters)
573        }
574        if (c >= '\u30A0' && c <= '\u30FF') {
575            if (!includeNonStarters) {
576                switch (c) {
577                case '\u30A0': //  # KATAKANA-HIRAGANA DOUBLE HYPHEN
578                case '\u30A1': //  # KATAKANA LETTER SMALL A
579                case '\u30A3': //  # KATAKANA LETTER SMALL I
580                case '\u30A5': //  # KATAKANA LETTER SMALL U
581                case '\u30A7': //  # KATAKANA LETTER SMALL E
582                case '\u30A9': //  # KATAKANA LETTER SMALL O
583                case '\u30C3': //  # KATAKANA LETTER SMALL TU
584                case '\u30E3': //  # KATAKANA LETTER SMALL YA
585                case '\u30E5': //  # KATAKANA LETTER SMALL YU
586                case '\u30E7': //  # KATAKANA LETTER SMALL YO
587                case '\u30EE': //  # KATAKANA LETTER SMALL WA
588                case '\u30F5': //  # KATAKANA LETTER SMALL KA
589                case '\u30F6': //  # KATAKANA LETTER SMALL KE
590                case '\u30FB': //  # KATAKANA MIDDLE DOT
591                case '\u30FC': //  # KATAKANA-HIRAGANA PROLONGED SOUND MARK
592                case '\u30FD': //  # KATAKANA ITERATION MARK
593                case '\u30FE': //  # KATAKANA VOICED ITERATION MARK
594                    return false;
595                }
596            }
597            return true; // Katakana (except small characters)
598        }
599        if (c >= '\u3400' && c <= '\u4DB5') {
600            return true; // CJK UNIFIED IDEOGRAPHS EXTENSION A
601        }
602        if (c >= '\u4E00' && c <= '\u9FBB') {
603            return true; // CJK UNIFIED IDEOGRAPHS
604        }
605        if (c >= '\uF900' && c <= '\uFAD9') {
606            return true; // CJK COMPATIBILITY IDEOGRAPHS
607        }
608        if (c >= '\uA000' && c <= '\uA48F') {
609            return true; // YI SYLLABLES
610        }
611        if (c >= '\uA490' && c <= '\uA4CF') {
612            return true; // YI RADICALS
613        }
614        if (c >= '\uFE62' && c <= '\uFE66') {
615            return true; // SMALL PLUS SIGN to SMALL EQUALS SIGN
616        }
617        if (c >= '\uFF10' && c <= '\uFF19') {
618            return true; // WIDE DIGITS
619        }
620
621        return false;
622    }
623
624/*
625    private static void dump(byte[] data, int count, String label) {
626        if (false) {
627            System.out.print(label);
628
629            for (int i = 0; i < count; i++)
630                System.out.print(" " + data[i]);
631
632            System.out.println();
633        }
634    }
635*/
636
637    private int out(CharSequence text, int start, int end,
638                      int above, int below, int top, int bottom, int v,
639                      float spacingmult, float spacingadd,
640                      LineHeightSpan[] chooseht, int[] choosehtv,
641                      Paint.FontMetricsInt fm, boolean hasTabOrEmoji,
642                      boolean needMultiply, int pstart, byte[] chdirs,
643                      int dir, boolean easy, boolean last,
644                      boolean includepad, boolean trackpad,
645                      char[] chs, float[] widths, int widstart,
646                      TextUtils.TruncateAt ellipsize, float ellipsiswidth,
647                      float textwidth, TextPaint paint) {
648        int j = mLineCount;
649        int off = j * mColumns;
650        int want = off + mColumns + TOP;
651        int[] lines = mLines;
652
653        if (want >= lines.length) {
654            int nlen = ArrayUtils.idealIntArraySize(want + 1);
655            int[] grow = new int[nlen];
656            System.arraycopy(lines, 0, grow, 0, lines.length);
657            mLines = grow;
658            lines = grow;
659
660            Directions[] grow2 = new Directions[nlen];
661            System.arraycopy(mLineDirections, 0, grow2, 0,
662                             mLineDirections.length);
663            mLineDirections = grow2;
664        }
665
666        if (chooseht != null) {
667            fm.ascent = above;
668            fm.descent = below;
669            fm.top = top;
670            fm.bottom = bottom;
671
672            for (int i = 0; i < chooseht.length; i++) {
673                if (chooseht[i] instanceof LineHeightSpan.WithDensity) {
674                    ((LineHeightSpan.WithDensity) chooseht[i]).
675                        chooseHeight(text, start, end, choosehtv[i], v, fm, paint);
676
677                } else {
678                    chooseht[i].chooseHeight(text, start, end, choosehtv[i], v, fm);
679                }
680            }
681
682            above = fm.ascent;
683            below = fm.descent;
684            top = fm.top;
685            bottom = fm.bottom;
686        }
687
688        if (j == 0) {
689            if (trackpad) {
690                mTopPadding = top - above;
691            }
692
693            if (includepad) {
694                above = top;
695            }
696        }
697        if (last) {
698            if (trackpad) {
699                mBottomPadding = bottom - below;
700            }
701
702            if (includepad) {
703                below = bottom;
704            }
705        }
706
707        int extra;
708
709        if (needMultiply) {
710            double ex = (below - above) * (spacingmult - 1) + spacingadd;
711            if (ex >= 0) {
712                extra = (int)(ex + 0.5);
713            } else {
714                extra = -(int)(-ex + 0.5);
715            }
716        } else {
717            extra = 0;
718        }
719
720        lines[off + START] = start;
721        lines[off + TOP] = v;
722        lines[off + DESCENT] = below + extra;
723
724        v += (below - above) + extra;
725        lines[off + mColumns + START] = end;
726        lines[off + mColumns + TOP] = v;
727
728        if (hasTabOrEmoji)
729            lines[off + TAB] |= TAB_MASK;
730
731        lines[off + DIR] |= dir << DIR_SHIFT;
732        Directions linedirs = DIRS_ALL_LEFT_TO_RIGHT;
733        // easy means all chars < the first RTL, so no emoji, no nothing
734        // XXX a run with no text or all spaces is easy but might be an empty
735        // RTL paragraph.  Make sure easy is false if this is the case.
736        if (easy) {
737            mLineDirections[j] = linedirs;
738        } else {
739            mLineDirections[j] = AndroidBidi.directions(dir, chdirs, widstart, chs,
740                    widstart, end - start);
741
742            // If ellipsize is in marquee mode, do not apply ellipsis on the first line
743            if (ellipsize != null && (ellipsize != TextUtils.TruncateAt.MARQUEE || j != 0)) {
744                calculateEllipsis(start, end, widths, widstart,
745                                  ellipsiswidth, ellipsize, j,
746                                  textwidth, paint);
747            }
748        }
749
750        mLineCount++;
751        return v;
752    }
753
754    private void calculateEllipsis(int linestart, int lineend,
755                                   float[] widths, int widstart,
756                                   float avail, TextUtils.TruncateAt where,
757                                   int line, float textwidth, TextPaint paint) {
758        int len = lineend - linestart;
759
760        if (textwidth <= avail) {
761            // Everything fits!
762            mLines[mColumns * line + ELLIPSIS_START] = 0;
763            mLines[mColumns * line + ELLIPSIS_COUNT] = 0;
764            return;
765        }
766
767        float ellipsiswid = paint.measureText("\u2026");
768        int ellipsisStart, ellipsisCount;
769
770        if (where == TextUtils.TruncateAt.START) {
771            float sum = 0;
772            int i;
773
774            for (i = len; i >= 0; i--) {
775                float w = widths[i - 1 + linestart - widstart];
776
777                if (w + sum + ellipsiswid > avail) {
778                    break;
779                }
780
781                sum += w;
782            }
783
784            ellipsisStart = 0;
785            ellipsisCount = i;
786        } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE) {
787            float sum = 0;
788            int i;
789
790            for (i = 0; i < len; i++) {
791                float w = widths[i + linestart - widstart];
792
793                if (w + sum + ellipsiswid > avail) {
794                    break;
795                }
796
797                sum += w;
798            }
799
800            ellipsisStart = i;
801            ellipsisCount = len - i;
802        } else /* where = TextUtils.TruncateAt.MIDDLE */ {
803            float lsum = 0, rsum = 0;
804            int left = 0, right = len;
805
806            float ravail = (avail - ellipsiswid) / 2;
807            for (right = len; right >= 0; right--) {
808                float w = widths[right - 1 + linestart - widstart];
809
810                if (w + rsum > ravail) {
811                    break;
812                }
813
814                rsum += w;
815            }
816
817            float lavail = avail - ellipsiswid - rsum;
818            for (left = 0; left < right; left++) {
819                float w = widths[left + linestart - widstart];
820
821                if (w + lsum > lavail) {
822                    break;
823                }
824
825                lsum += w;
826            }
827
828            ellipsisStart = left;
829            ellipsisCount = right - left;
830        }
831
832        mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
833        mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
834    }
835
836    // Override the base class so we can directly access our members,
837    // rather than relying on member functions.
838    // The logic mirrors that of Layout.getLineForVertical
839    // FIXME: It may be faster to do a linear search for layouts without many lines.
840    public int getLineForVertical(int vertical) {
841        int high = mLineCount;
842        int low = -1;
843        int guess;
844        int[] lines = mLines;
845        while (high - low > 1) {
846            guess = (high + low) >> 1;
847            if (lines[mColumns * guess + TOP] > vertical){
848                high = guess;
849            } else {
850                low = guess;
851            }
852        }
853        if (low < 0) {
854            return 0;
855        } else {
856            return low;
857        }
858    }
859
860    public int getLineCount() {
861        return mLineCount;
862    }
863
864    public int getLineTop(int line) {
865        return mLines[mColumns * line + TOP];
866    }
867
868    public int getLineDescent(int line) {
869        return mLines[mColumns * line + DESCENT];
870    }
871
872    public int getLineStart(int line) {
873        return mLines[mColumns * line + START] & START_MASK;
874    }
875
876    public int getParagraphDirection(int line) {
877        return mLines[mColumns * line + DIR] >> DIR_SHIFT;
878    }
879
880    public boolean getLineContainsTab(int line) {
881        return (mLines[mColumns * line + TAB] & TAB_MASK) != 0;
882    }
883
884    public final Directions getLineDirections(int line) {
885        return mLineDirections[line];
886    }
887
888    public int getTopPadding() {
889        return mTopPadding;
890    }
891
892    public int getBottomPadding() {
893        return mBottomPadding;
894    }
895
896    @Override
897    public int getEllipsisCount(int line) {
898        if (mColumns < COLUMNS_ELLIPSIZE) {
899            return 0;
900        }
901
902        return mLines[mColumns * line + ELLIPSIS_COUNT];
903    }
904
905    @Override
906    public int getEllipsisStart(int line) {
907        if (mColumns < COLUMNS_ELLIPSIZE) {
908            return 0;
909        }
910
911        return mLines[mColumns * line + ELLIPSIS_START];
912    }
913
914    @Override
915    public int getEllipsizedWidth() {
916        return mEllipsizedWidth;
917    }
918
919    private int mLineCount;
920    private int mTopPadding, mBottomPadding;
921    private int mColumns;
922    private int mEllipsizedWidth;
923
924    private static final int COLUMNS_NORMAL = 3;
925    private static final int COLUMNS_ELLIPSIZE = 5;
926    private static final int START = 0;
927    private static final int DIR = START;
928    private static final int TAB = START;
929    private static final int TOP = 1;
930    private static final int DESCENT = 2;
931    private static final int ELLIPSIS_START = 3;
932    private static final int ELLIPSIS_COUNT = 4;
933
934    private int[] mLines;
935    private Directions[] mLineDirections;
936
937    private static final int START_MASK = 0x1FFFFFFF;
938    private static final int DIR_MASK   = 0xC0000000;
939    private static final int DIR_SHIFT  = 30;
940    private static final int TAB_MASK   = 0x20000000;
941
942    private static final int TAB_INCREMENT = 20; // same as Layout, but that's private
943
944    /*
945     * This is reused across calls to generate()
946     */
947    private MeasuredText mMeasured;
948    private Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
949}
950