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