StaticLayout.java revision 423f0e4205e3c49c6a87b389fa6025772aa7010c
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                         */
623
624                        if (c == ' ' || c == '\t' ||
625                            ((c == '.'  || c == ',' || c == ':' || c == ';') &&
626                             (j - 1 < here || !Character.isDigit(chs[j - 1 - start])) &&
627                             (j + 1 >= next || !Character.isDigit(chs[j + 1 - start]))) ||
628                            ((c == '/' || c == '-') &&
629                             (j + 1 >= next || !Character.isDigit(chs[j + 1 - start]))) ||
630                            (c >= FIRST_CJK && isIdeographic(c) &&
631                             j + 1 < next && isIdeographic(chs[j + 1 - start]))) {
632                            okwidth = w;
633                            ok = j + 1;
634
635                            if (fittop < oktop)
636                                oktop = fittop;
637                            if (fitascent < okascent)
638                                okascent = fitascent;
639                            if (fitdescent > okdescent)
640                                okdescent = fitdescent;
641                            if (fitbottom > okbottom)
642                                okbottom = fitbottom;
643                        }
644                    } else if (breakOnlyAtSpaces) {
645                        if (ok != here) {
646                            // Log.e("text", "output ok " + here + " to " +ok);
647
648                            while (ok < next && chs[ok - start] == ' ') {
649                                ok++;
650                            }
651
652                            v = out(source,
653                                    here, ok,
654                                    okascent, okdescent, oktop, okbottom,
655                                    v,
656                                    spacingmult, spacingadd, chooseht,
657                                    choosehtv, fm, tab,
658                                    needMultiply, start, chdirs, dir, easy,
659                                    ok == bufend, includepad, trackpad,
660                                    widths, start, end - start,
661                                    where, ellipsizedWidth, okwidth,
662                                    paint);
663
664                            here = ok;
665                        } else {
666                            // Act like it fit even though it didn't.
667
668                            fitwidth = w;
669                            fit = j + 1;
670
671                            if (fmtop < fittop)
672                                fittop = fmtop;
673                            if (fmascent < fitascent)
674                                fitascent = fmascent;
675                            if (fmdescent > fitdescent)
676                                fitdescent = fmdescent;
677                            if (fmbottom > fitbottom)
678                                fitbottom = fmbottom;
679                        }
680                    } else {
681                        if (ok != here) {
682                            // Log.e("text", "output ok " + here + " to " +ok);
683
684                            while (ok < next && chs[ok - start] == ' ') {
685                                ok++;
686                            }
687
688                            v = out(source,
689                                    here, ok,
690                                    okascent, okdescent, oktop, okbottom,
691                                    v,
692                                    spacingmult, spacingadd, chooseht,
693                                    choosehtv, fm, tab,
694                                    needMultiply, start, chdirs, dir, easy,
695                                    ok == bufend, includepad, trackpad,
696                                    widths, start, end - start,
697                                    where, ellipsizedWidth, okwidth,
698                                    paint);
699
700                            here = ok;
701                        } else if (fit != here) {
702                            // Log.e("text", "output fit " + here + " to " +fit);
703                            v = out(source,
704                                    here, fit,
705                                    fitascent, fitdescent,
706                                    fittop, fitbottom,
707                                    v,
708                                    spacingmult, spacingadd, chooseht,
709                                    choosehtv, fm, tab,
710                                    needMultiply, start, chdirs, dir, easy,
711                                    fit == bufend, includepad, trackpad,
712                                    widths, start, end - start,
713                                    where, ellipsizedWidth, fitwidth,
714                                    paint);
715
716                            here = fit;
717                        } else {
718                            // Log.e("text", "output one " + here + " to " +(here + 1));
719                            measureText(paint, mWorkPaint,
720                                        source, here, here + 1, fm, tab,
721                                        null);
722
723                            v = out(source,
724                                    here, here+1,
725                                    fm.ascent, fm.descent,
726                                    fm.top, fm.bottom,
727                                    v,
728                                    spacingmult, spacingadd, chooseht,
729                                    choosehtv, fm, tab,
730                                    needMultiply, start, chdirs, dir, easy,
731                                    here + 1 == bufend, includepad,
732                                    trackpad,
733                                    widths, start, end - start,
734                                    where, ellipsizedWidth,
735                                    widths[here - start], paint);
736
737                            here = here + 1;
738                        }
739
740                        if (here < i) {
741                            j = next = here; // must remeasure
742                        } else {
743                            j = here - 1;    // continue looping
744                        }
745
746                        ok = fit = here;
747                        w = 0;
748                        fitascent = fitdescent = fittop = fitbottom = 0;
749                        okascent = okdescent = oktop = okbottom = 0;
750
751                        width = restwidth;
752                    }
753                }
754            }
755
756            if (end != here) {
757                if ((fittop | fitbottom | fitdescent | fitascent) == 0) {
758                    paint.getFontMetricsInt(fm);
759
760                    fittop = fm.top;
761                    fitbottom = fm.bottom;
762                    fitascent = fm.ascent;
763                    fitdescent = fm.descent;
764                }
765
766                // Log.e("text", "output rest " + here + " to " + end);
767
768                v = out(source,
769                        here, end, fitascent, fitdescent,
770                        fittop, fitbottom,
771                        v,
772                        spacingmult, spacingadd, chooseht,
773                        choosehtv, fm, tab,
774                        needMultiply, start, chdirs, dir, easy,
775                        end == bufend, includepad, trackpad,
776                        widths, start, end - start,
777                        where, ellipsizedWidth, w, paint);
778            }
779
780            start = end;
781
782            if (end == bufend)
783                break;
784        }
785
786        if (bufend == bufstart || source.charAt(bufend - 1) == '\n') {
787            // Log.e("text", "output last " + bufend);
788
789            paint.getFontMetricsInt(fm);
790
791            v = out(source,
792                    bufend, bufend, fm.ascent, fm.descent,
793                    fm.top, fm.bottom,
794                    v,
795                    spacingmult, spacingadd, null,
796                    null, fm, false,
797                    needMultiply, bufend, chdirs, DEFAULT_DIR, true,
798                    true, includepad, trackpad,
799                    widths, bufstart, 0,
800                    where, ellipsizedWidth, 0, paint);
801        }
802    }
803
804    private static final char FIRST_CJK = '\u2E80';
805    /**
806     * Returns true if the specified character is one of those specified
807     * as being Ideographic (class ID) by the Unicode Line Breaking Algorithm
808     * (http://www.unicode.org/unicode/reports/tr14/), and is therefore OK
809     * to break between a pair of.
810     */
811    private static final boolean isIdeographic(char c) {
812        if (c >= '\u2E80' && c <= '\u2FFF') {
813            return true; // CJK, KANGXI RADICALS, DESCRIPTION SYMBOLS
814        }
815        if (c == '\u3000') {
816            return true; // IDEOGRAPHIC SPACE
817        }
818        if (c >= '\u3040' && c <= '\u309F') {
819            return true; // Hiragana (except small characters)
820        }
821        if (c >= '\u30A0' && c <= '\u30FF') {
822            return true; // Katakana (except small characters)
823        }
824        if (c >= '\u3400' && c <= '\u4DB5') {
825            return true; // CJK UNIFIED IDEOGRAPHS EXTENSION A
826        }
827        if (c >= '\u4E00' && c <= '\u9FBB') {
828            return true; // CJK UNIFIED IDEOGRAPHS
829        }
830        if (c >= '\uF900' && c <= '\uFAD9') {
831            return true; // CJK COMPATIBILITY IDEOGRAPHS
832        }
833        if (c >= '\uA000' && c <= '\uA48F') {
834            return true; // YI SYLLABLES
835        }
836        if (c >= '\uA490' && c <= '\uA4CF') {
837            return true; // YI RADICALS
838        }
839        if (c >= '\uFE62' && c <= '\uFE66') {
840            return true; // SMALL PLUS SIGN to SMALL EQUALS SIGN
841        }
842        if (c >= '\uFF10' && c <= '\uFF19') {
843            return true; // WIDE DIGITS
844        }
845
846        return false;
847    }
848
849/*
850    private static void dump(byte[] data, int count, String label) {
851        if (false) {
852            System.out.print(label);
853
854            for (int i = 0; i < count; i++)
855                System.out.print(" " + data[i]);
856
857            System.out.println();
858        }
859    }
860*/
861
862    private static int getFit(TextPaint paint,
863                              TextPaint workPaint,
864                       CharSequence text, int start, int end,
865                       float wid) {
866        int high = end + 1, low = start - 1, guess;
867
868        while (high - low > 1) {
869            guess = (high + low) / 2;
870
871            if (measureText(paint, workPaint,
872                            text, start, guess, null, true, null) > wid)
873                high = guess;
874            else
875                low = guess;
876        }
877
878        if (low < start)
879            return start;
880        else
881            return low;
882    }
883
884    private int out(CharSequence text, int start, int end,
885                      int above, int below, int top, int bottom, int v,
886                      float spacingmult, float spacingadd,
887                      LineHeightSpan[] chooseht, int[] choosehtv,
888                      Paint.FontMetricsInt fm, boolean tab,
889                      boolean needMultiply, int pstart, byte[] chdirs,
890                      int dir, boolean easy, boolean last,
891                      boolean includepad, boolean trackpad,
892                      float[] widths, int widstart, int widoff,
893                      TextUtils.TruncateAt ellipsize, float ellipsiswidth,
894                      float textwidth, TextPaint paint) {
895        int j = mLineCount;
896        int off = j * mColumns;
897        int want = off + mColumns + TOP;
898        int[] lines = mLines;
899
900        // Log.e("text", "line " + start + " to " + end + (last ? "===" : ""));
901
902        if (want >= lines.length) {
903            int nlen = ArrayUtils.idealIntArraySize(want + 1);
904            int[] grow = new int[nlen];
905            System.arraycopy(lines, 0, grow, 0, lines.length);
906            mLines = grow;
907            lines = grow;
908
909            Directions[] grow2 = new Directions[nlen];
910            System.arraycopy(mLineDirections, 0, grow2, 0,
911                             mLineDirections.length);
912            mLineDirections = grow2;
913        }
914
915        if (chooseht != null) {
916            fm.ascent = above;
917            fm.descent = below;
918            fm.top = top;
919            fm.bottom = bottom;
920
921            for (int i = 0; i < chooseht.length; i++) {
922                chooseht[i].chooseHeight(text, start, end, choosehtv[i], v, fm);
923            }
924
925            above = fm.ascent;
926            below = fm.descent;
927            top = fm.top;
928            bottom = fm.bottom;
929        }
930
931        if (j == 0) {
932            if (trackpad) {
933                mTopPadding = top - above;
934            }
935
936            if (includepad) {
937                above = top;
938            }
939        }
940        if (last) {
941            if (trackpad) {
942                mBottomPadding = bottom - below;
943            }
944
945            if (includepad) {
946                below = bottom;
947            }
948        }
949
950        int extra;
951
952        if (needMultiply) {
953            extra = (int) ((below - above) * (spacingmult - 1)
954                           + spacingadd + 0.5);
955        } else {
956            extra = 0;
957        }
958
959        lines[off + START] = start;
960        lines[off + TOP] = v;
961        lines[off + DESCENT] = below + extra;
962
963        v += (below - above) + extra;
964        lines[off + mColumns + START] = end;
965        lines[off + mColumns + TOP] = v;
966
967        if (tab)
968            lines[off + TAB] |= TAB_MASK;
969
970        {
971            lines[off + DIR] |= dir << DIR_SHIFT;
972
973            int cur = Character.DIRECTIONALITY_LEFT_TO_RIGHT;
974            int count = 0;
975
976            if (!easy) {
977                for (int k = start; k < end; k++) {
978                    if (chdirs[k - pstart] != cur) {
979                        count++;
980                        cur = chdirs[k - pstart];
981                    }
982                }
983            }
984
985            Directions linedirs;
986
987            if (count == 0) {
988                linedirs = DIRS_ALL_LEFT_TO_RIGHT;
989            } else {
990                short[] ld = new short[count + 1];
991
992                cur = Character.DIRECTIONALITY_LEFT_TO_RIGHT;
993                count = 0;
994                int here = start;
995
996                for (int k = start; k < end; k++) {
997                    if (chdirs[k - pstart] != cur) {
998                        // XXX check to make sure we don't
999                        //     overflow short
1000                        ld[count++] = (short) (k - here);
1001                        cur = chdirs[k - pstart];
1002                        here = k;
1003                    }
1004                }
1005
1006                ld[count] = (short) (end - here);
1007
1008                if (count == 1 && ld[0] == 0) {
1009                    linedirs = DIRS_ALL_RIGHT_TO_LEFT;
1010                } else {
1011                    linedirs = new Directions(ld);
1012                }
1013            }
1014
1015            mLineDirections[j] = linedirs;
1016
1017            // If ellipsize is in marquee mode, do not apply ellipsis on the first line
1018            if (ellipsize != null && (ellipsize != TextUtils.TruncateAt.MARQUEE || j != 0)) {
1019                calculateEllipsis(start, end, widths, widstart, widoff,
1020                                  ellipsiswidth, ellipsize, j,
1021                                  textwidth, paint);
1022            }
1023        }
1024
1025        mLineCount++;
1026        return v;
1027    }
1028
1029    private void calculateEllipsis(int linestart, int lineend,
1030                                   float[] widths, int widstart, int widoff,
1031                                   float avail, TextUtils.TruncateAt where,
1032                                   int line, float textwidth, TextPaint paint) {
1033        int len = lineend - linestart;
1034
1035        if (textwidth <= avail) {
1036            // Everything fits!
1037            mLines[mColumns * line + ELLIPSIS_START] = 0;
1038            mLines[mColumns * line + ELLIPSIS_COUNT] = 0;
1039            return;
1040        }
1041
1042        float ellipsiswid = paint.measureText("\u2026");
1043        int ellipsisStart, ellipsisCount;
1044
1045        if (where == TextUtils.TruncateAt.START) {
1046            float sum = 0;
1047            int i;
1048
1049            for (i = len; i >= 0; i--) {
1050                float w = widths[i - 1 + linestart - widstart + widoff];
1051
1052                if (w + sum + ellipsiswid > avail) {
1053                    break;
1054                }
1055
1056                sum += w;
1057            }
1058
1059            ellipsisStart = 0;
1060            ellipsisCount = i;
1061        } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE) {
1062            float sum = 0;
1063            int i;
1064
1065            for (i = 0; i < len; i++) {
1066                float w = widths[i + linestart - widstart + widoff];
1067
1068                if (w + sum + ellipsiswid > avail) {
1069                    break;
1070                }
1071
1072                sum += w;
1073            }
1074
1075            ellipsisStart = i;
1076            ellipsisCount = len - i;
1077        } else /* where = TextUtils.TruncateAt.MIDDLE */ {
1078            float lsum = 0, rsum = 0;
1079            int left = 0, right = len;
1080
1081            float ravail = (avail - ellipsiswid) / 2;
1082            for (right = len; right >= 0; right--) {
1083                float w = widths[right - 1 + linestart - widstart + widoff];
1084
1085                if (w + rsum > ravail) {
1086                    break;
1087                }
1088
1089                rsum += w;
1090            }
1091
1092            float lavail = avail - ellipsiswid - rsum;
1093            for (left = 0; left < right; left++) {
1094                float w = widths[left + linestart - widstart + widoff];
1095
1096                if (w + lsum > lavail) {
1097                    break;
1098                }
1099
1100                lsum += w;
1101            }
1102
1103            ellipsisStart = left;
1104            ellipsisCount = right - left;
1105        }
1106
1107        mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
1108        mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
1109    }
1110
1111    // Override the baseclass so we can directly access our members,
1112    // rather than relying on member functions.
1113    // The logic mirrors that of Layout.getLineForVertical
1114    // FIXME: It may be faster to do a linear search for layouts without many lines.
1115    public int getLineForVertical(int vertical) {
1116        int high = mLineCount;
1117        int low = -1;
1118        int guess;
1119        int[] lines = mLines;
1120        while (high - low > 1) {
1121            guess = (high + low) >> 1;
1122            if (lines[mColumns * guess + TOP] > vertical){
1123                high = guess;
1124            } else {
1125                low = guess;
1126            }
1127        }
1128        if (low < 0) {
1129            return 0;
1130        } else {
1131            return low;
1132        }
1133    }
1134
1135    public int getLineCount() {
1136        return mLineCount;
1137    }
1138
1139    public int getLineTop(int line) {
1140        return mLines[mColumns * line + TOP];
1141    }
1142
1143    public int getLineDescent(int line) {
1144        return mLines[mColumns * line + DESCENT];
1145    }
1146
1147    public int getLineStart(int line) {
1148        return mLines[mColumns * line + START] & START_MASK;
1149    }
1150
1151    public int getParagraphDirection(int line) {
1152        return mLines[mColumns * line + DIR] >> DIR_SHIFT;
1153    }
1154
1155    public boolean getLineContainsTab(int line) {
1156        return (mLines[mColumns * line + TAB] & TAB_MASK) != 0;
1157    }
1158
1159    public final Directions getLineDirections(int line) {
1160        return mLineDirections[line];
1161    }
1162
1163    public int getTopPadding() {
1164        return mTopPadding;
1165    }
1166
1167    public int getBottomPadding() {
1168        return mBottomPadding;
1169    }
1170
1171    @Override
1172    public int getEllipsisCount(int line) {
1173        if (mColumns < COLUMNS_ELLIPSIZE) {
1174            return 0;
1175        }
1176
1177        return mLines[mColumns * line + ELLIPSIS_COUNT];
1178    }
1179
1180    @Override
1181    public int getEllipsisStart(int line) {
1182        if (mColumns < COLUMNS_ELLIPSIZE) {
1183            return 0;
1184        }
1185
1186        return mLines[mColumns * line + ELLIPSIS_START];
1187    }
1188
1189    @Override
1190    public int getEllipsizedWidth() {
1191        return mEllipsizedWidth;
1192    }
1193
1194    private int mLineCount;
1195    private int mTopPadding, mBottomPadding;
1196    private int mColumns;
1197    private int mEllipsizedWidth;
1198
1199    private static final int COLUMNS_NORMAL = 3;
1200    private static final int COLUMNS_ELLIPSIZE = 5;
1201    private static final int START = 0;
1202    private static final int DIR = START;
1203    private static final int TAB = START;
1204    private static final int TOP = 1;
1205    private static final int DESCENT = 2;
1206    private static final int ELLIPSIS_START = 3;
1207    private static final int ELLIPSIS_COUNT = 4;
1208
1209    private int[] mLines;
1210    private Directions[] mLineDirections;
1211
1212    private static final int START_MASK = 0x1FFFFFFF;
1213    private static final int DIR_MASK   = 0xC0000000;
1214    private static final int DIR_SHIFT  = 30;
1215    private static final int TAB_MASK   = 0x20000000;
1216
1217    private static final char FIRST_RIGHT_TO_LEFT = '\u0590';
1218
1219    /*
1220     * These are reused across calls to generate()
1221     */
1222    private byte[] mChdirs;
1223    private char[] mChs;
1224    private float[] mWidths;
1225    private Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
1226}
1227