[go: nahoru, domu]

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.annotation.Nullable;
20import android.graphics.Paint;
21import android.text.style.LeadingMarginSpan;
22import android.text.style.LeadingMarginSpan.LeadingMarginSpan2;
23import android.text.style.LineHeightSpan;
24import android.text.style.MetricAffectingSpan;
25import android.text.style.TabStopSpan;
26import android.util.Log;
27import android.util.Pools.SynchronizedPool;
28
29import com.android.internal.util.ArrayUtils;
30import com.android.internal.util.GrowingArrayUtils;
31
32import java.nio.ByteBuffer;
33import java.util.Arrays;
34import java.util.Locale;
35
36/**
37 * StaticLayout is a Layout for text that will not be edited after it
38 * is laid out.  Use {@link DynamicLayout} for text that may change.
39 * <p>This is used by widgets to control text layout. You should not need
40 * to use this class directly unless you are implementing your own widget
41 * or custom display object, or would be tempted to call
42 * {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int,
43 * float, float, android.graphics.Paint)
44 * Canvas.drawText()} directly.</p>
45 */
46public class StaticLayout extends Layout {
47
48    static final String TAG = "StaticLayout";
49
50    /**
51     * Builder for static layouts. The builder is a newer pattern for constructing
52     * StaticLayout objects and should be preferred over the constructors,
53     * particularly to access newer features. To build a static layout, first
54     * call {@link #obtain} with the required arguments (text, paint, and width),
55     * then call setters for optional parameters, and finally {@link #build}
56     * to build the StaticLayout object. Parameters not explicitly set will get
57     * default values.
58     */
59    public final static class Builder {
60        private Builder() {
61            mNativePtr = nNewBuilder();
62        }
63
64        /**
65         * Obtain a builder for constructing StaticLayout objects
66         *
67         * @param source The text to be laid out, optionally with spans
68         * @param start The index of the start of the text
69         * @param end The index + 1 of the end of the text
70         * @param paint The base paint used for layout
71         * @param width The width in pixels
72         * @return a builder object used for constructing the StaticLayout
73         */
74        public static Builder obtain(CharSequence source, int start, int end, TextPaint paint,
75                int width) {
76            Builder b = sPool.acquire();
77            if (b == null) {
78                b = new Builder();
79            }
80
81            // set default initial values
82            b.mText = source;
83            b.mStart = start;
84            b.mEnd = end;
85            b.mPaint = paint;
86            b.mWidth = width;
87            b.mAlignment = Alignment.ALIGN_NORMAL;
88            b.mTextDir = TextDirectionHeuristics.FIRSTSTRONG_LTR;
89            b.mSpacingMult = 1.0f;
90            b.mSpacingAdd = 0.0f;
91            b.mIncludePad = true;
92            b.mEllipsizedWidth = width;
93            b.mEllipsize = null;
94            b.mMaxLines = Integer.MAX_VALUE;
95            b.mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
96            b.mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
97
98            b.mMeasuredText = MeasuredText.obtain();
99            return b;
100        }
101
102        private static void recycle(Builder b) {
103            b.mPaint = null;
104            b.mText = null;
105            MeasuredText.recycle(b.mMeasuredText);
106            b.mMeasuredText = null;
107            b.mLeftIndents = null;
108            b.mRightIndents = null;
109            nFinishBuilder(b.mNativePtr);
110            sPool.release(b);
111        }
112
113        // release any expensive state
114        /* package */ void finish() {
115            nFinishBuilder(mNativePtr);
116            mText = null;
117            mPaint = null;
118            mLeftIndents = null;
119            mRightIndents = null;
120            mMeasuredText.finish();
121        }
122
123        public Builder setText(CharSequence source) {
124            return setText(source, 0, source.length());
125        }
126
127        /**
128         * Set the text. Only useful when re-using the builder, which is done for
129         * the internal implementation of {@link DynamicLayout} but not as part
130         * of normal {@link StaticLayout} usage.
131         *
132         * @param source The text to be laid out, optionally with spans
133         * @param start The index of the start of the text
134         * @param end The index + 1 of the end of the text
135         * @return this builder, useful for chaining
136         *
137         * @hide
138         */
139        public Builder setText(CharSequence source, int start, int end) {
140            mText = source;
141            mStart = start;
142            mEnd = end;
143            return this;
144        }
145
146        /**
147         * Set the paint. Internal for reuse cases only.
148         *
149         * @param paint The base paint used for layout
150         * @return this builder, useful for chaining
151         *
152         * @hide
153         */
154        public Builder setPaint(TextPaint paint) {
155            mPaint = paint;
156            return this;
157        }
158
159        /**
160         * Set the width. Internal for reuse cases only.
161         *
162         * @param width The width in pixels
163         * @return this builder, useful for chaining
164         *
165         * @hide
166         */
167        public Builder setWidth(int width) {
168            mWidth = width;
169            if (mEllipsize == null) {
170                mEllipsizedWidth = width;
171            }
172            return this;
173        }
174
175        /**
176         * Set the alignment. The default is {@link Layout.Alignment#ALIGN_NORMAL}.
177         *
178         * @param alignment Alignment for the resulting {@link StaticLayout}
179         * @return this builder, useful for chaining
180         */
181        public Builder setAlignment(Alignment alignment) {
182            mAlignment = alignment;
183            return this;
184        }
185
186        /**
187         * Set the text direction heuristic. The text direction heuristic is used to
188         * resolve text direction based per-paragraph based on the input text. The default is
189         * {@link TextDirectionHeuristics#FIRSTSTRONG_LTR}.
190         *
191         * @param textDir text direction heuristic for resolving BiDi behavior.
192         * @return this builder, useful for chaining
193         */
194        public Builder setTextDirection(TextDirectionHeuristic textDir) {
195            mTextDir = textDir;
196            return this;
197        }
198
199        /**
200         * Set line spacing parameters. The default is 0.0 for {@code spacingAdd}
201         * and 1.0 for {@code spacingMult}.
202         *
203         * @param spacingAdd line spacing add
204         * @param spacingMult line spacing multiplier
205         * @return this builder, useful for chaining
206         * @see android.widget.TextView#setLineSpacing
207         */
208        public Builder setLineSpacing(float spacingAdd, float spacingMult) {
209            mSpacingAdd = spacingAdd;
210            mSpacingMult = spacingMult;
211            return this;
212        }
213
214        /**
215         * Set whether to include extra space beyond font ascent and descent (which is
216         * needed to avoid clipping in some languages, such as Arabic and Kannada). The
217         * default is {@code true}.
218         *
219         * @param includePad whether to include padding
220         * @return this builder, useful for chaining
221         * @see android.widget.TextView#setIncludeFontPadding
222         */
223        public Builder setIncludePad(boolean includePad) {
224            mIncludePad = includePad;
225            return this;
226        }
227
228        /**
229         * Set the width as used for ellipsizing purposes, if it differs from the
230         * normal layout width. The default is the {@code width}
231         * passed to {@link #obtain}.
232         *
233         * @param ellipsizedWidth width used for ellipsizing, in pixels
234         * @return this builder, useful for chaining
235         * @see android.widget.TextView#setEllipsize
236         */
237        public Builder setEllipsizedWidth(int ellipsizedWidth) {
238            mEllipsizedWidth = ellipsizedWidth;
239            return this;
240        }
241
242        /**
243         * Set ellipsizing on the layout. Causes words that are longer than the view
244         * is wide, or exceeding the number of lines (see #setMaxLines) in the case
245         * of {@link android.text.TextUtils.TruncateAt#END} or
246         * {@link android.text.TextUtils.TruncateAt#MARQUEE}, to be ellipsized instead
247         * of broken. The default is
248         * {@code null}, indicating no ellipsis is to be applied.
249         *
250         * @param ellipsize type of ellipsis behavior
251         * @return this builder, useful for chaining
252         * @see android.widget.TextView#setEllipsize
253         */
254        public Builder setEllipsize(@Nullable TextUtils.TruncateAt ellipsize) {
255            mEllipsize = ellipsize;
256            return this;
257        }
258
259        /**
260         * Set maximum number of lines. This is particularly useful in the case of
261         * ellipsizing, where it changes the layout of the last line. The default is
262         * unlimited.
263         *
264         * @param maxLines maximum number of lines in the layout
265         * @return this builder, useful for chaining
266         * @see android.widget.TextView#setMaxLines
267         */
268        public Builder setMaxLines(int maxLines) {
269            mMaxLines = maxLines;
270            return this;
271        }
272
273        /**
274         * Set break strategy, useful for selecting high quality or balanced paragraph
275         * layout options. The default is {@link Layout#BREAK_STRATEGY_SIMPLE}.
276         *
277         * @param breakStrategy break strategy for paragraph layout
278         * @return this builder, useful for chaining
279         * @see android.widget.TextView#setBreakStrategy
280         */
281        public Builder setBreakStrategy(@BreakStrategy int breakStrategy) {
282            mBreakStrategy = breakStrategy;
283            return this;
284        }
285
286        /**
287         * Set hyphenation frequency, to control the amount of automatic hyphenation used. The
288         * default is {@link Layout#HYPHENATION_FREQUENCY_NONE}.
289         *
290         * @param hyphenationFrequency hyphenation frequency for the paragraph
291         * @return this builder, useful for chaining
292         * @see android.widget.TextView#setHyphenationFrequency
293         */
294        public Builder setHyphenationFrequency(@HyphenationFrequency int hyphenationFrequency) {
295            mHyphenationFrequency = hyphenationFrequency;
296            return this;
297        }
298
299        /**
300         * Set indents. Arguments are arrays holding an indent amount, one per line, measured in
301         * pixels. For lines past the last element in the array, the last element repeats.
302         *
303         * @param leftIndents array of indent values for left margin, in pixels
304         * @param rightIndents array of indent values for right margin, in pixels
305         * @return this builder, useful for chaining
306         */
307        public Builder setIndents(int[] leftIndents, int[] rightIndents) {
308            mLeftIndents = leftIndents;
309            mRightIndents = rightIndents;
310            int leftLen = leftIndents == null ? 0 : leftIndents.length;
311            int rightLen = rightIndents == null ? 0 : rightIndents.length;
312            int[] indents = new int[Math.max(leftLen, rightLen)];
313            for (int i = 0; i < indents.length; i++) {
314                int leftMargin = i < leftLen ? leftIndents[i] : 0;
315                int rightMargin = i < rightLen ? rightIndents[i] : 0;
316                indents[i] = leftMargin + rightMargin;
317            }
318            nSetIndents(mNativePtr, indents);
319            return this;
320        }
321
322        /**
323         * Measurement and break iteration is done in native code. The protocol for using
324         * the native code is as follows.
325         *
326         * For each paragraph, do a nSetupParagraph, which sets paragraph text, line width, tab
327         * stops, break strategy, and hyphenation frequency (and possibly other parameters in the
328         * future).
329         *
330         * Then, for each run within the paragraph:
331         *  - setLocale (this must be done at least for the first run, optional afterwards)
332         *  - one of the following, depending on the type of run:
333         *    + addStyleRun (a text run, to be measured in native code)
334         *    + addMeasuredRun (a run already measured in Java, passed into native code)
335         *    + addReplacementRun (a replacement run, width is given)
336         *
337         * After measurement, nGetWidths() is valid if the widths are needed (eg for ellipsis).
338         * Run nComputeLineBreaks() to obtain line breaks for the paragraph.
339         *
340         * After all paragraphs, call finish() to release expensive buffers.
341         */
342
343        private void setLocale(Locale locale) {
344            if (!locale.equals(mLocale)) {
345                nSetLocale(mNativePtr, locale.toLanguageTag(),
346                        Hyphenator.get(locale).getNativePtr());
347                mLocale = locale;
348            }
349        }
350
351        /* package */ float addStyleRun(TextPaint paint, int start, int end, boolean isRtl) {
352            return nAddStyleRun(mNativePtr, paint.getNativeInstance(), paint.mNativeTypeface,
353                    start, end, isRtl);
354        }
355
356        /* package */ void addMeasuredRun(int start, int end, float[] widths) {
357            nAddMeasuredRun(mNativePtr, start, end, widths);
358        }
359
360        /* package */ void addReplacementRun(int start, int end, float width) {
361            nAddReplacementRun(mNativePtr, start, end, width);
362        }
363
364        /**
365         * Build the {@link StaticLayout} after options have been set.
366         *
367         * <p>Note: the builder object must not be reused in any way after calling this
368         * method. Setting parameters after calling this method, or calling it a second
369         * time on the same builder object, will likely lead to unexpected results.
370         *
371         * @return the newly constructed {@link StaticLayout} object
372         */
373        public StaticLayout build() {
374            StaticLayout result = new StaticLayout(this);
375            Builder.recycle(this);
376            return result;
377        }
378
379        @Override
380        protected void finalize() throws Throwable {
381            try {
382                nFreeBuilder(mNativePtr);
383            } finally {
384                super.finalize();
385            }
386        }
387
388        /* package */ long mNativePtr;
389
390        CharSequence mText;
391        int mStart;
392        int mEnd;
393        TextPaint mPaint;
394        int mWidth;
395        Alignment mAlignment;
396        TextDirectionHeuristic mTextDir;
397        float mSpacingMult;
398        float mSpacingAdd;
399        boolean mIncludePad;
400        int mEllipsizedWidth;
401        TextUtils.TruncateAt mEllipsize;
402        int mMaxLines;
403        int mBreakStrategy;
404        int mHyphenationFrequency;
405        int[] mLeftIndents;
406        int[] mRightIndents;
407
408        Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
409
410        // This will go away and be subsumed by native builder code
411        MeasuredText mMeasuredText;
412
413        Locale mLocale;
414
415        private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<Builder>(3);
416    }
417
418    public StaticLayout(CharSequence source, TextPaint paint,
419                        int width,
420                        Alignment align, float spacingmult, float spacingadd,
421                        boolean includepad) {
422        this(source, 0, source.length(), paint, width, align,
423             spacingmult, spacingadd, includepad);
424    }
425
426    /**
427     * @hide
428     */
429    public StaticLayout(CharSequence source, TextPaint paint,
430            int width, Alignment align, TextDirectionHeuristic textDir,
431            float spacingmult, float spacingadd,
432            boolean includepad) {
433        this(source, 0, source.length(), paint, width, align, textDir,
434                spacingmult, spacingadd, includepad);
435    }
436
437    public StaticLayout(CharSequence source, int bufstart, int bufend,
438                        TextPaint paint, int outerwidth,
439                        Alignment align,
440                        float spacingmult, float spacingadd,
441                        boolean includepad) {
442        this(source, bufstart, bufend, paint, outerwidth, align,
443             spacingmult, spacingadd, includepad, null, 0);
444    }
445
446    /**
447     * @hide
448     */
449    public StaticLayout(CharSequence source, int bufstart, int bufend,
450            TextPaint paint, int outerwidth,
451            Alignment align, TextDirectionHeuristic textDir,
452            float spacingmult, float spacingadd,
453            boolean includepad) {
454        this(source, bufstart, bufend, paint, outerwidth, align, textDir,
455                spacingmult, spacingadd, includepad, null, 0, Integer.MAX_VALUE);
456}
457
458    public StaticLayout(CharSequence source, int bufstart, int bufend,
459            TextPaint paint, int outerwidth,
460            Alignment align,
461            float spacingmult, float spacingadd,
462            boolean includepad,
463            TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
464        this(source, bufstart, bufend, paint, outerwidth, align,
465                TextDirectionHeuristics.FIRSTSTRONG_LTR,
466                spacingmult, spacingadd, includepad, ellipsize, ellipsizedWidth, Integer.MAX_VALUE);
467    }
468
469    /**
470     * @hide
471     */
472    public StaticLayout(CharSequence source, int bufstart, int bufend,
473                        TextPaint paint, int outerwidth,
474                        Alignment align, TextDirectionHeuristic textDir,
475                        float spacingmult, float spacingadd,
476                        boolean includepad,
477                        TextUtils.TruncateAt ellipsize, int ellipsizedWidth, int maxLines) {
478        super((ellipsize == null)
479                ? source
480                : (source instanceof Spanned)
481                    ? new SpannedEllipsizer(source)
482                    : new Ellipsizer(source),
483              paint, outerwidth, align, textDir, spacingmult, spacingadd);
484
485        Builder b = Builder.obtain(source, bufstart, bufend, paint, outerwidth)
486            .setAlignment(align)
487            .setTextDirection(textDir)
488            .setLineSpacing(spacingadd, spacingmult)
489            .setIncludePad(includepad)
490            .setEllipsizedWidth(ellipsizedWidth)
491            .setEllipsize(ellipsize)
492            .setMaxLines(maxLines);
493        /*
494         * This is annoying, but we can't refer to the layout until
495         * superclass construction is finished, and the superclass
496         * constructor wants the reference to the display text.
497         *
498         * This will break if the superclass constructor ever actually
499         * cares about the content instead of just holding the reference.
500         */
501        if (ellipsize != null) {
502            Ellipsizer e = (Ellipsizer) getText();
503
504            e.mLayout = this;
505            e.mWidth = ellipsizedWidth;
506            e.mMethod = ellipsize;
507            mEllipsizedWidth = ellipsizedWidth;
508
509            mColumns = COLUMNS_ELLIPSIZE;
510        } else {
511            mColumns = COLUMNS_NORMAL;
512            mEllipsizedWidth = outerwidth;
513        }
514
515        mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2 * mColumns);
516        mLines = new int[mLineDirections.length];
517        mMaximumVisibleLineCount = maxLines;
518
519        generate(b, b.mIncludePad, b.mIncludePad);
520
521        Builder.recycle(b);
522    }
523
524    /* package */ StaticLayout(CharSequence text) {
525        super(text, null, 0, null, 0, 0);
526
527        mColumns = COLUMNS_ELLIPSIZE;
528        mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2 * mColumns);
529        mLines = new int[mLineDirections.length];
530    }
531
532    private StaticLayout(Builder b) {
533        super((b.mEllipsize == null)
534                ? b.mText
535                : (b.mText instanceof Spanned)
536                    ? new SpannedEllipsizer(b.mText)
537                    : new Ellipsizer(b.mText),
538                b.mPaint, b.mWidth, b.mAlignment, b.mSpacingMult, b.mSpacingAdd);
539
540        if (b.mEllipsize != null) {
541            Ellipsizer e = (Ellipsizer) getText();
542
543            e.mLayout = this;
544            e.mWidth = b.mEllipsizedWidth;
545            e.mMethod = b.mEllipsize;
546            mEllipsizedWidth = b.mEllipsizedWidth;
547
548            mColumns = COLUMNS_ELLIPSIZE;
549        } else {
550            mColumns = COLUMNS_NORMAL;
551            mEllipsizedWidth = b.mWidth;
552        }
553
554        mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2 * mColumns);
555        mLines = new int[mLineDirections.length];
556        mMaximumVisibleLineCount = b.mMaxLines;
557
558        mLeftIndents = b.mLeftIndents;
559        mRightIndents = b.mRightIndents;
560
561        generate(b, b.mIncludePad, b.mIncludePad);
562    }
563
564    /* package */ void generate(Builder b, boolean includepad, boolean trackpad) {
565        CharSequence source = b.mText;
566        int bufStart = b.mStart;
567        int bufEnd = b.mEnd;
568        TextPaint paint = b.mPaint;
569        int outerWidth = b.mWidth;
570        TextDirectionHeuristic textDir = b.mTextDir;
571        float spacingmult = b.mSpacingMult;
572        float spacingadd = b.mSpacingAdd;
573        float ellipsizedWidth = b.mEllipsizedWidth;
574        TextUtils.TruncateAt ellipsize = b.mEllipsize;
575        LineBreaks lineBreaks = new LineBreaks();  // TODO: move to builder to avoid allocation costs
576        // store span end locations
577        int[] spanEndCache = new int[4];
578        // store fontMetrics per span range
579        // must be a multiple of 4 (and > 0) (store top, bottom, ascent, and descent per range)
580        int[] fmCache = new int[4 * 4];
581        b.setLocale(paint.getTextLocale());  // TODO: also respect LocaleSpan within the text
582
583        mLineCount = 0;
584
585        int v = 0;
586        boolean needMultiply = (spacingmult != 1 || spacingadd != 0);
587
588        Paint.FontMetricsInt fm = b.mFontMetricsInt;
589        int[] chooseHtv = null;
590
591        MeasuredText measured = b.mMeasuredText;
592
593        Spanned spanned = null;
594        if (source instanceof Spanned)
595            spanned = (Spanned) source;
596
597        int paraEnd;
598        for (int paraStart = bufStart; paraStart <= bufEnd; paraStart = paraEnd) {
599            paraEnd = TextUtils.indexOf(source, CHAR_NEW_LINE, paraStart, bufEnd);
600            if (paraEnd < 0)
601                paraEnd = bufEnd;
602            else
603                paraEnd++;
604
605            int firstWidthLineCount = 1;
606            int firstWidth = outerWidth;
607            int restWidth = outerWidth;
608
609            LineHeightSpan[] chooseHt = null;
610
611            if (spanned != null) {
612                LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
613                        LeadingMarginSpan.class);
614                for (int i = 0; i < sp.length; i++) {
615                    LeadingMarginSpan lms = sp[i];
616                    firstWidth -= sp[i].getLeadingMargin(true);
617                    restWidth -= sp[i].getLeadingMargin(false);
618
619                    // LeadingMarginSpan2 is odd.  The count affects all
620                    // leading margin spans, not just this particular one
621                    if (lms instanceof LeadingMarginSpan2) {
622                        LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
623                        firstWidthLineCount = Math.max(firstWidthLineCount,
624                                lms2.getLeadingMarginLineCount());
625                    }
626                }
627
628                chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
629
630                if (chooseHt.length == 0) {
631                    chooseHt = null; // So that out() would not assume it has any contents
632                } else {
633                    if (chooseHtv == null ||
634                        chooseHtv.length < chooseHt.length) {
635                        chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
636                    }
637
638                    for (int i = 0; i < chooseHt.length; i++) {
639                        int o = spanned.getSpanStart(chooseHt[i]);
640
641                        if (o < paraStart) {
642                            // starts in this layout, before the
643                            // current paragraph
644
645                            chooseHtv[i] = getLineTop(getLineForOffset(o));
646                        } else {
647                            // starts in this paragraph
648
649                            chooseHtv[i] = v;
650                        }
651                    }
652                }
653            }
654
655            measured.setPara(source, paraStart, paraEnd, textDir, b);
656            char[] chs = measured.mChars;
657            float[] widths = measured.mWidths;
658            byte[] chdirs = measured.mLevels;
659            int dir = measured.mDir;
660            boolean easy = measured.mEasy;
661
662            // tab stop locations
663            int[] variableTabStops = null;
664            if (spanned != null) {
665                TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
666                        paraEnd, TabStopSpan.class);
667                if (spans.length > 0) {
668                    int[] stops = new int[spans.length];
669                    for (int i = 0; i < spans.length; i++) {
670                        stops[i] = spans[i].getTabStop();
671                    }
672                    Arrays.sort(stops, 0, stops.length);
673                    variableTabStops = stops;
674                }
675            }
676
677            nSetupParagraph(b.mNativePtr, chs, paraEnd - paraStart,
678                    firstWidth, firstWidthLineCount, restWidth,
679                    variableTabStops, TAB_INCREMENT, b.mBreakStrategy, b.mHyphenationFrequency);
680            if (mLeftIndents != null || mRightIndents != null) {
681                // TODO(raph) performance: it would be better to do this once per layout rather
682                // than once per paragraph, but that would require a change to the native
683                // interface.
684                int leftLen = mLeftIndents == null ? 0 : mLeftIndents.length;
685                int rightLen = mRightIndents == null ? 0 : mRightIndents.length;
686                int indentsLen = Math.max(1, Math.max(leftLen, rightLen) - mLineCount);
687                int[] indents = new int[indentsLen];
688                for (int i = 0; i < indentsLen; i++) {
689                    int leftMargin = mLeftIndents == null ? 0 :
690                            mLeftIndents[Math.min(i + mLineCount, leftLen - 1)];
691                    int rightMargin = mRightIndents == null ? 0 :
692                            mRightIndents[Math.min(i + mLineCount, rightLen - 1)];
693                    indents[i] = leftMargin + rightMargin;
694                }
695                nSetIndents(b.mNativePtr, indents);
696            }
697
698            // measurement has to be done before performing line breaking
699            // but we don't want to recompute fontmetrics or span ranges the
700            // second time, so we cache those and then use those stored values
701            int fmCacheCount = 0;
702            int spanEndCacheCount = 0;
703            for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
704                if (fmCacheCount * 4 >= fmCache.length) {
705                    int[] grow = new int[fmCacheCount * 4 * 2];
706                    System.arraycopy(fmCache, 0, grow, 0, fmCacheCount * 4);
707                    fmCache = grow;
708                }
709
710                if (spanEndCacheCount >= spanEndCache.length) {
711                    int[] grow = new int[spanEndCacheCount * 2];
712                    System.arraycopy(spanEndCache, 0, grow, 0, spanEndCacheCount);
713                    spanEndCache = grow;
714                }
715
716                if (spanned == null) {
717                    spanEnd = paraEnd;
718                    int spanLen = spanEnd - spanStart;
719                    measured.addStyleRun(paint, spanLen, fm);
720                } else {
721                    spanEnd = spanned.nextSpanTransition(spanStart, paraEnd,
722                            MetricAffectingSpan.class);
723                    int spanLen = spanEnd - spanStart;
724                    MetricAffectingSpan[] spans =
725                            spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class);
726                    spans = TextUtils.removeEmptySpans(spans, spanned, MetricAffectingSpan.class);
727                    measured.addStyleRun(paint, spans, spanLen, fm);
728                }
729
730                // the order of storage here (top, bottom, ascent, descent) has to match the code below
731                // where these values are retrieved
732                fmCache[fmCacheCount * 4 + 0] = fm.top;
733                fmCache[fmCacheCount * 4 + 1] = fm.bottom;
734                fmCache[fmCacheCount * 4 + 2] = fm.ascent;
735                fmCache[fmCacheCount * 4 + 3] = fm.descent;
736                fmCacheCount++;
737
738                spanEndCache[spanEndCacheCount] = spanEnd;
739                spanEndCacheCount++;
740            }
741
742            nGetWidths(b.mNativePtr, widths);
743            int breakCount = nComputeLineBreaks(b.mNativePtr, lineBreaks, lineBreaks.breaks,
744                    lineBreaks.widths, lineBreaks.flags, lineBreaks.breaks.length);
745
746            int[] breaks = lineBreaks.breaks;
747            float[] lineWidths = lineBreaks.widths;
748            int[] flags = lineBreaks.flags;
749
750            final int remainingLineCount = mMaximumVisibleLineCount - mLineCount;
751            final boolean ellipsisMayBeApplied = ellipsize != null
752                    && (ellipsize == TextUtils.TruncateAt.END
753                        || (mMaximumVisibleLineCount == 1
754                                && ellipsize != TextUtils.TruncateAt.MARQUEE));
755            if (remainingLineCount > 0 && remainingLineCount < breakCount &&
756                    ellipsisMayBeApplied) {
757                // Calculate width and flag.
758                float width = 0;
759                int flag = 0;
760                for (int i = remainingLineCount - 1; i < breakCount; i++) {
761                    if (i == breakCount - 1) {
762                        width += lineWidths[i];
763                    } else {
764                        for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) {
765                            width += widths[j];
766                        }
767                    }
768                    flag |= flags[i] & TAB_MASK;
769                }
770                // Treat the last line and overflowed lines as a single line.
771                breaks[remainingLineCount - 1] = breaks[breakCount - 1];
772                lineWidths[remainingLineCount - 1] = width;
773                flags[remainingLineCount - 1] = flag;
774
775                breakCount = remainingLineCount;
776            }
777
778            // here is the offset of the starting character of the line we are currently measuring
779            int here = paraStart;
780
781            int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
782            int fmCacheIndex = 0;
783            int spanEndCacheIndex = 0;
784            int breakIndex = 0;
785            for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
786                // retrieve end of span
787                spanEnd = spanEndCache[spanEndCacheIndex++];
788
789                // retrieve cached metrics, order matches above
790                fm.top = fmCache[fmCacheIndex * 4 + 0];
791                fm.bottom = fmCache[fmCacheIndex * 4 + 1];
792                fm.ascent = fmCache[fmCacheIndex * 4 + 2];
793                fm.descent = fmCache[fmCacheIndex * 4 + 3];
794                fmCacheIndex++;
795
796                if (fm.top < fmTop) {
797                    fmTop = fm.top;
798                }
799                if (fm.ascent < fmAscent) {
800                    fmAscent = fm.ascent;
801                }
802                if (fm.descent > fmDescent) {
803                    fmDescent = fm.descent;
804                }
805                if (fm.bottom > fmBottom) {
806                    fmBottom = fm.bottom;
807                }
808
809                // skip breaks ending before current span range
810                while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
811                    breakIndex++;
812                }
813
814                while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
815                    int endPos = paraStart + breaks[breakIndex];
816
817                    boolean moreChars = (endPos < bufEnd);
818
819                    v = out(source, here, endPos,
820                            fmAscent, fmDescent, fmTop, fmBottom,
821                            v, spacingmult, spacingadd, chooseHt, chooseHtv, fm, flags[breakIndex],
822                            needMultiply, chdirs, dir, easy, bufEnd, includepad, trackpad,
823                            chs, widths, paraStart, ellipsize, ellipsizedWidth,
824                            lineWidths[breakIndex], paint, moreChars);
825
826                    if (endPos < spanEnd) {
827                        // preserve metrics for current span
828                        fmTop = fm.top;
829                        fmBottom = fm.bottom;
830                        fmAscent = fm.ascent;
831                        fmDescent = fm.descent;
832                    } else {
833                        fmTop = fmBottom = fmAscent = fmDescent = 0;
834                    }
835
836                    here = endPos;
837                    breakIndex++;
838
839                    if (mLineCount >= mMaximumVisibleLineCount) {
840                        return;
841                    }
842                }
843            }
844
845            if (paraEnd == bufEnd)
846                break;
847        }
848
849        if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE) &&
850                mLineCount < mMaximumVisibleLineCount) {
851            // Log.e("text", "output last " + bufEnd);
852
853            measured.setPara(source, bufEnd, bufEnd, textDir, b);
854
855            paint.getFontMetricsInt(fm);
856
857            v = out(source,
858                    bufEnd, bufEnd, fm.ascent, fm.descent,
859                    fm.top, fm.bottom,
860                    v,
861                    spacingmult, spacingadd, null,
862                    null, fm, 0,
863                    needMultiply, measured.mLevels, measured.mDir, measured.mEasy, bufEnd,
864                    includepad, trackpad, null,
865                    null, bufStart, ellipsize,
866                    ellipsizedWidth, 0, paint, false);
867        }
868    }
869
870    private int out(CharSequence text, int start, int end,
871                      int above, int below, int top, int bottom, int v,
872                      float spacingmult, float spacingadd,
873                      LineHeightSpan[] chooseHt, int[] chooseHtv,
874                      Paint.FontMetricsInt fm, int flags,
875                      boolean needMultiply, byte[] chdirs, int dir,
876                      boolean easy, int bufEnd, boolean includePad,
877                      boolean trackPad, char[] chs,
878                      float[] widths, int widthStart, TextUtils.TruncateAt ellipsize,
879                      float ellipsisWidth, float textWidth,
880                      TextPaint paint, boolean moreChars) {
881        int j = mLineCount;
882        int off = j * mColumns;
883        int want = off + mColumns + TOP;
884        int[] lines = mLines;
885
886        if (want >= lines.length) {
887            Directions[] grow2 = ArrayUtils.newUnpaddedArray(
888                    Directions.class, GrowingArrayUtils.growSize(want));
889            System.arraycopy(mLineDirections, 0, grow2, 0,
890                             mLineDirections.length);
891            mLineDirections = grow2;
892
893            int[] grow = new int[grow2.length];
894            System.arraycopy(lines, 0, grow, 0, lines.length);
895            mLines = grow;
896            lines = grow;
897        }
898
899        if (chooseHt != null) {
900            fm.ascent = above;
901            fm.descent = below;
902            fm.top = top;
903            fm.bottom = bottom;
904
905            for (int i = 0; i < chooseHt.length; i++) {
906                if (chooseHt[i] instanceof LineHeightSpan.WithDensity) {
907                    ((LineHeightSpan.WithDensity) chooseHt[i]).
908                        chooseHeight(text, start, end, chooseHtv[i], v, fm, paint);
909
910                } else {
911                    chooseHt[i].chooseHeight(text, start, end, chooseHtv[i], v, fm);
912                }
913            }
914
915            above = fm.ascent;
916            below = fm.descent;
917            top = fm.top;
918            bottom = fm.bottom;
919        }
920
921        boolean firstLine = (j == 0);
922        boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);
923        boolean lastLine = currentLineIsTheLastVisibleOne || (end == bufEnd);
924
925        if (firstLine) {
926            if (trackPad) {
927                mTopPadding = top - above;
928            }
929
930            if (includePad) {
931                above = top;
932            }
933        }
934
935        int extra;
936
937        if (lastLine) {
938            if (trackPad) {
939                mBottomPadding = bottom - below;
940            }
941
942            if (includePad) {
943                below = bottom;
944            }
945        }
946
947
948        if (needMultiply && !lastLine) {
949            double ex = (below - above) * (spacingmult - 1) + spacingadd;
950            if (ex >= 0) {
951                extra = (int)(ex + EXTRA_ROUNDING);
952            } else {
953                extra = -(int)(-ex + EXTRA_ROUNDING);
954            }
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        // TODO: could move TAB to share same column as HYPHEN, simplifying this code and gaining
968        // one bit for start field
969        lines[off + TAB] |= flags & TAB_MASK;
970        lines[off + HYPHEN] = flags;
971
972        lines[off + DIR] |= dir << DIR_SHIFT;
973        Directions linedirs = DIRS_ALL_LEFT_TO_RIGHT;
974        // easy means all chars < the first RTL, so no emoji, no nothing
975        // XXX a run with no text or all spaces is easy but might be an empty
976        // RTL paragraph.  Make sure easy is false if this is the case.
977        if (easy) {
978            mLineDirections[j] = linedirs;
979        } else {
980            mLineDirections[j] = AndroidBidi.directions(dir, chdirs, start - widthStart, chs,
981                    start - widthStart, end - start);
982        }
983
984        if (ellipsize != null) {
985            // If there is only one line, then do any type of ellipsis except when it is MARQUEE
986            // if there are multiple lines, just allow END ellipsis on the last line
987            boolean forceEllipsis = moreChars && (mLineCount + 1 == mMaximumVisibleLineCount);
988
989            boolean doEllipsis =
990                        (((mMaximumVisibleLineCount == 1 && moreChars) || (firstLine && !moreChars)) &&
991                                ellipsize != TextUtils.TruncateAt.MARQUEE) ||
992                        (!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
993                                ellipsize == TextUtils.TruncateAt.END);
994            if (doEllipsis) {
995                calculateEllipsis(start, end, widths, widthStart,
996                        ellipsisWidth, ellipsize, j,
997                        textWidth, paint, forceEllipsis);
998            }
999        }
1000
1001        mLineCount++;
1002        return v;
1003    }
1004
1005    private void calculateEllipsis(int lineStart, int lineEnd,
1006                                   float[] widths, int widthStart,
1007                                   float avail, TextUtils.TruncateAt where,
1008                                   int line, float textWidth, TextPaint paint,
1009                                   boolean forceEllipsis) {
1010        if (textWidth <= avail && !forceEllipsis) {
1011            // Everything fits!
1012            mLines[mColumns * line + ELLIPSIS_START] = 0;
1013            mLines[mColumns * line + ELLIPSIS_COUNT] = 0;
1014            return;
1015        }
1016
1017        float ellipsisWidth = paint.measureText(
1018                (where == TextUtils.TruncateAt.END_SMALL) ?
1019                        TextUtils.ELLIPSIS_TWO_DOTS : TextUtils.ELLIPSIS_NORMAL, 0, 1);
1020        int ellipsisStart = 0;
1021        int ellipsisCount = 0;
1022        int len = lineEnd - lineStart;
1023
1024        // We only support start ellipsis on a single line
1025        if (where == TextUtils.TruncateAt.START) {
1026            if (mMaximumVisibleLineCount == 1) {
1027                float sum = 0;
1028                int i;
1029
1030                for (i = len; i > 0; i--) {
1031                    float w = widths[i - 1 + lineStart - widthStart];
1032
1033                    if (w + sum + ellipsisWidth > avail) {
1034                        break;
1035                    }
1036
1037                    sum += w;
1038                }
1039
1040                ellipsisStart = 0;
1041                ellipsisCount = i;
1042            } else {
1043                if (Log.isLoggable(TAG, Log.WARN)) {
1044                    Log.w(TAG, "Start Ellipsis only supported with one line");
1045                }
1046            }
1047        } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE ||
1048                where == TextUtils.TruncateAt.END_SMALL) {
1049            float sum = 0;
1050            int i;
1051
1052            for (i = 0; i < len; i++) {
1053                float w = widths[i + lineStart - widthStart];
1054
1055                if (w + sum + ellipsisWidth > avail) {
1056                    break;
1057                }
1058
1059                sum += w;
1060            }
1061
1062            ellipsisStart = i;
1063            ellipsisCount = len - i;
1064            if (forceEllipsis && ellipsisCount == 0 && len > 0) {
1065                ellipsisStart = len - 1;
1066                ellipsisCount = 1;
1067            }
1068        } else {
1069            // where = TextUtils.TruncateAt.MIDDLE We only support middle ellipsis on a single line
1070            if (mMaximumVisibleLineCount == 1) {
1071                float lsum = 0, rsum = 0;
1072                int left = 0, right = len;
1073
1074                float ravail = (avail - ellipsisWidth) / 2;
1075                for (right = len; right > 0; right--) {
1076                    float w = widths[right - 1 + lineStart - widthStart];
1077
1078                    if (w + rsum > ravail) {
1079                        break;
1080                    }
1081
1082                    rsum += w;
1083                }
1084
1085                float lavail = avail - ellipsisWidth - rsum;
1086                for (left = 0; left < right; left++) {
1087                    float w = widths[left + lineStart - widthStart];
1088
1089                    if (w + lsum > lavail) {
1090                        break;
1091                    }
1092
1093                    lsum += w;
1094                }
1095
1096                ellipsisStart = left;
1097                ellipsisCount = right - left;
1098            } else {
1099                if (Log.isLoggable(TAG, Log.WARN)) {
1100                    Log.w(TAG, "Middle Ellipsis only supported with one line");
1101                }
1102            }
1103        }
1104
1105        mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
1106        mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
1107    }
1108
1109    // Override the base class so we can directly access our members,
1110    // rather than relying on member functions.
1111    // The logic mirrors that of Layout.getLineForVertical
1112    // FIXME: It may be faster to do a linear search for layouts without many lines.
1113    @Override
1114    public int getLineForVertical(int vertical) {
1115        int high = mLineCount;
1116        int low = -1;
1117        int guess;
1118        int[] lines = mLines;
1119        while (high - low > 1) {
1120            guess = (high + low) >> 1;
1121            if (lines[mColumns * guess + TOP] > vertical){
1122                high = guess;
1123            } else {
1124                low = guess;
1125            }
1126        }
1127        if (low < 0) {
1128            return 0;
1129        } else {
1130            return low;
1131        }
1132    }
1133
1134    @Override
1135    public int getLineCount() {
1136        return mLineCount;
1137    }
1138
1139    @Override
1140    public int getLineTop(int line) {
1141        return mLines[mColumns * line + TOP];
1142    }
1143
1144    @Override
1145    public int getLineDescent(int line) {
1146        return mLines[mColumns * line + DESCENT];
1147    }
1148
1149    @Override
1150    public int getLineStart(int line) {
1151        return mLines[mColumns * line + START] & START_MASK;
1152    }
1153
1154    @Override
1155    public int getParagraphDirection(int line) {
1156        return mLines[mColumns * line + DIR] >> DIR_SHIFT;
1157    }
1158
1159    @Override
1160    public boolean getLineContainsTab(int line) {
1161        return (mLines[mColumns * line + TAB] & TAB_MASK) != 0;
1162    }
1163
1164    @Override
1165    public final Directions getLineDirections(int line) {
1166        return mLineDirections[line];
1167    }
1168
1169    @Override
1170    public int getTopPadding() {
1171        return mTopPadding;
1172    }
1173
1174    @Override
1175    public int getBottomPadding() {
1176        return mBottomPadding;
1177    }
1178
1179    /**
1180     * @hide
1181     */
1182    @Override
1183    public int getHyphen(int line) {
1184        return mLines[mColumns * line + HYPHEN] & 0xff;
1185    }
1186
1187    /**
1188     * @hide
1189     */
1190    @Override
1191    public int getIndentAdjust(int line, Alignment align) {
1192        if (align == Alignment.ALIGN_LEFT) {
1193            if (mLeftIndents == null) {
1194                return 0;
1195            } else {
1196                return mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1197            }
1198        } else if (align == Alignment.ALIGN_RIGHT) {
1199            if (mRightIndents == null) {
1200                return 0;
1201            } else {
1202                return -mRightIndents[Math.min(line, mRightIndents.length - 1)];
1203            }
1204        } else if (align == Alignment.ALIGN_CENTER) {
1205            int left = 0;
1206            if (mLeftIndents != null) {
1207                left = mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
1208            }
1209            int right = 0;
1210            if (mRightIndents != null) {
1211                right = mRightIndents[Math.min(line, mRightIndents.length - 1)];
1212            }
1213            return (left - right) >> 1;
1214        } else {
1215            throw new AssertionError("unhandled alignment " + align);
1216        }
1217    }
1218
1219    @Override
1220    public int getEllipsisCount(int line) {
1221        if (mColumns < COLUMNS_ELLIPSIZE) {
1222            return 0;
1223        }
1224
1225        return mLines[mColumns * line + ELLIPSIS_COUNT];
1226    }
1227
1228    @Override
1229    public int getEllipsisStart(int line) {
1230        if (mColumns < COLUMNS_ELLIPSIZE) {
1231            return 0;
1232        }
1233
1234        return mLines[mColumns * line + ELLIPSIS_START];
1235    }
1236
1237    @Override
1238    public int getEllipsizedWidth() {
1239        return mEllipsizedWidth;
1240    }
1241
1242    private static native long nNewBuilder();
1243    private static native void nFreeBuilder(long nativePtr);
1244    private static native void nFinishBuilder(long nativePtr);
1245
1246    /* package */ static native long nLoadHyphenator(ByteBuffer buf, int offset);
1247
1248    private static native void nSetLocale(long nativePtr, String locale, long nativeHyphenator);
1249
1250    private static native void nSetIndents(long nativePtr, int[] indents);
1251
1252    // Set up paragraph text and settings; done as one big method to minimize jni crossings
1253    private static native void nSetupParagraph(long nativePtr, char[] text, int length,
1254            float firstWidth, int firstWidthLineCount, float restWidth,
1255            int[] variableTabStops, int defaultTabStop, int breakStrategy, int hyphenationFrequency);
1256
1257    private static native float nAddStyleRun(long nativePtr, long nativePaint,
1258            long nativeTypeface, int start, int end, boolean isRtl);
1259
1260    private static native void nAddMeasuredRun(long nativePtr,
1261            int start, int end, float[] widths);
1262
1263    private static native void nAddReplacementRun(long nativePtr, int start, int end, float width);
1264
1265    private static native void nGetWidths(long nativePtr, float[] widths);
1266
1267    // populates LineBreaks and returns the number of breaks found
1268    //
1269    // the arrays inside the LineBreaks objects are passed in as well
1270    // to reduce the number of JNI calls in the common case where the
1271    // arrays do not have to be resized
1272    private static native int nComputeLineBreaks(long nativePtr, LineBreaks recycle,
1273            int[] recycleBreaks, float[] recycleWidths, int[] recycleFlags, int recycleLength);
1274
1275    private int mLineCount;
1276    private int mTopPadding, mBottomPadding;
1277    private int mColumns;
1278    private int mEllipsizedWidth;
1279
1280    private static final int COLUMNS_NORMAL = 4;
1281    private static final int COLUMNS_ELLIPSIZE = 6;
1282    private static final int START = 0;
1283    private static final int DIR = START;
1284    private static final int TAB = START;
1285    private static final int TOP = 1;
1286    private static final int DESCENT = 2;
1287    private static final int HYPHEN = 3;
1288    private static final int ELLIPSIS_START = 4;
1289    private static final int ELLIPSIS_COUNT = 5;
1290
1291    private int[] mLines;
1292    private Directions[] mLineDirections;
1293    private int mMaximumVisibleLineCount = Integer.MAX_VALUE;
1294
1295    private static final int START_MASK = 0x1FFFFFFF;
1296    private static final int DIR_SHIFT  = 30;
1297    private static final int TAB_MASK   = 0x20000000;
1298
1299    private static final int TAB_INCREMENT = 20; // same as Layout, but that's private
1300
1301    private static final char CHAR_NEW_LINE = '\n';
1302
1303    private static final double EXTRA_ROUNDING = 0.5;
1304
1305    // This is used to return three arrays from a single JNI call when
1306    // performing line breaking
1307    /*package*/ static class LineBreaks {
1308        private static final int INITIAL_SIZE = 16;
1309        public int[] breaks = new int[INITIAL_SIZE];
1310        public float[] widths = new float[INITIAL_SIZE];
1311        public int[] flags = new int[INITIAL_SIZE]; // hasTab
1312        // breaks, widths, and flags should all have the same length
1313    }
1314
1315    private int[] mLeftIndents;
1316    private int[] mRightIndents;
1317}
1318