• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {
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          */
obtain(CharSequence source, int start, int end, TextPaint paint, int width)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 
recycle(Builder b)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
finish()114         /* package */ void finish() {
115             nFinishBuilder(mNativePtr);
116             mText = null;
117             mPaint = null;
118             mLeftIndents = null;
119             mRightIndents = null;
120             mMeasuredText.finish();
121         }
122 
setText(CharSequence source)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          */
setText(CharSequence source, int start, int end)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          */
setPaint(TextPaint paint)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          */
setWidth(int width)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          */
setAlignment(Alignment alignment)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          */
setTextDirection(TextDirectionHeuristic textDir)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          */
setLineSpacing(float spacingAdd, float spacingMult)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          */
setIncludePad(boolean includePad)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          */
setEllipsizedWidth(int ellipsizedWidth)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          */
setEllipsize(@ullable TextUtils.TruncateAt ellipsize)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          */
setMaxLines(int maxLines)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          */
setBreakStrategy(@reakStrategy int breakStrategy)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          */
setHyphenationFrequency(@yphenationFrequency int hyphenationFrequency)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          */
setIndents(int[] leftIndents, int[] rightIndents)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