StaticLayout.java revision c982f60e982c1d2df9f115ed9a5c3ef3643d0892
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 = spanned.getSpans(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 = spanned.getSpans(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                            // First tab this para, check for tabstops
269                            TabStopSpan[] spans = spanned.getSpans(paraStart,
270                                    paraEnd, TabStopSpan.class);
271                            if (spans.length > 0) {
272                                tabStops = new TabStops(TAB_INCREMENT, spans);
273                            }
274                        }
275                        if (tabStops != null) {
276                            w = tabStops.nextTab(w);
277                        } else {
278                            w = TabStops.nextDefaultStop(w, TAB_INCREMENT);
279                        }
280                    } else if (c >= 0xD800 && c <= 0xDFFF && j + 1 < spanEnd) {
281                        int emoji = Character.codePointAt(chs, j - paraStart);
282
283                        if (emoji >= MIN_EMOJI && emoji <= MAX_EMOJI) {
284                            Bitmap bm = EMOJI_FACTORY.
285                                getBitmapFromAndroidPua(emoji);
286
287                            if (bm != null) {
288                                Paint whichPaint;
289
290                                if (spanned == null) {
291                                    whichPaint = paint;
292                                } else {
293                                    whichPaint = mWorkPaint;
294                                }
295
296                                float wid = bm.getWidth() *
297                                            -whichPaint.ascent() /
298                                            bm.getHeight();
299
300                                w += wid;
301                                hasTabOrEmoji = true;
302                                j++;
303                            } else {
304                                w += widths[j - paraStart];
305                            }
306                        } else {
307                            w += widths[j - paraStart];
308                        }
309                    } else {
310                        w += widths[j - paraStart];
311                    }
312
313                    // Log.e("text", "was " + before + " now " + w + " after " + c + " within " + width);
314
315                    if (w <= width) {
316                        fitwidth = w;
317                        fit = j + 1;
318
319                        if (fmtop < fittop)
320                            fittop = fmtop;
321                        if (fmascent < fitascent)
322                            fitascent = fmascent;
323                        if (fmdescent > fitdescent)
324                            fitdescent = fmdescent;
325                        if (fmbottom > fitbottom)
326                            fitbottom = fmbottom;
327
328                        /*
329                         * From the Unicode Line Breaking Algorithm:
330                         * (at least approximately)
331                         *
332                         * .,:; are class IS: breakpoints
333                         *      except when adjacent to digits
334                         * /    is class SY: a breakpoint
335                         *      except when followed by a digit.
336                         * -    is class HY: a breakpoint
337                         *      except when followed by a digit.
338                         *
339                         * Ideographs are class ID: breakpoints when adjacent,
340                         * except for NS (non-starters), which can be broken
341                         * after but not before.
342                         */
343
344                        if (c == ' ' || c == '\t' ||
345                            ((c == '.'  || c == ',' || c == ':' || c == ';') &&
346                             (j - 1 < here || !Character.isDigit(chs[j - 1 - paraStart])) &&
347                             (j + 1 >= spanEnd || !Character.isDigit(chs[j + 1 - paraStart]))) ||
348                            ((c == '/' || c == '-') &&
349                             (j + 1 >= spanEnd || !Character.isDigit(chs[j + 1 - paraStart]))) ||
350                            (c >= FIRST_CJK && isIdeographic(c, true) &&
351                             j + 1 < spanEnd && isIdeographic(chs[j + 1 - paraStart], false))) {
352                            okwidth = w;
353                            ok = j + 1;
354
355                            if (fittop < oktop)
356                                oktop = fittop;
357                            if (fitascent < okascent)
358                                okascent = fitascent;
359                            if (fitdescent > okdescent)
360                                okdescent = fitdescent;
361                            if (fitbottom > okbottom)
362                                okbottom = fitbottom;
363                        }
364                    } else if (breakOnlyAtSpaces) {
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                                    where, 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                            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                                    where, 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                                    where, 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                                    where, ellipsizedWidth,
456                                    widths[here - paraStart], paint);
457
458                            here = here + 1;
459                        }
460
461                        if (here < spanStart) {
462                            // didn't output all the text for this span
463                            // we've measured the raw widths, though, so
464                            // just reset the start point
465                            j = nextSpanStart = here;
466                        } else {
467                            j = here - 1;    // continue looping
468                        }
469
470                        ok = fit = here;
471                        w = 0;
472                        fitascent = fitdescent = fittop = fitbottom = 0;
473                        okascent = okdescent = oktop = okbottom = 0;
474
475                        if (--firstWidthLineLimit <= 0) {
476                            width = restwidth;
477                        }
478                    }
479                }
480            }
481
482            if (paraEnd != here) {
483                if ((fittop | fitbottom | fitdescent | fitascent) == 0) {
484                    paint.getFontMetricsInt(fm);
485
486                    fittop = fm.top;
487                    fitbottom = fm.bottom;
488                    fitascent = fm.ascent;
489                    fitdescent = fm.descent;
490                }
491
492                // Log.e("text", "output rest " + here + " to " + end);
493
494                v = out(source,
495                        here, paraEnd, fitascent, fitdescent,
496                        fittop, fitbottom,
497                        v,
498                        spacingmult, spacingadd, chooseht,
499                        choosehtv, fm, hasTabOrEmoji,
500                        needMultiply, paraStart, chdirs, dir, easy,
501                        paraEnd == bufend, includepad, trackpad,
502                        chs, widths, here - paraStart,
503                        where, ellipsizedWidth, w, paint);
504            }
505
506            paraStart = paraEnd;
507
508            if (paraEnd == bufend)
509                break;
510        }
511
512        if (bufend == bufstart || source.charAt(bufend - 1) == '\n') {
513            // Log.e("text", "output last " + bufend);
514
515            paint.getFontMetricsInt(fm);
516
517            v = out(source,
518                    bufend, bufend, fm.ascent, fm.descent,
519                    fm.top, fm.bottom,
520                    v,
521                    spacingmult, spacingadd, null,
522                    null, fm, false,
523                    needMultiply, bufend, null, DEFAULT_DIR, true,
524                    true, includepad, trackpad,
525                    null, null, bufstart,
526                    where, ellipsizedWidth, 0, paint);
527        }
528    }
529
530    private static final char FIRST_CJK = '\u2E80';
531    /**
532     * Returns true if the specified character is one of those specified
533     * as being Ideographic (class ID) by the Unicode Line Breaking Algorithm
534     * (http://www.unicode.org/unicode/reports/tr14/), and is therefore OK
535     * to break between a pair of.
536     *
537     * @param includeNonStarters also return true for category NS
538     *                           (non-starters), which can be broken
539     *                           after but not before.
540     */
541    private static final boolean isIdeographic(char c, boolean includeNonStarters) {
542        if (c >= '\u2E80' && c <= '\u2FFF') {
543            return true; // CJK, KANGXI RADICALS, DESCRIPTION SYMBOLS
544        }
545        if (c == '\u3000') {
546            return true; // IDEOGRAPHIC SPACE
547        }
548        if (c >= '\u3040' && c <= '\u309F') {
549            if (!includeNonStarters) {
550                switch (c) {
551                case '\u3041': //  # HIRAGANA LETTER SMALL A
552                case '\u3043': //  # HIRAGANA LETTER SMALL I
553                case '\u3045': //  # HIRAGANA LETTER SMALL U
554                case '\u3047': //  # HIRAGANA LETTER SMALL E
555                case '\u3049': //  # HIRAGANA LETTER SMALL O
556                case '\u3063': //  # HIRAGANA LETTER SMALL TU
557                case '\u3083': //  # HIRAGANA LETTER SMALL YA
558                case '\u3085': //  # HIRAGANA LETTER SMALL YU
559                case '\u3087': //  # HIRAGANA LETTER SMALL YO
560                case '\u308E': //  # HIRAGANA LETTER SMALL WA
561                case '\u3095': //  # HIRAGANA LETTER SMALL KA
562                case '\u3096': //  # HIRAGANA LETTER SMALL KE
563                case '\u309B': //  # KATAKANA-HIRAGANA VOICED SOUND MARK
564                case '\u309C': //  # KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK
565                case '\u309D': //  # HIRAGANA ITERATION MARK
566                case '\u309E': //  # HIRAGANA VOICED ITERATION MARK
567                    return false;
568                }
569            }
570            return true; // Hiragana (except small characters)
571        }
572        if (c >= '\u30A0' && c <= '\u30FF') {
573            if (!includeNonStarters) {
574                switch (c) {
575                case '\u30A0': //  # KATAKANA-HIRAGANA DOUBLE HYPHEN
576                case '\u30A1': //  # KATAKANA LETTER SMALL A
577                case '\u30A3': //  # KATAKANA LETTER SMALL I
578                case '\u30A5': //  # KATAKANA LETTER SMALL U
579                case '\u30A7': //  # KATAKANA LETTER SMALL E
580                case '\u30A9': //  # KATAKANA LETTER SMALL O
581                case '\u30C3': //  # KATAKANA LETTER SMALL TU
582                case '\u30E3': //  # KATAKANA LETTER SMALL YA
583                case '\u30E5': //  # KATAKANA LETTER SMALL YU
584                case '\u30E7': //  # KATAKANA LETTER SMALL YO
585                case '\u30EE': //  # KATAKANA LETTER SMALL WA
586                case '\u30F5': //  # KATAKANA LETTER SMALL KA
587                case '\u30F6': //  # KATAKANA LETTER SMALL KE
588                case '\u30FB': //  # KATAKANA MIDDLE DOT
589                case '\u30FC': //  # KATAKANA-HIRAGANA PROLONGED SOUND MARK
590                case '\u30FD': //  # KATAKANA ITERATION MARK
591                case '\u30FE': //  # KATAKANA VOICED ITERATION MARK
592                    return false;
593                }
594            }
595            return true; // Katakana (except small characters)
596        }
597        if (c >= '\u3400' && c <= '\u4DB5') {
598            return true; // CJK UNIFIED IDEOGRAPHS EXTENSION A
599        }
600        if (c >= '\u4E00' && c <= '\u9FBB') {
601            return true; // CJK UNIFIED IDEOGRAPHS
602        }
603        if (c >= '\uF900' && c <= '\uFAD9') {
604            return true; // CJK COMPATIBILITY IDEOGRAPHS
605        }
606        if (c >= '\uA000' && c <= '\uA48F') {
607            return true; // YI SYLLABLES
608        }
609        if (c >= '\uA490' && c <= '\uA4CF') {
610            return true; // YI RADICALS
611        }
612        if (c >= '\uFE62' && c <= '\uFE66') {
613            return true; // SMALL PLUS SIGN to SMALL EQUALS SIGN
614        }
615        if (c >= '\uFF10' && c <= '\uFF19') {
616            return true; // WIDE DIGITS
617        }
618
619        return false;
620    }
621
622/*
623    private static void dump(byte[] data, int count, String label) {
624        if (false) {
625            System.out.print(label);
626
627            for (int i = 0; i < count; i++)
628                System.out.print(" " + data[i]);
629
630            System.out.println();
631        }
632    }
633*/
634
635    private int out(CharSequence text, int start, int end,
636                      int above, int below, int top, int bottom, int v,
637                      float spacingmult, float spacingadd,
638                      LineHeightSpan[] chooseht, int[] choosehtv,
639                      Paint.FontMetricsInt fm, boolean hasTabOrEmoji,
640                      boolean needMultiply, int pstart, byte[] chdirs,
641                      int dir, boolean easy, boolean last,
642                      boolean includepad, boolean trackpad,
643                      char[] chs, float[] widths, int widstart,
644                      TextUtils.TruncateAt ellipsize, float ellipsiswidth,
645                      float textwidth, TextPaint paint) {
646        int j = mLineCount;
647        int off = j * mColumns;
648        int want = off + mColumns + TOP;
649        int[] lines = mLines;
650
651        if (want >= lines.length) {
652            int nlen = ArrayUtils.idealIntArraySize(want + 1);
653            int[] grow = new int[nlen];
654            System.arraycopy(lines, 0, grow, 0, lines.length);
655            mLines = grow;
656            lines = grow;
657
658            Directions[] grow2 = new Directions[nlen];
659            System.arraycopy(mLineDirections, 0, grow2, 0,
660                             mLineDirections.length);
661            mLineDirections = grow2;
662        }
663
664        if (chooseht != null) {
665            fm.ascent = above;
666            fm.descent = below;
667            fm.top = top;
668            fm.bottom = bottom;
669
670            for (int i = 0; i < chooseht.length; i++) {
671                if (chooseht[i] instanceof LineHeightSpan.WithDensity) {
672                    ((LineHeightSpan.WithDensity) chooseht[i]).
673                        chooseHeight(text, start, end, choosehtv[i], v, fm, paint);
674
675                } else {
676                    chooseht[i].chooseHeight(text, start, end, choosehtv[i], v, fm);
677                }
678            }
679
680            above = fm.ascent;
681            below = fm.descent;
682            top = fm.top;
683            bottom = fm.bottom;
684        }
685
686        if (j == 0) {
687            if (trackpad) {
688                mTopPadding = top - above;
689            }
690
691            if (includepad) {
692                above = top;
693            }
694        }
695        if (last) {
696            if (trackpad) {
697                mBottomPadding = bottom - below;
698            }
699
700            if (includepad) {
701                below = bottom;
702            }
703        }
704
705        int extra;
706
707        if (needMultiply) {
708            double ex = (below - above) * (spacingmult - 1) + spacingadd;
709            if (ex >= 0) {
710                extra = (int)(ex + 0.5);
711            } else {
712                extra = -(int)(-ex + 0.5);
713            }
714        } else {
715            extra = 0;
716        }
717
718        lines[off + START] = start;
719        lines[off + TOP] = v;
720        lines[off + DESCENT] = below + extra;
721
722        v += (below - above) + extra;
723        lines[off + mColumns + START] = end;
724        lines[off + mColumns + TOP] = v;
725
726        if (hasTabOrEmoji)
727            lines[off + TAB] |= TAB_MASK;
728
729        lines[off + DIR] |= dir << DIR_SHIFT;
730        Directions linedirs = DIRS_ALL_LEFT_TO_RIGHT;
731        // easy means all chars < the first RTL, so no emoji, no nothing
732        // XXX a run with no text or all spaces is easy but might be an empty
733        // RTL paragraph.  Make sure easy is false if this is the case.
734        if (easy) {
735            mLineDirections[j] = linedirs;
736        } else {
737            mLineDirections[j] = AndroidBidi.directions(dir, chdirs, widstart, chs,
738                    widstart, end - start);
739
740            // If ellipsize is in marquee mode, do not apply ellipsis on the first line
741            if (ellipsize != null && (ellipsize != TextUtils.TruncateAt.MARQUEE || j != 0)) {
742                calculateEllipsis(start, end, widths, widstart,
743                                  ellipsiswidth, ellipsize, j,
744                                  textwidth, paint);
745            }
746        }
747
748        mLineCount++;
749        return v;
750    }
751
752    private void calculateEllipsis(int linestart, int lineend,
753                                   float[] widths, int widstart,
754                                   float avail, TextUtils.TruncateAt where,
755                                   int line, float textwidth, TextPaint paint) {
756        int len = lineend - linestart;
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
768        if (where == TextUtils.TruncateAt.START) {
769            float sum = 0;
770            int i;
771
772            for (i = len; i >= 0; i--) {
773                float w = widths[i - 1 + linestart - widstart];
774
775                if (w + sum + ellipsiswid > avail) {
776                    break;
777                }
778
779                sum += w;
780            }
781
782            ellipsisStart = 0;
783            ellipsisCount = i;
784        } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE) {
785            float sum = 0;
786            int i;
787
788            for (i = 0; i < len; i++) {
789                float w = widths[i + linestart - widstart];
790
791                if (w + sum + ellipsiswid > avail) {
792                    break;
793                }
794
795                sum += w;
796            }
797
798            ellipsisStart = i;
799            ellipsisCount = len - i;
800        } else /* where = TextUtils.TruncateAt.MIDDLE */ {
801            float lsum = 0, rsum = 0;
802            int left = 0, right = len;
803
804            float ravail = (avail - ellipsiswid) / 2;
805            for (right = len; right >= 0; right--) {
806                float w = widths[right - 1 + linestart - widstart];
807
808                if (w + rsum > ravail) {
809                    break;
810                }
811
812                rsum += w;
813            }
814
815            float lavail = avail - ellipsiswid - rsum;
816            for (left = 0; left < right; left++) {
817                float w = widths[left + linestart - widstart];
818
819                if (w + lsum > lavail) {
820                    break;
821                }
822
823                lsum += w;
824            }
825
826            ellipsisStart = left;
827            ellipsisCount = right - left;
828        }
829
830        mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
831        mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
832    }
833
834    // Override the base class so we can directly access our members,
835    // rather than relying on member functions.
836    // The logic mirrors that of Layout.getLineForVertical
837    // FIXME: It may be faster to do a linear search for layouts without many lines.
838    public int getLineForVertical(int vertical) {
839        int high = mLineCount;
840        int low = -1;
841        int guess;
842        int[] lines = mLines;
843        while (high - low > 1) {
844            guess = (high + low) >> 1;
845            if (lines[mColumns * guess + TOP] > vertical){
846                high = guess;
847            } else {
848                low = guess;
849            }
850        }
851        if (low < 0) {
852            return 0;
853        } else {
854            return low;
855        }
856    }
857
858    public int getLineCount() {
859        return mLineCount;
860    }
861
862    public int getLineTop(int line) {
863        return mLines[mColumns * line + TOP];
864    }
865
866    public int getLineDescent(int line) {
867        return mLines[mColumns * line + DESCENT];
868    }
869
870    public int getLineStart(int line) {
871        return mLines[mColumns * line + START] & START_MASK;
872    }
873
874    public int getParagraphDirection(int line) {
875        return mLines[mColumns * line + DIR] >> DIR_SHIFT;
876    }
877
878    public boolean getLineContainsTab(int line) {
879        return (mLines[mColumns * line + TAB] & TAB_MASK) != 0;
880    }
881
882    public final Directions getLineDirections(int line) {
883        return mLineDirections[line];
884    }
885
886    public int getTopPadding() {
887        return mTopPadding;
888    }
889
890    public int getBottomPadding() {
891        return mBottomPadding;
892    }
893
894    @Override
895    public int getEllipsisCount(int line) {
896        if (mColumns < COLUMNS_ELLIPSIZE) {
897            return 0;
898        }
899
900        return mLines[mColumns * line + ELLIPSIS_COUNT];
901    }
902
903    @Override
904    public int getEllipsisStart(int line) {
905        if (mColumns < COLUMNS_ELLIPSIZE) {
906            return 0;
907        }
908
909        return mLines[mColumns * line + ELLIPSIS_START];
910    }
911
912    @Override
913    public int getEllipsizedWidth() {
914        return mEllipsizedWidth;
915    }
916
917    private int mLineCount;
918    private int mTopPadding, mBottomPadding;
919    private int mColumns;
920    private int mEllipsizedWidth;
921
922    private static final int COLUMNS_NORMAL = 3;
923    private static final int COLUMNS_ELLIPSIZE = 5;
924    private static final int START = 0;
925    private static final int DIR = START;
926    private static final int TAB = START;
927    private static final int TOP = 1;
928    private static final int DESCENT = 2;
929    private static final int ELLIPSIS_START = 3;
930    private static final int ELLIPSIS_COUNT = 4;
931
932    private int[] mLines;
933    private Directions[] mLineDirections;
934
935    private static final int START_MASK = 0x1FFFFFFF;
936    private static final int DIR_MASK   = 0xC0000000;
937    private static final int DIR_SHIFT  = 30;
938    private static final int TAB_MASK   = 0x20000000;
939
940    private static final int TAB_INCREMENT = 20; // same as Layout, but that's private
941
942    /*
943     * This is reused across calls to generate()
944     */
945    private MeasuredText mMeasured;
946    private Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
947}
948