StaticLayout.java revision 54b6cfa9a9e5b861a9930af873580d6dc20f773c
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.Paint;
20import com.android.internal.util.ArrayUtils;
21import android.util.Log;
22import android.text.style.LeadingMarginSpan;
23import android.text.style.LineHeightSpan;
24import android.text.style.MetricAffectingSpan;
25import android.text.style.ReplacementSpan;
26
27/**
28 * StaticLayout is a Layout for text that will not be edited after it
29 * is laid out.  Use {@link DynamicLayout} for text that may change.
30 * <p>This is used by widgets to control text layout. You should not need
31 * to use this class directly unless you are implementing your own widget
32 * or custom display object, or would be tempted to call
33 * {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int, float, float, android.graphics.Paint)
34 *  Canvas.drawText()} directly.</p>
35 */
36public class
37StaticLayout
38extends Layout
39{
40    public StaticLayout(CharSequence source, TextPaint paint,
41                        int width,
42                        Alignment align, float spacingmult, float spacingadd,
43                        boolean includepad) {
44        this(source, 0, source.length(), paint, width, align,
45             spacingmult, spacingadd, includepad);
46    }
47
48    public StaticLayout(CharSequence source, int bufstart, int bufend,
49                        TextPaint paint, int outerwidth,
50                        Alignment align,
51                        float spacingmult, float spacingadd,
52                        boolean includepad) {
53        this(source, bufstart, bufend, paint, outerwidth, align,
54             spacingmult, spacingadd, includepad, null, 0);
55    }
56
57    public StaticLayout(CharSequence source, int bufstart, int bufend,
58                        TextPaint paint, int outerwidth,
59                        Alignment align,
60                        float spacingmult, float spacingadd,
61                        boolean includepad,
62                        TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
63        super((ellipsize == null)
64                ? source
65                : (source instanceof Spanned)
66                    ? new SpannedEllipsizer(source)
67                    : new Ellipsizer(source),
68              paint, outerwidth, align, spacingmult, spacingadd);
69
70        /*
71         * This is annoying, but we can't refer to the layout until
72         * superclass construction is finished, and the superclass
73         * constructor wants the reference to the display text.
74         *
75         * This will break if the superclass constructor ever actually
76         * cares about the content instead of just holding the reference.
77         */
78        if (ellipsize != null) {
79            Ellipsizer e = (Ellipsizer) getText();
80
81            e.mLayout = this;
82            e.mWidth = ellipsizedWidth;
83            e.mMethod = ellipsize;
84            mEllipsizedWidth = ellipsizedWidth;
85
86            mColumns = COLUMNS_ELLIPSIZE;
87        } else {
88            mColumns = COLUMNS_NORMAL;
89            mEllipsizedWidth = outerwidth;
90        }
91
92        mLines = new int[ArrayUtils.idealIntArraySize(2 * mColumns)];
93        mLineDirections = new Directions[
94                             ArrayUtils.idealIntArraySize(2 * mColumns)];
95
96        generate(source, bufstart, bufend, paint, outerwidth, align,
97                 spacingmult, spacingadd, includepad, includepad,
98                 ellipsize != null, ellipsizedWidth, ellipsize);
99
100        mChdirs = null;
101        mChs = null;
102        mWidths = null;
103        mFontMetricsInt = null;
104    }
105
106    /* package */ StaticLayout(boolean ellipsize) {
107        super(null, null, 0, null, 0, 0);
108
109        mColumns = COLUMNS_ELLIPSIZE;
110        mLines = new int[ArrayUtils.idealIntArraySize(2 * mColumns)];
111        mLineDirections = new Directions[
112                             ArrayUtils.idealIntArraySize(2 * mColumns)];
113    }
114
115    /* package */ void generate(CharSequence source, int bufstart, int bufend,
116                        TextPaint paint, int outerwidth,
117                        Alignment align,
118                        float spacingmult, float spacingadd,
119                        boolean includepad, boolean trackpad,
120                        boolean breakOnlyAtSpaces,
121                        float ellipsizedWidth, TextUtils.TruncateAt where) {
122        mLineCount = 0;
123
124        int v = 0;
125        boolean needMultiply = (spacingmult != 1 || spacingadd != 0);
126
127        Paint.FontMetricsInt fm = mFontMetricsInt;
128        int[] choosehtv = null;
129
130        int end = TextUtils.indexOf(source, '\n', bufstart, bufend);
131        int bufsiz = end >= 0 ? end - bufstart : bufend - bufstart;
132        boolean first = true;
133
134        if (mChdirs == null) {
135            mChdirs = new byte[ArrayUtils.idealByteArraySize(bufsiz + 1)];
136            mChs = new char[ArrayUtils.idealCharArraySize(bufsiz + 1)];
137            mWidths = new float[ArrayUtils.idealIntArraySize((bufsiz + 1) * 2)];
138        }
139
140        byte[] chdirs = mChdirs;
141        char[] chs = mChs;
142        float[] widths = mWidths;
143
144        AlteredCharSequence alter = null;
145        Spanned spanned = null;
146
147        if (source instanceof Spanned)
148            spanned = (Spanned) source;
149
150        int DEFAULT_DIR = DIR_LEFT_TO_RIGHT; // XXX
151
152        for (int start = bufstart; start <= bufend; start = end) {
153            if (first)
154                first = false;
155            else
156                end = TextUtils.indexOf(source, '\n', start, bufend);
157
158            if (end < 0)
159                end = bufend;
160            else
161                end++;
162
163            int firstwidth = outerwidth;
164            int restwidth = outerwidth;
165
166            LineHeightSpan[] chooseht = null;
167
168            if (spanned != null) {
169                LeadingMarginSpan[] sp;
170
171                sp = spanned.getSpans(start, end, LeadingMarginSpan.class);
172                for (int i = 0; i < sp.length; i++) {
173                    firstwidth -= sp[i].getLeadingMargin(true);
174                    restwidth -= sp[i].getLeadingMargin(false);
175                }
176
177                chooseht = spanned.getSpans(start, end, LineHeightSpan.class);
178
179                if (chooseht.length != 0) {
180                    if (choosehtv == null ||
181                        choosehtv.length < chooseht.length) {
182                        choosehtv = new int[ArrayUtils.idealIntArraySize(
183                                            chooseht.length)];
184                    }
185
186                    for (int i = 0; i < chooseht.length; i++) {
187                        int o = spanned.getSpanStart(chooseht[i]);
188
189                        if (o < start) {
190                            // starts in this layout, before the
191                            // current paragraph
192
193                            choosehtv[i] = getLineTop(getLineForOffset(o));
194                        } else {
195                            // starts in this paragraph
196
197                            choosehtv[i] = v;
198                        }
199                    }
200                }
201            }
202
203            if (end - start > chdirs.length) {
204                chdirs = new byte[ArrayUtils.idealByteArraySize(end - start)];
205                mChdirs = chdirs;
206            }
207            if (end - start > chs.length) {
208                chs = new char[ArrayUtils.idealCharArraySize(end - start)];
209                mChs = chs;
210            }
211            if ((end - start) * 2 > widths.length) {
212                widths = new float[ArrayUtils.idealIntArraySize((end - start) * 2)];
213                mWidths = widths;
214            }
215
216            TextUtils.getChars(source, start, end, chs, 0);
217            final int n = end - start;
218
219            boolean easy = true;
220            boolean altered = false;
221            int dir = DEFAULT_DIR; // XXX
222
223            for (int i = 0; i < n; i++) {
224                if (chs[i] >= FIRST_RIGHT_TO_LEFT) {
225                    easy = false;
226                    break;
227                }
228            }
229
230            if (!easy) {
231                AndroidCharacter.getDirectionalities(chs, chdirs, end - start);
232
233                /*
234                 * Determine primary paragraph direction
235                 */
236
237                for (int j = start; j < end; j++) {
238                    int d = chdirs[j - start];
239
240                    if (d == Character.DIRECTIONALITY_LEFT_TO_RIGHT) {
241                        dir = DIR_LEFT_TO_RIGHT;
242                        break;
243                    }
244                    if (d == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
245                        dir = DIR_RIGHT_TO_LEFT;
246                        break;
247                    }
248                }
249
250                /*
251                 * XXX Explicit overrides should go here
252                 */
253
254                /*
255                 * Weak type resolution
256                 */
257
258                final byte SOR = dir == DIR_LEFT_TO_RIGHT ?
259                                    Character.DIRECTIONALITY_LEFT_TO_RIGHT :
260                                    Character.DIRECTIONALITY_RIGHT_TO_LEFT;
261
262                // dump(chdirs, n, "initial");
263
264                // W1 non spacing marks
265                for (int j = 0; j < n; j++) {
266                    if (chdirs[j] == Character.NON_SPACING_MARK) {
267                        if (j == 0)
268                            chdirs[j] = SOR;
269                        else
270                            chdirs[j] = chdirs[j - 1];
271                    }
272                }
273
274                // dump(chdirs, n, "W1");
275
276                // W2 european numbers
277                byte cur = SOR;
278                for (int j = 0; j < n; j++) {
279                    byte d = chdirs[j];
280
281                    if (d == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
282                        d == Character.DIRECTIONALITY_RIGHT_TO_LEFT ||
283                        d == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC)
284                        cur = d;
285                    else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER) {
286                         if (cur ==
287                            Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC)
288                            chdirs[j] = Character.DIRECTIONALITY_ARABIC_NUMBER;
289                    }
290                }
291
292                // dump(chdirs, n, "W2");
293
294                // W3 arabic letters
295                for (int j = 0; j < n; j++) {
296                    if (chdirs[j] == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC)
297                        chdirs[j] = Character.DIRECTIONALITY_RIGHT_TO_LEFT;
298                }
299
300                // dump(chdirs, n, "W3");
301
302                // W4 single separator between numbers
303                for (int j = 1; j < n - 1; j++) {
304                    byte d = chdirs[j];
305                    byte prev = chdirs[j - 1];
306                    byte next = chdirs[j + 1];
307
308                    if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_SEPARATOR) {
309                        if (prev == Character.DIRECTIONALITY_EUROPEAN_NUMBER &&
310                            next == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
311                            chdirs[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
312                    } else if (d == Character.DIRECTIONALITY_COMMON_NUMBER_SEPARATOR) {
313                        if (prev == Character.DIRECTIONALITY_EUROPEAN_NUMBER &&
314                            next == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
315                            chdirs[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
316                        if (prev == Character.DIRECTIONALITY_ARABIC_NUMBER &&
317                            next == Character.DIRECTIONALITY_ARABIC_NUMBER)
318                            chdirs[j] = Character.DIRECTIONALITY_ARABIC_NUMBER;
319                    }
320                }
321
322                // dump(chdirs, n, "W4");
323
324                // W5 european number terminators
325                boolean adjacent = false;
326                for (int j = 0; j < n; j++) {
327                    byte d = chdirs[j];
328
329                    if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
330                        adjacent = true;
331                    else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR && adjacent)
332                        chdirs[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
333                    else
334                        adjacent = false;
335                }
336
337                //dump(chdirs, n, "W5");
338
339                // W5 european number terminators part 2,
340                // W6 separators and terminators
341                adjacent = false;
342                for (int j = n - 1; j >= 0; j--) {
343                    byte d = chdirs[j];
344
345                    if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
346                        adjacent = true;
347                    else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR) {
348                        if (adjacent)
349                            chdirs[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
350                        else
351                            chdirs[j] = Character.DIRECTIONALITY_OTHER_NEUTRALS;
352                    }
353                    else {
354                        adjacent = false;
355
356                        if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_SEPARATOR ||
357                            d == Character.DIRECTIONALITY_COMMON_NUMBER_SEPARATOR ||
358                            d == Character.DIRECTIONALITY_PARAGRAPH_SEPARATOR ||
359                            d == Character.DIRECTIONALITY_SEGMENT_SEPARATOR)
360                            chdirs[j] = Character.DIRECTIONALITY_OTHER_NEUTRALS;
361                    }
362                }
363
364                // dump(chdirs, n, "W6");
365
366                // W7 strong direction of european numbers
367                cur = SOR;
368                for (int j = 0; j < n; j++) {
369                    byte d = chdirs[j];
370
371                    if (d == SOR ||
372                        d == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
373                        d == Character.DIRECTIONALITY_RIGHT_TO_LEFT)
374                        cur = d;
375
376                    if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
377                        chdirs[j] = cur;
378                }
379
380                // dump(chdirs, n, "W7");
381
382                // N1, N2 neutrals
383                cur = SOR;
384                for (int j = 0; j < n; j++) {
385                    byte d = chdirs[j];
386
387                    if (d == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
388                        d == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
389                        cur = d;
390                    } else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER ||
391                               d == Character.DIRECTIONALITY_ARABIC_NUMBER) {
392                        cur = Character.DIRECTIONALITY_RIGHT_TO_LEFT;
393                    } else {
394                        byte dd = SOR;
395                        int k;
396
397                        for (k = j + 1; k < n; k++) {
398                            dd = chdirs[k];
399
400                            if (dd == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
401                                dd == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
402                                break;
403                            }
404                            if (dd == Character.DIRECTIONALITY_EUROPEAN_NUMBER ||
405                                dd == Character.DIRECTIONALITY_ARABIC_NUMBER) {
406                                dd = Character.DIRECTIONALITY_RIGHT_TO_LEFT;
407                                break;
408                            }
409                        }
410
411                        for (int y = j; y < k; y++) {
412                            if (dd == cur)
413                                chdirs[y] = cur;
414                            else
415                                chdirs[y] = SOR;
416                        }
417
418                        j = k - 1;
419                    }
420                }
421
422                // dump(chdirs, n, "final");
423
424                // extra: enforce that all tabs go the primary direction
425
426                for (int j = 0; j < n; j++) {
427                    if (chs[j] == '\t')
428                        chdirs[j] = SOR;
429                }
430
431                // extra: enforce that object replacements go to the
432                // primary direction
433                // and that none of the underlying characters are treated
434                // as viable breakpoints
435
436                if (source instanceof Spanned) {
437                    Spanned sp = (Spanned) source;
438                    ReplacementSpan[] spans = sp.getSpans(start, end, ReplacementSpan.class);
439
440                    for (int y = 0; y < spans.length; y++) {
441                        int a = sp.getSpanStart(spans[y]);
442                        int b = sp.getSpanEnd(spans[y]);
443
444                        for (int x = a; x < b; x++) {
445                            chdirs[x - start] = SOR;
446                            chs[x - start] = '\uFFFC';
447                        }
448                    }
449                }
450
451                // Do mirroring for right-to-left segments
452
453                for (int i = 0; i < n; i++) {
454                    if (chdirs[i] == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
455                        int j;
456
457                        for (j = i; j < n; j++) {
458                            if (chdirs[j] !=
459                                Character.DIRECTIONALITY_RIGHT_TO_LEFT)
460                                break;
461                        }
462
463                        if (AndroidCharacter.mirror(chs, i, j - i))
464                            altered = true;
465
466                        i = j - 1;
467                    }
468                }
469            }
470
471            CharSequence sub;
472
473            if (altered) {
474                if (alter == null)
475                    alter = AlteredCharSequence.make(source, chs, start, end);
476                else
477                    alter.update(chs, start, end);
478
479                sub = alter;
480            } else {
481                sub = source;
482            }
483
484            int width = firstwidth;
485
486            float w = 0;
487            int here = start;
488
489            int ok = start;
490            float okwidth = w;
491            int okascent = 0, okdescent = 0, oktop = 0, okbottom = 0;
492
493            int fit = start;
494            float fitwidth = w;
495            int fitascent = 0, fitdescent = 0, fittop = 0, fitbottom = 0;
496
497            boolean tab = false;
498
499            int next;
500            for (int i = start; i < end; i = next) {
501                if (spanned == null)
502                    next = end;
503                else
504                    next = spanned.nextSpanTransition(i, end,
505                                                      MetricAffectingSpan.
506                                                      class);
507
508                if (spanned == null) {
509                    paint.getTextWidths(sub, i, next, widths);
510                    System.arraycopy(widths, 0, widths,
511                                     end - start + (i - start), next - i);
512
513                    paint.getFontMetricsInt(fm);
514                } else {
515                    mWorkPaint.baselineShift = 0;
516
517                    Styled.getTextWidths(paint, mWorkPaint,
518                                         spanned, i, next,
519                                         widths, fm);
520                    System.arraycopy(widths, 0, widths,
521                                     end - start + (i - start), next - i);
522
523                    if (mWorkPaint.baselineShift < 0) {
524                        fm.ascent += mWorkPaint.baselineShift;
525                        fm.top += mWorkPaint.baselineShift;
526                    } else {
527                        fm.descent += mWorkPaint.baselineShift;
528                        fm.bottom += mWorkPaint.baselineShift;
529                    }
530                }
531
532                int fmtop = fm.top;
533                int fmbottom = fm.bottom;
534                int fmascent = fm.ascent;
535                int fmdescent = fm.descent;
536
537                if (false) {
538                    StringBuilder sb = new StringBuilder();
539                    for (int j = i; j < next; j++) {
540                        sb.append(widths[j - start + (end - start)]);
541                        sb.append(' ');
542                    }
543
544                    Log.e("text", sb.toString());
545                }
546
547                for (int j = i; j < next; j++) {
548                    char c = chs[j - start];
549                    float before = w;
550
551                    switch (c) {
552                    case '\n':
553                        break;
554
555                    case '\t':
556                        w = Layout.nextTab(sub, start, end, w, null);
557                        tab = true;
558                        break;
559
560                    default:
561                        w += widths[j - start + (end - start)];
562                    }
563
564                    // Log.e("text", "was " + before + " now " + w + " after " + c + " within " + width);
565
566                    if (w <= width) {
567                        fitwidth = w;
568                        fit = j + 1;
569
570                        if (fmtop < fittop)
571                            fittop = fmtop;
572                        if (fmascent < fitascent)
573                            fitascent = fmascent;
574                        if (fmdescent > fitdescent)
575                            fitdescent = fmdescent;
576                        if (fmbottom > fitbottom)
577                            fitbottom = fmbottom;
578
579                        if (c == ' ' || c == '\t') {
580                            okwidth = w;
581                            ok = j + 1;
582
583                            if (fittop < oktop)
584                                oktop = fittop;
585                            if (fitascent < okascent)
586                                okascent = fitascent;
587                            if (fitdescent > okdescent)
588                                okdescent = fitdescent;
589                            if (fitbottom > okbottom)
590                                okbottom = fitbottom;
591                        }
592                    } else if (breakOnlyAtSpaces) {
593                        if (ok != here) {
594                            // Log.e("text", "output ok " + here + " to " +ok);
595                            v = out(source,
596                                    here, ok,
597                                    okascent, okdescent, oktop, okbottom,
598                                    v,
599                                    spacingmult, spacingadd, chooseht,
600                                    choosehtv, fm, tab,
601                                    needMultiply, start, chdirs, dir, easy,
602                                    ok == bufend, includepad, trackpad,
603                                    widths, start, end - start,
604                                    where, ellipsizedWidth, okwidth,
605                                    paint);
606
607                            here = ok;
608                        } else {
609                            // Act like it fit even though it didn't.
610
611                            fitwidth = w;
612                            fit = j + 1;
613
614                            if (fmtop < fittop)
615                                fittop = fmtop;
616                            if (fmascent < fitascent)
617                                fitascent = fmascent;
618                            if (fmdescent > fitdescent)
619                                fitdescent = fmdescent;
620                            if (fmbottom > fitbottom)
621                                fitbottom = fmbottom;
622                        }
623                    } else {
624                        if (ok != here) {
625                            // Log.e("text", "output ok " + here + " to " +ok);
626                            v = out(source,
627                                    here, ok,
628                                    okascent, okdescent, oktop, okbottom,
629                                    v,
630                                    spacingmult, spacingadd, chooseht,
631                                    choosehtv, fm, tab,
632                                    needMultiply, start, chdirs, dir, easy,
633                                    ok == bufend, includepad, trackpad,
634                                    widths, start, end - start,
635                                    where, ellipsizedWidth, okwidth,
636                                    paint);
637
638                            here = ok;
639                        } else if (fit != here) {
640                            // Log.e("text", "output fit " + here + " to " +fit);
641                            v = out(source,
642                                    here, fit,
643                                    fitascent, fitdescent,
644                                    fittop, fitbottom,
645                                    v,
646                                    spacingmult, spacingadd, chooseht,
647                                    choosehtv, fm, tab,
648                                    needMultiply, start, chdirs, dir, easy,
649                                    fit == bufend, includepad, trackpad,
650                                    widths, start, end - start,
651                                    where, ellipsizedWidth, fitwidth,
652                                    paint);
653
654                            here = fit;
655                        } else {
656                            // Log.e("text", "output one " + here + " to " +(here + 1));
657                            measureText(paint, mWorkPaint,
658                                        source, here, here + 1, fm, tab,
659                                        null);
660
661                            v = out(source,
662                                    here, here+1,
663                                    fm.ascent, fm.descent,
664                                    fm.top, fm.bottom,
665                                    v,
666                                    spacingmult, spacingadd, chooseht,
667                                    choosehtv, fm, tab,
668                                    needMultiply, start, chdirs, dir, easy,
669                                    here + 1 == bufend, includepad,
670                                    trackpad,
671                                    widths, start, end - start,
672                                    where, ellipsizedWidth,
673                                    widths[here - start], paint);
674
675                            here = here + 1;
676                        }
677
678                        if (here < i) {
679                            j = next = here; // must remeasure
680                        } else {
681                            j = here - 1;    // continue looping
682                        }
683
684                        ok = fit = here;
685                        w = 0;
686                        fitascent = fitdescent = fittop = fitbottom = 0;
687                        okascent = okdescent = oktop = okbottom = 0;
688
689                        width = restwidth;
690                    }
691                }
692            }
693
694            if (end != here) {
695                if ((fittop | fitbottom | fitdescent | fitascent) == 0) {
696                    paint.getFontMetricsInt(fm);
697
698                    fittop = fm.top;
699                    fitbottom = fm.bottom;
700                    fitascent = fm.ascent;
701                    fitdescent = fm.descent;
702                }
703
704                // Log.e("text", "output rest " + here + " to " + end);
705
706                v = out(source,
707                        here, end, fitascent, fitdescent,
708                        fittop, fitbottom,
709                        v,
710                        spacingmult, spacingadd, chooseht,
711                        choosehtv, fm, tab,
712                        needMultiply, start, chdirs, dir, easy,
713                        end == bufend, includepad, trackpad,
714                        widths, start, end - start,
715                        where, ellipsizedWidth, w, paint);
716            }
717
718            start = end;
719
720            if (end == bufend)
721                break;
722        }
723
724        if (bufend == bufstart || source.charAt(bufend - 1) == '\n') {
725            // Log.e("text", "output last " + bufend);
726
727            paint.getFontMetricsInt(fm);
728
729            v = out(source,
730                    bufend, bufend, fm.ascent, fm.descent,
731                    fm.top, fm.bottom,
732                    v,
733                    spacingmult, spacingadd, null,
734                    null, fm, false,
735                    needMultiply, bufend, chdirs, DEFAULT_DIR, true,
736                    true, includepad, trackpad,
737                    widths, bufstart, 0,
738                    where, ellipsizedWidth, 0, paint);
739        }
740    }
741
742/*
743    private static void dump(byte[] data, int count, String label) {
744        if (false) {
745            System.out.print(label);
746
747            for (int i = 0; i < count; i++)
748                System.out.print(" " + data[i]);
749
750            System.out.println();
751        }
752    }
753*/
754
755    private static int getFit(TextPaint paint,
756                              TextPaint workPaint,
757                       CharSequence text, int start, int end,
758                       float wid) {
759        int high = end + 1, low = start - 1, guess;
760
761        while (high - low > 1) {
762            guess = (high + low) / 2;
763
764            if (measureText(paint, workPaint,
765                            text, start, guess, null, true, null) > wid)
766                high = guess;
767            else
768                low = guess;
769        }
770
771        if (low < start)
772            return start;
773        else
774            return low;
775    }
776
777    private int out(CharSequence text, int start, int end,
778                      int above, int below, int top, int bottom, int v,
779                      float spacingmult, float spacingadd,
780                      LineHeightSpan[] chooseht, int[] choosehtv,
781                      Paint.FontMetricsInt fm, boolean tab,
782                      boolean needMultiply, int pstart, byte[] chdirs,
783                      int dir, boolean easy, boolean last,
784                      boolean includepad, boolean trackpad,
785                      float[] widths, int widstart, int widoff,
786                      TextUtils.TruncateAt ellipsize, float ellipsiswidth,
787                      float textwidth, TextPaint paint) {
788        int j = mLineCount;
789        int off = j * mColumns;
790        int want = off + mColumns + TOP;
791        int[] lines = mLines;
792
793        // Log.e("text", "line " + start + " to " + end + (last ? "===" : ""));
794
795        if (want >= lines.length) {
796            int nlen = ArrayUtils.idealIntArraySize(want + 1);
797            int[] grow = new int[nlen];
798            System.arraycopy(lines, 0, grow, 0, lines.length);
799            mLines = grow;
800            lines = grow;
801
802            Directions[] grow2 = new Directions[nlen];
803            System.arraycopy(mLineDirections, 0, grow2, 0,
804                             mLineDirections.length);
805            mLineDirections = grow2;
806        }
807
808        if (chooseht != null) {
809            fm.ascent = above;
810            fm.descent = below;
811            fm.top = top;
812            fm.bottom = bottom;
813
814            for (int i = 0; i < chooseht.length; i++) {
815                chooseht[i].chooseHeight(text, start, end, choosehtv[i], v, fm);
816            }
817
818            above = fm.ascent;
819            below = fm.descent;
820            top = fm.top;
821            bottom = fm.bottom;
822        }
823
824        if (j == 0) {
825            if (trackpad) {
826                mTopPadding = top - above;
827            }
828
829            if (includepad) {
830                above = top;
831            }
832        }
833        if (last) {
834            if (trackpad) {
835                mBottomPadding = bottom - below;
836            }
837
838            if (includepad) {
839                below = bottom;
840            }
841        }
842
843        int extra;
844
845        if (needMultiply) {
846            extra = (int) ((below - above) * (spacingmult - 1)
847                           + spacingadd + 0.5);
848        } else {
849            extra = 0;
850        }
851
852        lines[off + START] = start;
853        lines[off + TOP] = v;
854        lines[off + DESCENT] = below + extra;
855
856        v += (below - above) + extra;
857        lines[off + mColumns + START] = end;
858        lines[off + mColumns + TOP] = v;
859
860        if (tab)
861            lines[off + TAB] |= TAB_MASK;
862
863        {
864            lines[off + DIR] |= dir << DIR_SHIFT;
865
866            int cur = Character.DIRECTIONALITY_LEFT_TO_RIGHT;
867            int count = 0;
868
869            if (!easy) {
870                for (int k = start; k < end; k++) {
871                    if (chdirs[k - pstart] != cur) {
872                        count++;
873                        cur = chdirs[k - pstart];
874                    }
875                }
876            }
877
878            Directions linedirs;
879
880            if (count == 0) {
881                linedirs = DIRS_ALL_LEFT_TO_RIGHT;
882            } else {
883                short[] ld = new short[count + 1];
884
885                cur = Character.DIRECTIONALITY_LEFT_TO_RIGHT;
886                count = 0;
887                int here = start;
888
889                for (int k = start; k < end; k++) {
890                    if (chdirs[k - pstart] != cur) {
891                        // XXX check to make sure we don't
892                        //     overflow short
893                        ld[count++] = (short) (k - here);
894                        cur = chdirs[k - pstart];
895                        here = k;
896                    }
897                }
898
899                ld[count] = (short) (end - here);
900
901                if (count == 1 && ld[0] == 0) {
902                    linedirs = DIRS_ALL_RIGHT_TO_LEFT;
903                } else {
904                    linedirs = new Directions(ld);
905                }
906            }
907
908            mLineDirections[j] = linedirs;
909
910            if (ellipsize != null) {
911                calculateEllipsis(start, end, widths, widstart, widoff,
912                                  ellipsiswidth, ellipsize, j,
913                                  textwidth, paint);
914            }
915        }
916
917        mLineCount++;
918        return v;
919    }
920
921    private void calculateEllipsis(int linestart, int lineend,
922                                   float[] widths, int widstart, int widoff,
923                                   float avail, TextUtils.TruncateAt where,
924                                   int line, float textwidth, TextPaint paint) {
925        int len = lineend - linestart;
926
927        if (textwidth <= avail) {
928            // Everything fits!
929            mLines[mColumns * line + ELLIPSIS_START] = 0;
930            mLines[mColumns * line + ELLIPSIS_COUNT] = 0;
931            return;
932        }
933
934        float ellipsiswid = paint.measureText("\u2026");
935        int ellipsisStart, ellipsisCount;
936
937        if (where == TextUtils.TruncateAt.START) {
938            float sum = 0;
939            int i;
940
941            for (i = len; i >= 0; i--) {
942                float w = widths[i - 1 + linestart - widstart + widoff];
943
944                if (w + sum + ellipsiswid > avail) {
945                    break;
946                }
947
948                sum += w;
949            }
950
951            ellipsisStart = 0;
952            ellipsisCount = i;
953        } else if (where == TextUtils.TruncateAt.END) {
954            float sum = 0;
955            int i;
956
957            for (i = 0; i < len; i++) {
958                float w = widths[i + linestart - widstart + widoff];
959
960                if (w + sum + ellipsiswid > avail) {
961                    break;
962                }
963
964                sum += w;
965            }
966
967            ellipsisStart = i;
968            ellipsisCount = len - i;
969        } else /* where = TextUtils.TruncateAt.MIDDLE */ {
970            float lsum = 0, rsum = 0;
971            int left = 0, right = len;
972
973            float ravail = (avail - ellipsiswid) / 2;
974            for (right = len; right >= 0; right--) {
975                float w = widths[right - 1 + linestart - widstart + widoff];
976
977                if (w + rsum > ravail) {
978                    break;
979                }
980
981                rsum += w;
982            }
983
984            float lavail = avail - ellipsiswid - rsum;
985            for (left = 0; left < right; left++) {
986                float w = widths[left + linestart - widstart + widoff];
987
988                if (w + lsum > lavail) {
989                    break;
990                }
991
992                lsum += w;
993            }
994
995            ellipsisStart = left;
996            ellipsisCount = right - left;
997        }
998
999        mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
1000        mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
1001    }
1002
1003    // Override the baseclass so we can directly access our members,
1004    // rather than relying on member functions.
1005    // The logic mirrors that of Layout.getLineForVertical
1006    // FIXME: It may be faster to do a linear search for layouts without many lines.
1007    public int getLineForVertical(int vertical) {
1008        int high = mLineCount;
1009        int low = -1;
1010        int guess;
1011        int[] lines = mLines;
1012        while (high - low > 1) {
1013            guess = (high + low) >> 1;
1014            if (lines[mColumns * guess + TOP] > vertical){
1015                high = guess;
1016            } else {
1017                low = guess;
1018            }
1019        }
1020        if (low < 0) {
1021            return 0;
1022        } else {
1023            return low;
1024        }
1025    }
1026
1027    public int getLineCount() {
1028        return mLineCount;
1029    }
1030
1031    public int getLineTop(int line) {
1032        return mLines[mColumns * line + TOP];
1033    }
1034
1035    public int getLineDescent(int line) {
1036        return mLines[mColumns * line + DESCENT];
1037    }
1038
1039    public int getLineStart(int line) {
1040        return mLines[mColumns * line + START] & START_MASK;
1041    }
1042
1043    public int getParagraphDirection(int line) {
1044        return mLines[mColumns * line + DIR] >> DIR_SHIFT;
1045    }
1046
1047    public boolean getLineContainsTab(int line) {
1048        return (mLines[mColumns * line + TAB] & TAB_MASK) != 0;
1049    }
1050
1051    public final Directions getLineDirections(int line) {
1052        return mLineDirections[line];
1053    }
1054
1055    public int getTopPadding() {
1056        return mTopPadding;
1057    }
1058
1059    public int getBottomPadding() {
1060        return mBottomPadding;
1061    }
1062
1063    @Override
1064    public int getEllipsisCount(int line) {
1065        if (mColumns < COLUMNS_ELLIPSIZE) {
1066            return 0;
1067        }
1068
1069        return mLines[mColumns * line + ELLIPSIS_COUNT];
1070    }
1071
1072    @Override
1073    public int getEllipsisStart(int line) {
1074        if (mColumns < COLUMNS_ELLIPSIZE) {
1075            return 0;
1076        }
1077
1078        return mLines[mColumns * line + ELLIPSIS_START];
1079    }
1080
1081    @Override
1082    public int getEllipsizedWidth() {
1083        return mEllipsizedWidth;
1084    }
1085
1086    private int mLineCount;
1087    private int mTopPadding, mBottomPadding;
1088    private int mColumns;
1089    private int mEllipsizedWidth;
1090
1091    private static final int COLUMNS_NORMAL = 3;
1092    private static final int COLUMNS_ELLIPSIZE = 5;
1093    private static final int START = 0;
1094    private static final int DIR = START;
1095    private static final int TAB = START;
1096    private static final int TOP = 1;
1097    private static final int DESCENT = 2;
1098    private static final int ELLIPSIS_START = 3;
1099    private static final int ELLIPSIS_COUNT = 4;
1100
1101    private int[] mLines;
1102    private Directions[] mLineDirections;
1103
1104    private static final int START_MASK = 0x1FFFFFFF;
1105    private static final int DIR_MASK   = 0xC0000000;
1106    private static final int DIR_SHIFT  = 30;
1107    private static final int TAB_MASK   = 0x20000000;
1108
1109    private static final char FIRST_RIGHT_TO_LEFT = '\u0590';
1110
1111    /*
1112     * These are reused across calls to generate()
1113     */
1114    private byte[] mChdirs;
1115    private char[] mChs;
1116    private float[] mWidths;
1117    private Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
1118}
1119