Home | History | Annotate | Download | only in text
      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 
     17 package android.text;
     18 
     19 import android.annotation.Nullable;
     20 import android.graphics.Paint;
     21 import android.text.style.LeadingMarginSpan;
     22 import android.text.style.LeadingMarginSpan.LeadingMarginSpan2;
     23 import android.text.style.LineHeightSpan;
     24 import android.text.style.MetricAffectingSpan;
     25 import android.text.style.TabStopSpan;
     26 import android.util.Log;
     27 import android.util.Pools.SynchronizedPool;
     28 
     29 import com.android.internal.util.ArrayUtils;
     30 import com.android.internal.util.GrowingArrayUtils;
     31 
     32 import java.nio.ByteBuffer;
     33 import java.util.Arrays;
     34 import 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  */
     46 public 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