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