StaticLayout.java revision 1065758a0f8966a8597a61492112f7859a7050a4
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            double ex = (below - above) * (spacingmult - 1) + spacingadd;
1016            if (ex >= 0) {
1017                extra = (int)(ex + 0.5);
1018            } else {
1019                extra = -(int)(-ex + 0.5);
1020            }
1021        } else {
1022            extra = 0;
1023        }
1024
1025        lines[off + START] = start;
1026        lines[off + TOP] = v;
1027        lines[off + DESCENT] = below + extra;
1028
1029        v += (below - above) + extra;
1030        lines[off + mColumns + START] = end;
1031        lines[off + mColumns + TOP] = v;
1032
1033        if (tab)
1034            lines[off + TAB] |= TAB_MASK;
1035
1036        {
1037            lines[off + DIR] |= dir << DIR_SHIFT;
1038
1039            int cur = Character.DIRECTIONALITY_LEFT_TO_RIGHT;
1040            int count = 0;
1041
1042            if (!easy) {
1043                for (int k = start; k < end; k++) {
1044                    if (chdirs[k - pstart] != cur) {
1045                        count++;
1046                        cur = chdirs[k - pstart];
1047                    }
1048                }
1049            }
1050
1051            Directions linedirs;
1052
1053            if (count == 0) {
1054                linedirs = DIRS_ALL_LEFT_TO_RIGHT;
1055            } else {
1056                short[] ld = new short[count + 1];
1057
1058                cur = Character.DIRECTIONALITY_LEFT_TO_RIGHT;
1059                count = 0;
1060                int here = start;
1061
1062                for (int k = start; k < end; k++) {
1063                    if (chdirs[k - pstart] != cur) {
1064                        // XXX check to make sure we don't
1065                        //     overflow short
1066                        ld[count++] = (short) (k - here);
1067                        cur = chdirs[k - pstart];
1068                        here = k;
1069                    }
1070                }
1071
1072                ld[count] = (short) (end - here);
1073
1074                if (count == 1 && ld[0] == 0) {
1075                    linedirs = DIRS_ALL_RIGHT_TO_LEFT;
1076                } else {
1077                    linedirs = new Directions(ld);
1078                }
1079            }
1080
1081            mLineDirections[j] = linedirs;
1082
1083            // If ellipsize is in marquee mode, do not apply ellipsis on the first line
1084            if (ellipsize != null && (ellipsize != TextUtils.TruncateAt.MARQUEE || j != 0)) {
1085                calculateEllipsis(start, end, widths, widstart, widoff,
1086                                  ellipsiswidth, ellipsize, j,
1087                                  textwidth, paint);
1088            }
1089        }
1090
1091        mLineCount++;
1092        return v;
1093    }
1094
1095    private void calculateEllipsis(int linestart, int lineend,
1096                                   float[] widths, int widstart, int widoff,
1097                                   float avail, TextUtils.TruncateAt where,
1098                                   int line, float textwidth, TextPaint paint) {
1099        int len = lineend - linestart;
1100
1101        if (textwidth <= avail) {
1102            // Everything fits!
1103            mLines[mColumns * line + ELLIPSIS_START] = 0;
1104            mLines[mColumns * line + ELLIPSIS_COUNT] = 0;
1105            return;
1106        }
1107
1108        float ellipsiswid = paint.measureText("\u2026");
1109        int ellipsisStart, ellipsisCount;
1110
1111        if (where == TextUtils.TruncateAt.START) {
1112            float sum = 0;
1113            int i;
1114
1115            for (i = len; i >= 0; i--) {
1116                float w = widths[i - 1 + linestart - widstart + widoff];
1117
1118                if (w + sum + ellipsiswid > avail) {
1119                    break;
1120                }
1121
1122                sum += w;
1123            }
1124
1125            ellipsisStart = 0;
1126            ellipsisCount = i;
1127        } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE) {
1128            float sum = 0;
1129            int i;
1130
1131            for (i = 0; i < len; i++) {
1132                float w = widths[i + linestart - widstart + widoff];
1133
1134                if (w + sum + ellipsiswid > avail) {
1135                    break;
1136                }
1137
1138                sum += w;
1139            }
1140
1141            ellipsisStart = i;
1142            ellipsisCount = len - i;
1143        } else /* where = TextUtils.TruncateAt.MIDDLE */ {
1144            float lsum = 0, rsum = 0;
1145            int left = 0, right = len;
1146
1147            float ravail = (avail - ellipsiswid) / 2;
1148            for (right = len; right >= 0; right--) {
1149                float w = widths[right - 1 + linestart - widstart + widoff];
1150
1151                if (w + rsum > ravail) {
1152                    break;
1153                }
1154
1155                rsum += w;
1156            }
1157
1158            float lavail = avail - ellipsiswid - rsum;
1159            for (left = 0; left < right; left++) {
1160                float w = widths[left + linestart - widstart + widoff];
1161
1162                if (w + lsum > lavail) {
1163                    break;
1164                }
1165
1166                lsum += w;
1167            }
1168
1169            ellipsisStart = left;
1170            ellipsisCount = right - left;
1171        }
1172
1173        mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
1174        mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
1175    }
1176
1177    // Override the baseclass so we can directly access our members,
1178    // rather than relying on member functions.
1179    // The logic mirrors that of Layout.getLineForVertical
1180    // FIXME: It may be faster to do a linear search for layouts without many lines.
1181    public int getLineForVertical(int vertical) {
1182        int high = mLineCount;
1183        int low = -1;
1184        int guess;
1185        int[] lines = mLines;
1186        while (high - low > 1) {
1187            guess = (high + low) >> 1;
1188            if (lines[mColumns * guess + TOP] > vertical){
1189                high = guess;
1190            } else {
1191                low = guess;
1192            }
1193        }
1194        if (low < 0) {
1195            return 0;
1196        } else {
1197            return low;
1198        }
1199    }
1200
1201    public int getLineCount() {
1202        return mLineCount;
1203    }
1204
1205    public int getLineTop(int line) {
1206        return mLines[mColumns * line + TOP];
1207    }
1208
1209    public int getLineDescent(int line) {
1210        return mLines[mColumns * line + DESCENT];
1211    }
1212
1213    public int getLineStart(int line) {
1214        return mLines[mColumns * line + START] & START_MASK;
1215    }
1216
1217    public int getParagraphDirection(int line) {
1218        return mLines[mColumns * line + DIR] >> DIR_SHIFT;
1219    }
1220
1221    public boolean getLineContainsTab(int line) {
1222        return (mLines[mColumns * line + TAB] & TAB_MASK) != 0;
1223    }
1224
1225    public final Directions getLineDirections(int line) {
1226        return mLineDirections[line];
1227    }
1228
1229    public int getTopPadding() {
1230        return mTopPadding;
1231    }
1232
1233    public int getBottomPadding() {
1234        return mBottomPadding;
1235    }
1236
1237    @Override
1238    public int getEllipsisCount(int line) {
1239        if (mColumns < COLUMNS_ELLIPSIZE) {
1240            return 0;
1241        }
1242
1243        return mLines[mColumns * line + ELLIPSIS_COUNT];
1244    }
1245
1246    @Override
1247    public int getEllipsisStart(int line) {
1248        if (mColumns < COLUMNS_ELLIPSIZE) {
1249            return 0;
1250        }
1251
1252        return mLines[mColumns * line + ELLIPSIS_START];
1253    }
1254
1255    @Override
1256    public int getEllipsizedWidth() {
1257        return mEllipsizedWidth;
1258    }
1259
1260    private int mLineCount;
1261    private int mTopPadding, mBottomPadding;
1262    private int mColumns;
1263    private int mEllipsizedWidth;
1264
1265    private static final int COLUMNS_NORMAL = 3;
1266    private static final int COLUMNS_ELLIPSIZE = 5;
1267    private static final int START = 0;
1268    private static final int DIR = START;
1269    private static final int TAB = START;
1270    private static final int TOP = 1;
1271    private static final int DESCENT = 2;
1272    private static final int ELLIPSIS_START = 3;
1273    private static final int ELLIPSIS_COUNT = 4;
1274
1275    private int[] mLines;
1276    private Directions[] mLineDirections;
1277
1278    private static final int START_MASK = 0x1FFFFFFF;
1279    private static final int DIR_MASK   = 0xC0000000;
1280    private static final int DIR_SHIFT  = 30;
1281    private static final int TAB_MASK   = 0x20000000;
1282
1283    private static final char FIRST_RIGHT_TO_LEFT = '\u0590';
1284
1285    /*
1286     * These are reused across calls to generate()
1287     */
1288    private byte[] mChdirs;
1289    private char[] mChs;
1290    private float[] mWidths;
1291    private Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
1292}
1293