OSDN Git Service

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