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