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