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