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