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.IntDef;
20 import android.emoji.EmojiFactory;
21 import android.graphics.Canvas;
22 import android.graphics.Paint;
23 import android.graphics.Path;
24 import android.graphics.Rect;
25 import android.text.method.TextKeyListener;
26 import android.text.style.AlignmentSpan;
27 import android.text.style.LeadingMarginSpan;
28 import android.text.style.LeadingMarginSpan.LeadingMarginSpan2;
29 import android.text.style.LineBackgroundSpan;
30 import android.text.style.ParagraphStyle;
31 import android.text.style.ReplacementSpan;
32 import android.text.style.TabStopSpan;
33 
34 import com.android.internal.util.ArrayUtils;
35 import com.android.internal.util.GrowingArrayUtils;
36 
37 import java.lang.annotation.Retention;
38 import java.lang.annotation.RetentionPolicy;
39 import java.util.Arrays;
40 
41 /**
42  * A base class that manages text layout in visual elements on
43  * the screen.
44  * <p>For text that will be edited, use a {@link DynamicLayout},
45  * which will be updated as the text changes.
46  * For text that will not change, use a {@link StaticLayout}.
47  */
48 public abstract class Layout {
49     /** @hide */
50     @IntDef({BREAK_STRATEGY_SIMPLE, BREAK_STRATEGY_HIGH_QUALITY, BREAK_STRATEGY_BALANCED})
51     @Retention(RetentionPolicy.SOURCE)
52     public @interface BreakStrategy {}
53 
54     /**
55      * Value for break strategy indicating simple line breaking. Automatic hyphens are not added
56      * (though soft hyphens are respected), and modifying text generally doesn't affect the layout
57      * before it (which yields a more consistent user experience when editing), but layout may not
58      * be the highest quality.
59      */
60     public static final int BREAK_STRATEGY_SIMPLE = 0;
61 
62     /**
63      * Value for break strategy indicating high quality line breaking, including automatic
64      * hyphenation and doing whole-paragraph optimization of line breaks.
65      */
66     public static final int BREAK_STRATEGY_HIGH_QUALITY = 1;
67 
68     /**
69      * Value for break strategy indicating balanced line breaking. The breaks are chosen to
70      * make all lines as close to the same length as possible, including automatic hyphenation.
71      */
72     public static final int BREAK_STRATEGY_BALANCED = 2;
73 
74     /** @hide */
75     @IntDef({HYPHENATION_FREQUENCY_NORMAL, HYPHENATION_FREQUENCY_FULL,
76              HYPHENATION_FREQUENCY_NONE})
77     @Retention(RetentionPolicy.SOURCE)
78     public @interface HyphenationFrequency {}
79 
80     /**
81      * Value for hyphenation frequency indicating no automatic hyphenation. Useful
82      * for backward compatibility, and for cases where the automatic hyphenation algorithm results
83      * in incorrect hyphenation. Mid-word breaks may still happen when a word is wider than the
84      * layout and there is otherwise no valid break. Soft hyphens are ignored and will not be used
85      * as suggestions for potential line breaks.
86      */
87     public static final int HYPHENATION_FREQUENCY_NONE = 0;
88 
89     /**
90      * Value for hyphenation frequency indicating a light amount of automatic hyphenation, which
91      * is a conservative default. Useful for informal cases, such as short sentences or chat
92      * messages.
93      */
94     public static final int HYPHENATION_FREQUENCY_NORMAL = 1;
95 
96     /**
97      * Value for hyphenation frequency indicating the full amount of automatic hyphenation, typical
98      * in typography. Useful for running text and where it's important to put the maximum amount of
99      * text in a screen with limited space.
100      */
101     public static final int HYPHENATION_FREQUENCY_FULL = 2;
102 
103     private static final ParagraphStyle[] NO_PARA_SPANS =
104         ArrayUtils.emptyArray(ParagraphStyle.class);
105 
106     /* package */ static final EmojiFactory EMOJI_FACTORY = EmojiFactory.newAvailableInstance();
107     /* package */ static final int MIN_EMOJI, MAX_EMOJI;
108 
109     static {
110         if (EMOJI_FACTORY != null) {
111             MIN_EMOJI = EMOJI_FACTORY.getMinimumAndroidPua();
112             MAX_EMOJI = EMOJI_FACTORY.getMaximumAndroidPua();
113         } else {
114             MIN_EMOJI = -1;
115             MAX_EMOJI = -1;
116         }
117     }
118 
119     /**
120      * Return how wide a layout must be in order to display the
121      * specified text with one line per paragraph.
122      */
getDesiredWidth(CharSequence source, TextPaint paint)123     public static float getDesiredWidth(CharSequence source,
124                                         TextPaint paint) {
125         return getDesiredWidth(source, 0, source.length(), paint);
126     }
127 
128     /**
129      * Return how wide a layout must be in order to display the
130      * specified text slice with one line per paragraph.
131      */
getDesiredWidth(CharSequence source, int start, int end, TextPaint paint)132     public static float getDesiredWidth(CharSequence source,
133                                         int start, int end,
134                                         TextPaint paint) {
135         float need = 0;
136 
137         int next;
138         for (int i = start; i <= end; i = next) {
139             next = TextUtils.indexOf(source, '\n', i, end);
140 
141             if (next < 0)
142                 next = end;
143 
144             // note, omits trailing paragraph char
145             float w = measurePara(paint, source, i, next);
146 
147             if (w > need)
148                 need = w;
149 
150             next++;
151         }
152 
153         return need;
154     }
155 
156     /**
157      * Subclasses of Layout use this constructor to set the display text,
158      * width, and other standard properties.
159      * @param text the text to render
160      * @param paint the default paint for the layout.  Styles can override
161      * various attributes of the paint.
162      * @param width the wrapping width for the text.
163      * @param align whether to left, right, or center the text.  Styles can
164      * override the alignment.
165      * @param spacingMult factor by which to scale the font size to get the
166      * default line spacing
167      * @param spacingAdd amount to add to the default line spacing
168      */
Layout(CharSequence text, TextPaint paint, int width, Alignment align, float spacingMult, float spacingAdd)169     protected Layout(CharSequence text, TextPaint paint,
170                      int width, Alignment align,
171                      float spacingMult, float spacingAdd) {
172         this(text, paint, width, align, TextDirectionHeuristics.FIRSTSTRONG_LTR,
173                 spacingMult, spacingAdd);
174     }
175 
176     /**
177      * Subclasses of Layout use this constructor to set the display text,
178      * width, and other standard properties.
179      * @param text the text to render
180      * @param paint the default paint for the layout.  Styles can override
181      * various attributes of the paint.
182      * @param width the wrapping width for the text.
183      * @param align whether to left, right, or center the text.  Styles can
184      * override the alignment.
185      * @param spacingMult factor by which to scale the font size to get the
186      * default line spacing
187      * @param spacingAdd amount to add to the default line spacing
188      *
189      * @hide
190      */
Layout(CharSequence text, TextPaint paint, int width, Alignment align, TextDirectionHeuristic textDir, float spacingMult, float spacingAdd)191     protected Layout(CharSequence text, TextPaint paint,
192                      int width, Alignment align, TextDirectionHeuristic textDir,
193                      float spacingMult, float spacingAdd) {
194 
195         if (width < 0)
196             throw new IllegalArgumentException("Layout: " + width + " < 0");
197 
198         // Ensure paint doesn't have baselineShift set.
199         // While normally we don't modify the paint the user passed in,
200         // we were already doing this in Styled.drawUniformRun with both
201         // baselineShift and bgColor.  We probably should reevaluate bgColor.
202         if (paint != null) {
203             paint.bgColor = 0;
204             paint.baselineShift = 0;
205         }
206 
207         mText = text;
208         mPaint = paint;
209         mWidth = width;
210         mAlignment = align;
211         mSpacingMult = spacingMult;
212         mSpacingAdd = spacingAdd;
213         mSpannedText = text instanceof Spanned;
214         mTextDir = textDir;
215     }
216 
217     /**
218      * Replace constructor properties of this Layout with new ones.  Be careful.
219      */
replaceWith(CharSequence text, TextPaint paint, int width, Alignment align, float spacingmult, float spacingadd)220     /* package */ void replaceWith(CharSequence text, TextPaint paint,
221                               int width, Alignment align,
222                               float spacingmult, float spacingadd) {
223         if (width < 0) {
224             throw new IllegalArgumentException("Layout: " + width + " < 0");
225         }
226 
227         mText = text;
228         mPaint = paint;
229         mWidth = width;
230         mAlignment = align;
231         mSpacingMult = spacingmult;
232         mSpacingAdd = spacingadd;
233         mSpannedText = text instanceof Spanned;
234     }
235 
236     /**
237      * Draw this Layout on the specified Canvas.
238      */
draw(Canvas c)239     public void draw(Canvas c) {
240         draw(c, null, null, 0);
241     }
242 
243     /**
244      * Draw this Layout on the specified canvas, with the highlight path drawn
245      * between the background and the text.
246      *
247      * @param canvas the canvas
248      * @param highlight the path of the highlight or cursor; can be null
249      * @param highlightPaint the paint for the highlight
250      * @param cursorOffsetVertical the amount to temporarily translate the
251      *        canvas while rendering the highlight
252      */
draw(Canvas canvas, Path highlight, Paint highlightPaint, int cursorOffsetVertical)253     public void draw(Canvas canvas, Path highlight, Paint highlightPaint,
254             int cursorOffsetVertical) {
255         final long lineRange = getLineRangeForDraw(canvas);
256         int firstLine = TextUtils.unpackRangeStartFromLong(lineRange);
257         int lastLine = TextUtils.unpackRangeEndFromLong(lineRange);
258         if (lastLine < 0) return;
259 
260         drawBackground(canvas, highlight, highlightPaint, cursorOffsetVertical,
261                 firstLine, lastLine);
262         drawText(canvas, firstLine, lastLine);
263     }
264 
265     /**
266      * @hide
267      */
drawText(Canvas canvas, int firstLine, int lastLine)268     public void drawText(Canvas canvas, int firstLine, int lastLine) {
269         int previousLineBottom = getLineTop(firstLine);
270         int previousLineEnd = getLineStart(firstLine);
271         ParagraphStyle[] spans = NO_PARA_SPANS;
272         int spanEnd = 0;
273         TextPaint paint = mPaint;
274         CharSequence buf = mText;
275 
276         Alignment paraAlign = mAlignment;
277         TabStops tabStops = null;
278         boolean tabStopsIsInitialized = false;
279 
280         TextLine tl = TextLine.obtain();
281 
282         // Draw the lines, one at a time.
283         // The baseline is the top of the following line minus the current line's descent.
284         for (int lineNum = firstLine; lineNum <= lastLine; lineNum++) {
285             int start = previousLineEnd;
286             previousLineEnd = getLineStart(lineNum + 1);
287             int end = getLineVisibleEnd(lineNum, start, previousLineEnd);
288 
289             int ltop = previousLineBottom;
290             int lbottom = getLineTop(lineNum + 1);
291             previousLineBottom = lbottom;
292             int lbaseline = lbottom - getLineDescent(lineNum);
293 
294             int dir = getParagraphDirection(lineNum);
295             int left = 0;
296             int right = mWidth;
297 
298             if (mSpannedText) {
299                 Spanned sp = (Spanned) buf;
300                 int textLength = buf.length();
301                 boolean isFirstParaLine = (start == 0 || buf.charAt(start - 1) == '\n');
302 
303                 // New batch of paragraph styles, collect into spans array.
304                 // Compute the alignment, last alignment style wins.
305                 // Reset tabStops, we'll rebuild if we encounter a line with
306                 // tabs.
307                 // We expect paragraph spans to be relatively infrequent, use
308                 // spanEnd so that we can check less frequently.  Since
309                 // paragraph styles ought to apply to entire paragraphs, we can
310                 // just collect the ones present at the start of the paragraph.
311                 // If spanEnd is before the end of the paragraph, that's not
312                 // our problem.
313                 if (start >= spanEnd && (lineNum == firstLine || isFirstParaLine)) {
314                     spanEnd = sp.nextSpanTransition(start, textLength,
315                                                     ParagraphStyle.class);
316                     spans = getParagraphSpans(sp, start, spanEnd, ParagraphStyle.class);
317 
318                     paraAlign = mAlignment;
319                     for (int n = spans.length - 1; n >= 0; n--) {
320                         if (spans[n] instanceof AlignmentSpan) {
321                             paraAlign = ((AlignmentSpan) spans[n]).getAlignment();
322                             break;
323                         }
324                     }
325 
326                     tabStopsIsInitialized = false;
327                 }
328 
329                 // Draw all leading margin spans.  Adjust left or right according
330                 // to the paragraph direction of the line.
331                 final int length = spans.length;
332                 boolean useFirstLineMargin = isFirstParaLine;
333                 for (int n = 0; n < length; n++) {
334                     if (spans[n] instanceof LeadingMarginSpan2) {
335                         int count = ((LeadingMarginSpan2) spans[n]).getLeadingMarginLineCount();
336                         int startLine = getLineForOffset(sp.getSpanStart(spans[n]));
337                         // if there is more than one LeadingMarginSpan2, use
338                         // the count that is greatest
339                         if (lineNum < startLine + count) {
340                             useFirstLineMargin = true;
341                             break;
342                         }
343                     }
344                 }
345                 for (int n = 0; n < length; n++) {
346                     if (spans[n] instanceof LeadingMarginSpan) {
347                         LeadingMarginSpan margin = (LeadingMarginSpan) spans[n];
348                         if (dir == DIR_RIGHT_TO_LEFT) {
349                             margin.drawLeadingMargin(canvas, paint, right, dir, ltop,
350                                                      lbaseline, lbottom, buf,
351                                                      start, end, isFirstParaLine, this);
352                             right -= margin.getLeadingMargin(useFirstLineMargin);
353                         } else {
354                             margin.drawLeadingMargin(canvas, paint, left, dir, ltop,
355                                                      lbaseline, lbottom, buf,
356                                                      start, end, isFirstParaLine, this);
357                             left += margin.getLeadingMargin(useFirstLineMargin);
358                         }
359                     }
360                 }
361             }
362 
363             boolean hasTabOrEmoji = getLineContainsTab(lineNum);
364             // Can't tell if we have tabs for sure, currently
365             if (hasTabOrEmoji && !tabStopsIsInitialized) {
366                 if (tabStops == null) {
367                     tabStops = new TabStops(TAB_INCREMENT, spans);
368                 } else {
369                     tabStops.reset(TAB_INCREMENT, spans);
370                 }
371                 tabStopsIsInitialized = true;
372             }
373 
374             // Determine whether the line aligns to normal, opposite, or center.
375             Alignment align = paraAlign;
376             if (align == Alignment.ALIGN_LEFT) {
377                 align = (dir == DIR_LEFT_TO_RIGHT) ?
378                     Alignment.ALIGN_NORMAL : Alignment.ALIGN_OPPOSITE;
379             } else if (align == Alignment.ALIGN_RIGHT) {
380                 align = (dir == DIR_LEFT_TO_RIGHT) ?
381                     Alignment.ALIGN_OPPOSITE : Alignment.ALIGN_NORMAL;
382             }
383 
384             int x;
385             if (align == Alignment.ALIGN_NORMAL) {
386                 if (dir == DIR_LEFT_TO_RIGHT) {
387                     x = left + getIndentAdjust(lineNum, Alignment.ALIGN_LEFT);
388                 } else {
389                     x = right + getIndentAdjust(lineNum, Alignment.ALIGN_RIGHT);
390                 }
391             } else {
392                 int max = (int)getLineExtent(lineNum, tabStops, false);
393                 if (align == Alignment.ALIGN_OPPOSITE) {
394                     if (dir == DIR_LEFT_TO_RIGHT) {
395                         x = right - max + getIndentAdjust(lineNum, Alignment.ALIGN_RIGHT);
396                     } else {
397                         x = left - max + getIndentAdjust(lineNum, Alignment.ALIGN_LEFT);
398                     }
399                 } else { // Alignment.ALIGN_CENTER
400                     max = max & ~1;
401                     x = ((right + left - max) >> 1) +
402                             getIndentAdjust(lineNum, Alignment.ALIGN_CENTER);
403                 }
404             }
405 
406             paint.setHyphenEdit(getHyphen(lineNum));
407             Directions directions = getLineDirections(lineNum);
408             if (directions == DIRS_ALL_LEFT_TO_RIGHT && !mSpannedText && !hasTabOrEmoji) {
409                 // XXX: assumes there's nothing additional to be done
410                 canvas.drawText(buf, start, end, x, lbaseline, paint);
411             } else {
412                 tl.set(paint, buf, start, end, dir, directions, hasTabOrEmoji, tabStops);
413                 tl.draw(canvas, x, ltop, lbaseline, lbottom);
414             }
415             paint.setHyphenEdit(0);
416         }
417 
418         TextLine.recycle(tl);
419     }
420 
421     /**
422      * @hide
423      */
drawBackground(Canvas canvas, Path highlight, Paint highlightPaint, int cursorOffsetVertical, int firstLine, int lastLine)424     public void drawBackground(Canvas canvas, Path highlight, Paint highlightPaint,
425             int cursorOffsetVertical, int firstLine, int lastLine) {
426         // First, draw LineBackgroundSpans.
427         // LineBackgroundSpans know nothing about the alignment, margins, or
428         // direction of the layout or line.  XXX: Should they?
429         // They are evaluated at each line.
430         if (mSpannedText) {
431             if (mLineBackgroundSpans == null) {
432                 mLineBackgroundSpans = new SpanSet<LineBackgroundSpan>(LineBackgroundSpan.class);
433             }
434 
435             Spanned buffer = (Spanned) mText;
436             int textLength = buffer.length();
437             mLineBackgroundSpans.init(buffer, 0, textLength);
438 
439             if (mLineBackgroundSpans.numberOfSpans > 0) {
440                 int previousLineBottom = getLineTop(firstLine);
441                 int previousLineEnd = getLineStart(firstLine);
442                 ParagraphStyle[] spans = NO_PARA_SPANS;
443                 int spansLength = 0;
444                 TextPaint paint = mPaint;
445                 int spanEnd = 0;
446                 final int width = mWidth;
447                 for (int i = firstLine; i <= lastLine; i++) {
448                     int start = previousLineEnd;
449                     int end = getLineStart(i + 1);
450                     previousLineEnd = end;
451 
452                     int ltop = previousLineBottom;
453                     int lbottom = getLineTop(i + 1);
454                     previousLineBottom = lbottom;
455                     int lbaseline = lbottom - getLineDescent(i);
456 
457                     if (start >= spanEnd) {
458                         // These should be infrequent, so we'll use this so that
459                         // we don't have to check as often.
460                         spanEnd = mLineBackgroundSpans.getNextTransition(start, textLength);
461                         // All LineBackgroundSpans on a line contribute to its background.
462                         spansLength = 0;
463                         // Duplication of the logic of getParagraphSpans
464                         if (start != end || start == 0) {
465                             // Equivalent to a getSpans(start, end), but filling the 'spans' local
466                             // array instead to reduce memory allocation
467                             for (int j = 0; j < mLineBackgroundSpans.numberOfSpans; j++) {
468                                 // equal test is valid since both intervals are not empty by
469                                 // construction
470                                 if (mLineBackgroundSpans.spanStarts[j] >= end ||
471                                         mLineBackgroundSpans.spanEnds[j] <= start) continue;
472                                 spans = GrowingArrayUtils.append(
473                                         spans, spansLength, mLineBackgroundSpans.spans[j]);
474                                 spansLength++;
475                             }
476                         }
477                     }
478 
479                     for (int n = 0; n < spansLength; n++) {
480                         LineBackgroundSpan lineBackgroundSpan = (LineBackgroundSpan) spans[n];
481                         lineBackgroundSpan.drawBackground(canvas, paint, 0, width,
482                                 ltop, lbaseline, lbottom,
483                                 buffer, start, end, i);
484                     }
485                 }
486             }
487             mLineBackgroundSpans.recycle();
488         }
489 
490         // There can be a highlight even without spans if we are drawing
491         // a non-spanned transformation of a spanned editing buffer.
492         if (highlight != null) {
493             if (cursorOffsetVertical != 0) canvas.translate(0, cursorOffsetVertical);
494             canvas.drawPath(highlight, highlightPaint);
495             if (cursorOffsetVertical != 0) canvas.translate(0, -cursorOffsetVertical);
496         }
497     }
498 
499     /**
500      * @param canvas
501      * @return The range of lines that need to be drawn, possibly empty.
502      * @hide
503      */
getLineRangeForDraw(Canvas canvas)504     public long getLineRangeForDraw(Canvas canvas) {
505         int dtop, dbottom;
506 
507         synchronized (sTempRect) {
508             if (!canvas.getClipBounds(sTempRect)) {
509                 // Negative range end used as a special flag
510                 return TextUtils.packRangeInLong(0, -1);
511             }
512 
513             dtop = sTempRect.top;
514             dbottom = sTempRect.bottom;
515         }
516 
517         final int top = Math.max(dtop, 0);
518         final int bottom = Math.min(getLineTop(getLineCount()), dbottom);
519 
520         if (top >= bottom) return TextUtils.packRangeInLong(0, -1);
521         return TextUtils.packRangeInLong(getLineForVertical(top), getLineForVertical(bottom));
522     }
523 
524     /**
525      * Return the start position of the line, given the left and right bounds
526      * of the margins.
527      *
528      * @param line the line index
529      * @param left the left bounds (0, or leading margin if ltr para)
530      * @param right the right bounds (width, minus leading margin if rtl para)
531      * @return the start position of the line (to right of line if rtl para)
532      */
getLineStartPos(int line, int left, int right)533     private int getLineStartPos(int line, int left, int right) {
534         // Adjust the point at which to start rendering depending on the
535         // alignment of the paragraph.
536         Alignment align = getParagraphAlignment(line);
537         int dir = getParagraphDirection(line);
538 
539         if (align == Alignment.ALIGN_LEFT) {
540             align = (dir == DIR_LEFT_TO_RIGHT) ? Alignment.ALIGN_NORMAL : Alignment.ALIGN_OPPOSITE;
541         } else if (align == Alignment.ALIGN_RIGHT) {
542             align = (dir == DIR_LEFT_TO_RIGHT) ? Alignment.ALIGN_OPPOSITE : Alignment.ALIGN_NORMAL;
543         }
544 
545         int x;
546         if (align == Alignment.ALIGN_NORMAL) {
547             if (dir == DIR_LEFT_TO_RIGHT) {
548                 x = left + getIndentAdjust(line, Alignment.ALIGN_LEFT);
549             } else {
550                 x = right + getIndentAdjust(line, Alignment.ALIGN_RIGHT);
551             }
552         } else {
553             TabStops tabStops = null;
554             if (mSpannedText && getLineContainsTab(line)) {
555                 Spanned spanned = (Spanned) mText;
556                 int start = getLineStart(line);
557                 int spanEnd = spanned.nextSpanTransition(start, spanned.length(),
558                         TabStopSpan.class);
559                 TabStopSpan[] tabSpans = getParagraphSpans(spanned, start, spanEnd,
560                         TabStopSpan.class);
561                 if (tabSpans.length > 0) {
562                     tabStops = new TabStops(TAB_INCREMENT, tabSpans);
563                 }
564             }
565             int max = (int)getLineExtent(line, tabStops, false);
566             if (align == Alignment.ALIGN_OPPOSITE) {
567                 if (dir == DIR_LEFT_TO_RIGHT) {
568                     x = right - max + getIndentAdjust(line, Alignment.ALIGN_RIGHT);
569                 } else {
570                     // max is negative here
571                     x = left - max + getIndentAdjust(line, Alignment.ALIGN_LEFT);
572                 }
573             } else { // Alignment.ALIGN_CENTER
574                 max = max & ~1;
575                 x = (left + right - max) >> 1 + getIndentAdjust(line, Alignment.ALIGN_CENTER);
576             }
577         }
578         return x;
579     }
580 
581     /**
582      * Return the text that is displayed by this Layout.
583      */
getText()584     public final CharSequence getText() {
585         return mText;
586     }
587 
588     /**
589      * Return the base Paint properties for this layout.
590      * Do NOT change the paint, which may result in funny
591      * drawing for this layout.
592      */
getPaint()593     public final TextPaint getPaint() {
594         return mPaint;
595     }
596 
597     /**
598      * Return the width of this layout.
599      */
getWidth()600     public final int getWidth() {
601         return mWidth;
602     }
603 
604     /**
605      * Return the width to which this Layout is ellipsizing, or
606      * {@link #getWidth} if it is not doing anything special.
607      */
getEllipsizedWidth()608     public int getEllipsizedWidth() {
609         return mWidth;
610     }
611 
612     /**
613      * Increase the width of this layout to the specified width.
614      * Be careful to use this only when you know it is appropriate&mdash;
615      * it does not cause the text to reflow to use the full new width.
616      */
increaseWidthTo(int wid)617     public final void increaseWidthTo(int wid) {
618         if (wid < mWidth) {
619             throw new RuntimeException("attempted to reduce Layout width");
620         }
621 
622         mWidth = wid;
623     }
624 
625     /**
626      * Return the total height of this layout.
627      */
getHeight()628     public int getHeight() {
629         return getLineTop(getLineCount());
630     }
631 
632     /**
633      * Return the base alignment of this layout.
634      */
getAlignment()635     public final Alignment getAlignment() {
636         return mAlignment;
637     }
638 
639     /**
640      * Return what the text height is multiplied by to get the line height.
641      */
getSpacingMultiplier()642     public final float getSpacingMultiplier() {
643         return mSpacingMult;
644     }
645 
646     /**
647      * Return the number of units of leading that are added to each line.
648      */
getSpacingAdd()649     public final float getSpacingAdd() {
650         return mSpacingAdd;
651     }
652 
653     /**
654      * Return the heuristic used to determine paragraph text direction.
655      * @hide
656      */
getTextDirectionHeuristic()657     public final TextDirectionHeuristic getTextDirectionHeuristic() {
658         return mTextDir;
659     }
660 
661     /**
662      * Return the number of lines of text in this layout.
663      */
getLineCount()664     public abstract int getLineCount();
665 
666     /**
667      * Return the baseline for the specified line (0&hellip;getLineCount() - 1)
668      * If bounds is not null, return the top, left, right, bottom extents
669      * of the specified line in it.
670      * @param line which line to examine (0..getLineCount() - 1)
671      * @param bounds Optional. If not null, it returns the extent of the line
672      * @return the Y-coordinate of the baseline
673      */
getLineBounds(int line, Rect bounds)674     public int getLineBounds(int line, Rect bounds) {
675         if (bounds != null) {
676             bounds.left = 0;     // ???
677             bounds.top = getLineTop(line);
678             bounds.right = mWidth;   // ???
679             bounds.bottom = getLineTop(line + 1);
680         }
681         return getLineBaseline(line);
682     }
683 
684     /**
685      * Return the vertical position of the top of the specified line
686      * (0&hellip;getLineCount()).
687      * If the specified line is equal to the line count, returns the
688      * bottom of the last line.
689      */
getLineTop(int line)690     public abstract int getLineTop(int line);
691 
692     /**
693      * Return the descent of the specified line(0&hellip;getLineCount() - 1).
694      */
getLineDescent(int line)695     public abstract int getLineDescent(int line);
696 
697     /**
698      * Return the text offset of the beginning of the specified line (
699      * 0&hellip;getLineCount()). If the specified line is equal to the line
700      * count, returns the length of the text.
701      */
getLineStart(int line)702     public abstract int getLineStart(int line);
703 
704     /**
705      * Returns the primary directionality of the paragraph containing the
706      * specified line, either 1 for left-to-right lines, or -1 for right-to-left
707      * lines (see {@link #DIR_LEFT_TO_RIGHT}, {@link #DIR_RIGHT_TO_LEFT}).
708      */
getParagraphDirection(int line)709     public abstract int getParagraphDirection(int line);
710 
711     /**
712      * Returns whether the specified line contains one or more
713      * characters that need to be handled specially, like tabs
714      * or emoji.
715      */
getLineContainsTab(int line)716     public abstract boolean getLineContainsTab(int line);
717 
718     /**
719      * Returns the directional run information for the specified line.
720      * The array alternates counts of characters in left-to-right
721      * and right-to-left segments of the line.
722      *
723      * <p>NOTE: this is inadequate to support bidirectional text, and will change.
724      */
getLineDirections(int line)725     public abstract Directions getLineDirections(int line);
726 
727     /**
728      * Returns the (negative) number of extra pixels of ascent padding in the
729      * top line of the Layout.
730      */
getTopPadding()731     public abstract int getTopPadding();
732 
733     /**
734      * Returns the number of extra pixels of descent padding in the
735      * bottom line of the Layout.
736      */
getBottomPadding()737     public abstract int getBottomPadding();
738 
739     /**
740      * Returns the hyphen edit for a line.
741      *
742      * @hide
743      */
getHyphen(int line)744     public int getHyphen(int line) {
745         return 0;
746     }
747 
748     /**
749      * Returns the left indent for a line.
750      *
751      * @hide
752      */
getIndentAdjust(int line, Alignment alignment)753     public int getIndentAdjust(int line, Alignment alignment) {
754         return 0;
755     }
756 
757     /**
758      * Returns true if the character at offset and the preceding character
759      * are at different run levels (and thus there's a split caret).
760      * @param offset the offset
761      * @return true if at a level boundary
762      * @hide
763      */
isLevelBoundary(int offset)764     public boolean isLevelBoundary(int offset) {
765         int line = getLineForOffset(offset);
766         Directions dirs = getLineDirections(line);
767         if (dirs == DIRS_ALL_LEFT_TO_RIGHT || dirs == DIRS_ALL_RIGHT_TO_LEFT) {
768             return false;
769         }
770 
771         int[] runs = dirs.mDirections;
772         int lineStart = getLineStart(line);
773         int lineEnd = getLineEnd(line);
774         if (offset == lineStart || offset == lineEnd) {
775             int paraLevel = getParagraphDirection(line) == 1 ? 0 : 1;
776             int runIndex = offset == lineStart ? 0 : runs.length - 2;
777             return ((runs[runIndex + 1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK) != paraLevel;
778         }
779 
780         offset -= lineStart;
781         for (int i = 0; i < runs.length; i += 2) {
782             if (offset == runs[i]) {
783                 return true;
784             }
785         }
786         return false;
787     }
788 
789     /**
790      * Returns true if the character at offset is right to left (RTL).
791      * @param offset the offset
792      * @return true if the character is RTL, false if it is LTR
793      */
isRtlCharAt(int offset)794     public boolean isRtlCharAt(int offset) {
795         int line = getLineForOffset(offset);
796         Directions dirs = getLineDirections(line);
797         if (dirs == DIRS_ALL_LEFT_TO_RIGHT) {
798             return false;
799         }
800         if (dirs == DIRS_ALL_RIGHT_TO_LEFT) {
801             return  true;
802         }
803         int[] runs = dirs.mDirections;
804         int lineStart = getLineStart(line);
805         for (int i = 0; i < runs.length; i += 2) {
806             int start = lineStart + runs[i];
807             int limit = start + (runs[i+1] & RUN_LENGTH_MASK);
808             if (offset >= start && offset < limit) {
809                 int level = (runs[i+1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK;
810                 return ((level & 1) != 0);
811             }
812         }
813         // Should happen only if the offset is "out of bounds"
814         return false;
815     }
816 
primaryIsTrailingPrevious(int offset)817     private boolean primaryIsTrailingPrevious(int offset) {
818         int line = getLineForOffset(offset);
819         int lineStart = getLineStart(line);
820         int lineEnd = getLineEnd(line);
821         int[] runs = getLineDirections(line).mDirections;
822 
823         int levelAt = -1;
824         for (int i = 0; i < runs.length; i += 2) {
825             int start = lineStart + runs[i];
826             int limit = start + (runs[i+1] & RUN_LENGTH_MASK);
827             if (limit > lineEnd) {
828                 limit = lineEnd;
829             }
830             if (offset >= start && offset < limit) {
831                 if (offset > start) {
832                     // Previous character is at same level, so don't use trailing.
833                     return false;
834                 }
835                 levelAt = (runs[i+1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK;
836                 break;
837             }
838         }
839         if (levelAt == -1) {
840             // Offset was limit of line.
841             levelAt = getParagraphDirection(line) == 1 ? 0 : 1;
842         }
843 
844         // At level boundary, check previous level.
845         int levelBefore = -1;
846         if (offset == lineStart) {
847             levelBefore = getParagraphDirection(line) == 1 ? 0 : 1;
848         } else {
849             offset -= 1;
850             for (int i = 0; i < runs.length; i += 2) {
851                 int start = lineStart + runs[i];
852                 int limit = start + (runs[i+1] & RUN_LENGTH_MASK);
853                 if (limit > lineEnd) {
854                     limit = lineEnd;
855                 }
856                 if (offset >= start && offset < limit) {
857                     levelBefore = (runs[i+1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK;
858                     break;
859                 }
860             }
861         }
862 
863         return levelBefore < levelAt;
864     }
865 
866     /**
867      * Get the primary horizontal position for the specified text offset.
868      * This is the location where a new character would be inserted in
869      * the paragraph's primary direction.
870      */
getPrimaryHorizontal(int offset)871     public float getPrimaryHorizontal(int offset) {
872         return getPrimaryHorizontal(offset, false /* not clamped */);
873     }
874 
875     /**
876      * Get the primary horizontal position for the specified text offset, but
877      * optionally clamp it so that it doesn't exceed the width of the layout.
878      * @hide
879      */
getPrimaryHorizontal(int offset, boolean clamped)880     public float getPrimaryHorizontal(int offset, boolean clamped) {
881         boolean trailing = primaryIsTrailingPrevious(offset);
882         return getHorizontal(offset, trailing, clamped);
883     }
884 
885     /**
886      * Get the secondary horizontal position for the specified text offset.
887      * This is the location where a new character would be inserted in
888      * the direction other than the paragraph's primary direction.
889      */
getSecondaryHorizontal(int offset)890     public float getSecondaryHorizontal(int offset) {
891         return getSecondaryHorizontal(offset, false /* not clamped */);
892     }
893 
894     /**
895      * Get the secondary horizontal position for the specified text offset, but
896      * optionally clamp it so that it doesn't exceed the width of the layout.
897      * @hide
898      */
getSecondaryHorizontal(int offset, boolean clamped)899     public float getSecondaryHorizontal(int offset, boolean clamped) {
900         boolean trailing = primaryIsTrailingPrevious(offset);
901         return getHorizontal(offset, !trailing, clamped);
902     }
903 
getHorizontal(int offset, boolean trailing, boolean clamped)904     private float getHorizontal(int offset, boolean trailing, boolean clamped) {
905         int line = getLineForOffset(offset);
906 
907         return getHorizontal(offset, trailing, line, clamped);
908     }
909 
getHorizontal(int offset, boolean trailing, int line, boolean clamped)910     private float getHorizontal(int offset, boolean trailing, int line, boolean clamped) {
911         int start = getLineStart(line);
912         int end = getLineEnd(line);
913         int dir = getParagraphDirection(line);
914         boolean hasTabOrEmoji = getLineContainsTab(line);
915         Directions directions = getLineDirections(line);
916 
917         TabStops tabStops = null;
918         if (hasTabOrEmoji && mText instanceof Spanned) {
919             // Just checking this line should be good enough, tabs should be
920             // consistent across all lines in a paragraph.
921             TabStopSpan[] tabs = getParagraphSpans((Spanned) mText, start, end, TabStopSpan.class);
922             if (tabs.length > 0) {
923                 tabStops = new TabStops(TAB_INCREMENT, tabs); // XXX should reuse
924             }
925         }
926 
927         TextLine tl = TextLine.obtain();
928         tl.set(mPaint, mText, start, end, dir, directions, hasTabOrEmoji, tabStops);
929         float wid = tl.measure(offset - start, trailing, null);
930         TextLine.recycle(tl);
931 
932         if (clamped && wid > mWidth) {
933             wid = mWidth;
934         }
935         int left = getParagraphLeft(line);
936         int right = getParagraphRight(line);
937 
938         return getLineStartPos(line, left, right) + wid;
939     }
940 
941     /**
942      * Get the leftmost position that should be exposed for horizontal
943      * scrolling on the specified line.
944      */
getLineLeft(int line)945     public float getLineLeft(int line) {
946         int dir = getParagraphDirection(line);
947         Alignment align = getParagraphAlignment(line);
948 
949         if (align == Alignment.ALIGN_LEFT) {
950             return 0;
951         } else if (align == Alignment.ALIGN_NORMAL) {
952             if (dir == DIR_RIGHT_TO_LEFT)
953                 return getParagraphRight(line) - getLineMax(line);
954             else
955                 return 0;
956         } else if (align == Alignment.ALIGN_RIGHT) {
957             return mWidth - getLineMax(line);
958         } else if (align == Alignment.ALIGN_OPPOSITE) {
959             if (dir == DIR_RIGHT_TO_LEFT)
960                 return 0;
961             else
962                 return mWidth - getLineMax(line);
963         } else { /* align == Alignment.ALIGN_CENTER */
964             int left = getParagraphLeft(line);
965             int right = getParagraphRight(line);
966             int max = ((int) getLineMax(line)) & ~1;
967 
968             return left + ((right - left) - max) / 2;
969         }
970     }
971 
972     /**
973      * Get the rightmost position that should be exposed for horizontal
974      * scrolling on the specified line.
975      */
getLineRight(int line)976     public float getLineRight(int line) {
977         int dir = getParagraphDirection(line);
978         Alignment align = getParagraphAlignment(line);
979 
980         if (align == Alignment.ALIGN_LEFT) {
981             return getParagraphLeft(line) + getLineMax(line);
982         } else if (align == Alignment.ALIGN_NORMAL) {
983             if (dir == DIR_RIGHT_TO_LEFT)
984                 return mWidth;
985             else
986                 return getParagraphLeft(line) + getLineMax(line);
987         } else if (align == Alignment.ALIGN_RIGHT) {
988             return mWidth;
989         } else if (align == Alignment.ALIGN_OPPOSITE) {
990             if (dir == DIR_RIGHT_TO_LEFT)
991                 return getLineMax(line);
992             else
993                 return mWidth;
994         } else { /* align == Alignment.ALIGN_CENTER */
995             int left = getParagraphLeft(line);
996             int right = getParagraphRight(line);
997             int max = ((int) getLineMax(line)) & ~1;
998 
999             return right - ((right - left) - max) / 2;
1000         }
1001     }
1002 
1003     /**
1004      * Gets the unsigned horizontal extent of the specified line, including
1005      * leading margin indent, but excluding trailing whitespace.
1006      */
getLineMax(int line)1007     public float getLineMax(int line) {
1008         float margin = getParagraphLeadingMargin(line);
1009         float signedExtent = getLineExtent(line, false);
1010         return margin + (signedExtent >= 0 ? signedExtent : -signedExtent);
1011     }
1012 
1013     /**
1014      * Gets the unsigned horizontal extent of the specified line, including
1015      * leading margin indent and trailing whitespace.
1016      */
getLineWidth(int line)1017     public float getLineWidth(int line) {
1018         float margin = getParagraphLeadingMargin(line);
1019         float signedExtent = getLineExtent(line, true);
1020         return margin + (signedExtent >= 0 ? signedExtent : -signedExtent);
1021     }
1022 
1023     /**
1024      * Like {@link #getLineExtent(int,TabStops,boolean)} but determines the
1025      * tab stops instead of using the ones passed in.
1026      * @param line the index of the line
1027      * @param full whether to include trailing whitespace
1028      * @return the extent of the line
1029      */
getLineExtent(int line, boolean full)1030     private float getLineExtent(int line, boolean full) {
1031         int start = getLineStart(line);
1032         int end = full ? getLineEnd(line) : getLineVisibleEnd(line);
1033 
1034         boolean hasTabsOrEmoji = getLineContainsTab(line);
1035         TabStops tabStops = null;
1036         if (hasTabsOrEmoji && mText instanceof Spanned) {
1037             // Just checking this line should be good enough, tabs should be
1038             // consistent across all lines in a paragraph.
1039             TabStopSpan[] tabs = getParagraphSpans((Spanned) mText, start, end, TabStopSpan.class);
1040             if (tabs.length > 0) {
1041                 tabStops = new TabStops(TAB_INCREMENT, tabs); // XXX should reuse
1042             }
1043         }
1044         Directions directions = getLineDirections(line);
1045         // Returned directions can actually be null
1046         if (directions == null) {
1047             return 0f;
1048         }
1049         int dir = getParagraphDirection(line);
1050 
1051         TextLine tl = TextLine.obtain();
1052         tl.set(mPaint, mText, start, end, dir, directions, hasTabsOrEmoji, tabStops);
1053         float width = tl.metrics(null);
1054         TextLine.recycle(tl);
1055         return width;
1056     }
1057 
1058     /**
1059      * Returns the signed horizontal extent of the specified line, excluding
1060      * leading margin.  If full is false, excludes trailing whitespace.
1061      * @param line the index of the line
1062      * @param tabStops the tab stops, can be null if we know they're not used.
1063      * @param full whether to include trailing whitespace
1064      * @return the extent of the text on this line
1065      */
getLineExtent(int line, TabStops tabStops, boolean full)1066     private float getLineExtent(int line, TabStops tabStops, boolean full) {
1067         int start = getLineStart(line);
1068         int end = full ? getLineEnd(line) : getLineVisibleEnd(line);
1069         boolean hasTabsOrEmoji = getLineContainsTab(line);
1070         Directions directions = getLineDirections(line);
1071         int dir = getParagraphDirection(line);
1072 
1073         TextLine tl = TextLine.obtain();
1074         tl.set(mPaint, mText, start, end, dir, directions, hasTabsOrEmoji, tabStops);
1075         float width = tl.metrics(null);
1076         TextLine.recycle(tl);
1077         return width;
1078     }
1079 
1080     /**
1081      * Get the line number corresponding to the specified vertical position.
1082      * If you ask for a position above 0, you get 0; if you ask for a position
1083      * below the bottom of the text, you get the last line.
1084      */
1085     // FIXME: It may be faster to do a linear search for layouts without many lines.
getLineForVertical(int vertical)1086     public int getLineForVertical(int vertical) {
1087         int high = getLineCount(), low = -1, guess;
1088 
1089         while (high - low > 1) {
1090             guess = (high + low) / 2;
1091 
1092             if (getLineTop(guess) > vertical)
1093                 high = guess;
1094             else
1095                 low = guess;
1096         }
1097 
1098         if (low < 0)
1099             return 0;
1100         else
1101             return low;
1102     }
1103 
1104     /**
1105      * Get the line number on which the specified text offset appears.
1106      * If you ask for a position before 0, you get 0; if you ask for a position
1107      * beyond the end of the text, you get the last line.
1108      */
getLineForOffset(int offset)1109     public int getLineForOffset(int offset) {
1110         int high = getLineCount(), low = -1, guess;
1111 
1112         while (high - low > 1) {
1113             guess = (high + low) / 2;
1114 
1115             if (getLineStart(guess) > offset)
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      * Get the character offset on the specified line whose position is
1129      * closest to the specified horizontal position.
1130      */
getOffsetForHorizontal(int line, float horiz)1131     public int getOffsetForHorizontal(int line, float horiz) {
1132         // TODO: use Paint.getOffsetForAdvance to avoid binary search
1133         int max = getLineEnd(line) - 1;
1134         int min = getLineStart(line);
1135         Directions dirs = getLineDirections(line);
1136 
1137         if (line == getLineCount() - 1)
1138             max++;
1139 
1140         int best = min;
1141         float bestdist = Math.abs(getPrimaryHorizontal(best) - horiz);
1142 
1143         for (int i = 0; i < dirs.mDirections.length; i += 2) {
1144             int here = min + dirs.mDirections[i];
1145             int there = here + (dirs.mDirections[i+1] & RUN_LENGTH_MASK);
1146             int swap = (dirs.mDirections[i+1] & RUN_RTL_FLAG) != 0 ? -1 : 1;
1147 
1148             if (there > max)
1149                 there = max;
1150             int high = there - 1 + 1, low = here + 1 - 1, guess;
1151 
1152             while (high - low > 1) {
1153                 guess = (high + low) / 2;
1154                 int adguess = getOffsetAtStartOf(guess);
1155 
1156                 if (getPrimaryHorizontal(adguess) * swap >= horiz * swap)
1157                     high = guess;
1158                 else
1159                     low = guess;
1160             }
1161 
1162             if (low < here + 1)
1163                 low = here + 1;
1164 
1165             if (low < there) {
1166                 low = getOffsetAtStartOf(low);
1167 
1168                 float dist = Math.abs(getPrimaryHorizontal(low) - horiz);
1169 
1170                 int aft = TextUtils.getOffsetAfter(mText, low);
1171                 if (aft < there) {
1172                     float other = Math.abs(getPrimaryHorizontal(aft) - horiz);
1173 
1174                     if (other < dist) {
1175                         dist = other;
1176                         low = aft;
1177                     }
1178                 }
1179 
1180                 if (dist < bestdist) {
1181                     bestdist = dist;
1182                     best = low;
1183                 }
1184             }
1185 
1186             float dist = Math.abs(getPrimaryHorizontal(here) - horiz);
1187 
1188             if (dist < bestdist) {
1189                 bestdist = dist;
1190                 best = here;
1191             }
1192         }
1193 
1194         float dist = Math.abs(getPrimaryHorizontal(max) - horiz);
1195 
1196         if (dist <= bestdist) {
1197             bestdist = dist;
1198             best = max;
1199         }
1200 
1201         return best;
1202     }
1203 
1204     /**
1205      * Return the text offset after the last character on the specified line.
1206      */
getLineEnd(int line)1207     public final int getLineEnd(int line) {
1208         return getLineStart(line + 1);
1209     }
1210 
1211     /**
1212      * Return the text offset after the last visible character (so whitespace
1213      * is not counted) on the specified line.
1214      */
getLineVisibleEnd(int line)1215     public int getLineVisibleEnd(int line) {
1216         return getLineVisibleEnd(line, getLineStart(line), getLineStart(line+1));
1217     }
1218 
getLineVisibleEnd(int line, int start, int end)1219     private int getLineVisibleEnd(int line, int start, int end) {
1220         CharSequence text = mText;
1221         char ch;
1222         if (line == getLineCount() - 1) {
1223             return end;
1224         }
1225 
1226         for (; end > start; end--) {
1227             ch = text.charAt(end - 1);
1228 
1229             if (ch == '\n') {
1230                 return end - 1;
1231             }
1232 
1233             // Note: keep this in sync with Minikin LineBreaker::isLineEndSpace()
1234             if (!(ch == ' ' || ch == '\t' || ch == 0x1680 ||
1235                     (0x2000 <= ch && ch <= 0x200A && ch != 0x2007) ||
1236                     ch == 0x205F || ch == 0x3000)) {
1237                 break;
1238             }
1239 
1240         }
1241 
1242         return end;
1243     }
1244 
1245     /**
1246      * Return the vertical position of the bottom of the specified line.
1247      */
getLineBottom(int line)1248     public final int getLineBottom(int line) {
1249         return getLineTop(line + 1);
1250     }
1251 
1252     /**
1253      * Return the vertical position of the baseline of the specified line.
1254      */
getLineBaseline(int line)1255     public final int getLineBaseline(int line) {
1256         // getLineTop(line+1) == getLineTop(line)
1257         return getLineTop(line+1) - getLineDescent(line);
1258     }
1259 
1260     /**
1261      * Get the ascent of the text on the specified line.
1262      * The return value is negative to match the Paint.ascent() convention.
1263      */
getLineAscent(int line)1264     public final int getLineAscent(int line) {
1265         // getLineTop(line+1) - getLineDescent(line) == getLineBaseLine(line)
1266         return getLineTop(line) - (getLineTop(line+1) - getLineDescent(line));
1267     }
1268 
getOffsetToLeftOf(int offset)1269     public int getOffsetToLeftOf(int offset) {
1270         return getOffsetToLeftRightOf(offset, true);
1271     }
1272 
getOffsetToRightOf(int offset)1273     public int getOffsetToRightOf(int offset) {
1274         return getOffsetToLeftRightOf(offset, false);
1275     }
1276 
getOffsetToLeftRightOf(int caret, boolean toLeft)1277     private int getOffsetToLeftRightOf(int caret, boolean toLeft) {
1278         int line = getLineForOffset(caret);
1279         int lineStart = getLineStart(line);
1280         int lineEnd = getLineEnd(line);
1281         int lineDir = getParagraphDirection(line);
1282 
1283         boolean lineChanged = false;
1284         boolean advance = toLeft == (lineDir == DIR_RIGHT_TO_LEFT);
1285         // if walking off line, look at the line we're headed to
1286         if (advance) {
1287             if (caret == lineEnd) {
1288                 if (line < getLineCount() - 1) {
1289                     lineChanged = true;
1290                     ++line;
1291                 } else {
1292                     return caret; // at very end, don't move
1293                 }
1294             }
1295         } else {
1296             if (caret == lineStart) {
1297                 if (line > 0) {
1298                     lineChanged = true;
1299                     --line;
1300                 } else {
1301                     return caret; // at very start, don't move
1302                 }
1303             }
1304         }
1305 
1306         if (lineChanged) {
1307             lineStart = getLineStart(line);
1308             lineEnd = getLineEnd(line);
1309             int newDir = getParagraphDirection(line);
1310             if (newDir != lineDir) {
1311                 // unusual case.  we want to walk onto the line, but it runs
1312                 // in a different direction than this one, so we fake movement
1313                 // in the opposite direction.
1314                 toLeft = !toLeft;
1315                 lineDir = newDir;
1316             }
1317         }
1318 
1319         Directions directions = getLineDirections(line);
1320 
1321         TextLine tl = TextLine.obtain();
1322         // XXX: we don't care about tabs
1323         tl.set(mPaint, mText, lineStart, lineEnd, lineDir, directions, false, null);
1324         caret = lineStart + tl.getOffsetToLeftRightOf(caret - lineStart, toLeft);
1325         tl = TextLine.recycle(tl);
1326         return caret;
1327     }
1328 
getOffsetAtStartOf(int offset)1329     private int getOffsetAtStartOf(int offset) {
1330         // XXX this probably should skip local reorderings and
1331         // zero-width characters, look at callers
1332         if (offset == 0)
1333             return 0;
1334 
1335         CharSequence text = mText;
1336         char c = text.charAt(offset);
1337 
1338         if (c >= '\uDC00' && c <= '\uDFFF') {
1339             char c1 = text.charAt(offset - 1);
1340 
1341             if (c1 >= '\uD800' && c1 <= '\uDBFF')
1342                 offset -= 1;
1343         }
1344 
1345         if (mSpannedText) {
1346             ReplacementSpan[] spans = ((Spanned) text).getSpans(offset, offset,
1347                                                        ReplacementSpan.class);
1348 
1349             for (int i = 0; i < spans.length; i++) {
1350                 int start = ((Spanned) text).getSpanStart(spans[i]);
1351                 int end = ((Spanned) text).getSpanEnd(spans[i]);
1352 
1353                 if (start < offset && end > offset)
1354                     offset = start;
1355             }
1356         }
1357 
1358         return offset;
1359     }
1360 
1361     /**
1362      * Determine whether we should clamp cursor position. Currently it's
1363      * only robust for left-aligned displays.
1364      * @hide
1365      */
shouldClampCursor(int line)1366     public boolean shouldClampCursor(int line) {
1367         // Only clamp cursor position in left-aligned displays.
1368         switch (getParagraphAlignment(line)) {
1369             case ALIGN_LEFT:
1370                 return true;
1371             case ALIGN_NORMAL:
1372                 return getParagraphDirection(line) > 0;
1373             default:
1374                 return false;
1375         }
1376 
1377     }
1378     /**
1379      * Fills in the specified Path with a representation of a cursor
1380      * at the specified offset.  This will often be a vertical line
1381      * but can be multiple discontinuous lines in text with multiple
1382      * directionalities.
1383      */
getCursorPath(int point, Path dest, CharSequence editingBuffer)1384     public void getCursorPath(int point, Path dest,
1385                               CharSequence editingBuffer) {
1386         dest.reset();
1387 
1388         int line = getLineForOffset(point);
1389         int top = getLineTop(line);
1390         int bottom = getLineTop(line+1);
1391 
1392         boolean clamped = shouldClampCursor(line);
1393         float h1 = getPrimaryHorizontal(point, clamped) - 0.5f;
1394         float h2 = isLevelBoundary(point) ? getSecondaryHorizontal(point, clamped) - 0.5f : h1;
1395 
1396         int caps = TextKeyListener.getMetaState(editingBuffer, TextKeyListener.META_SHIFT_ON) |
1397                    TextKeyListener.getMetaState(editingBuffer, TextKeyListener.META_SELECTING);
1398         int fn = TextKeyListener.getMetaState(editingBuffer, TextKeyListener.META_ALT_ON);
1399         int dist = 0;
1400 
1401         if (caps != 0 || fn != 0) {
1402             dist = (bottom - top) >> 2;
1403 
1404             if (fn != 0)
1405                 top += dist;
1406             if (caps != 0)
1407                 bottom -= dist;
1408         }
1409 
1410         if (h1 < 0.5f)
1411             h1 = 0.5f;
1412         if (h2 < 0.5f)
1413             h2 = 0.5f;
1414 
1415         if (Float.compare(h1, h2) == 0) {
1416             dest.moveTo(h1, top);
1417             dest.lineTo(h1, bottom);
1418         } else {
1419             dest.moveTo(h1, top);
1420             dest.lineTo(h1, (top + bottom) >> 1);
1421 
1422             dest.moveTo(h2, (top + bottom) >> 1);
1423             dest.lineTo(h2, bottom);
1424         }
1425 
1426         if (caps == 2) {
1427             dest.moveTo(h2, bottom);
1428             dest.lineTo(h2 - dist, bottom + dist);
1429             dest.lineTo(h2, bottom);
1430             dest.lineTo(h2 + dist, bottom + dist);
1431         } else if (caps == 1) {
1432             dest.moveTo(h2, bottom);
1433             dest.lineTo(h2 - dist, bottom + dist);
1434 
1435             dest.moveTo(h2 - dist, bottom + dist - 0.5f);
1436             dest.lineTo(h2 + dist, bottom + dist - 0.5f);
1437 
1438             dest.moveTo(h2 + dist, bottom + dist);
1439             dest.lineTo(h2, bottom);
1440         }
1441 
1442         if (fn == 2) {
1443             dest.moveTo(h1, top);
1444             dest.lineTo(h1 - dist, top - dist);
1445             dest.lineTo(h1, top);
1446             dest.lineTo(h1 + dist, top - dist);
1447         } else if (fn == 1) {
1448             dest.moveTo(h1, top);
1449             dest.lineTo(h1 - dist, top - dist);
1450 
1451             dest.moveTo(h1 - dist, top - dist + 0.5f);
1452             dest.lineTo(h1 + dist, top - dist + 0.5f);
1453 
1454             dest.moveTo(h1 + dist, top - dist);
1455             dest.lineTo(h1, top);
1456         }
1457     }
1458 
addSelection(int line, int start, int end, int top, int bottom, Path dest)1459     private void addSelection(int line, int start, int end,
1460                               int top, int bottom, Path dest) {
1461         int linestart = getLineStart(line);
1462         int lineend = getLineEnd(line);
1463         Directions dirs = getLineDirections(line);
1464 
1465         if (lineend > linestart && mText.charAt(lineend - 1) == '\n')
1466             lineend--;
1467 
1468         for (int i = 0; i < dirs.mDirections.length; i += 2) {
1469             int here = linestart + dirs.mDirections[i];
1470             int there = here + (dirs.mDirections[i+1] & RUN_LENGTH_MASK);
1471 
1472             if (there > lineend)
1473                 there = lineend;
1474 
1475             if (start <= there && end >= here) {
1476                 int st = Math.max(start, here);
1477                 int en = Math.min(end, there);
1478 
1479                 if (st != en) {
1480                     float h1 = getHorizontal(st, false, line, false /* not clamped */);
1481                     float h2 = getHorizontal(en, true, line, false /* not clamped */);
1482 
1483                     float left = Math.min(h1, h2);
1484                     float right = Math.max(h1, h2);
1485 
1486                     dest.addRect(left, top, right, bottom, Path.Direction.CW);
1487                 }
1488             }
1489         }
1490     }
1491 
1492     /**
1493      * Fills in the specified Path with a representation of a highlight
1494      * between the specified offsets.  This will often be a rectangle
1495      * or a potentially discontinuous set of rectangles.  If the start
1496      * and end are the same, the returned path is empty.
1497      */
getSelectionPath(int start, int end, Path dest)1498     public void getSelectionPath(int start, int end, Path dest) {
1499         dest.reset();
1500 
1501         if (start == end)
1502             return;
1503 
1504         if (end < start) {
1505             int temp = end;
1506             end = start;
1507             start = temp;
1508         }
1509 
1510         int startline = getLineForOffset(start);
1511         int endline = getLineForOffset(end);
1512 
1513         int top = getLineTop(startline);
1514         int bottom = getLineBottom(endline);
1515 
1516         if (startline == endline) {
1517             addSelection(startline, start, end, top, bottom, dest);
1518         } else {
1519             final float width = mWidth;
1520 
1521             addSelection(startline, start, getLineEnd(startline),
1522                          top, getLineBottom(startline), dest);
1523 
1524             if (getParagraphDirection(startline) == DIR_RIGHT_TO_LEFT)
1525                 dest.addRect(getLineLeft(startline), top,
1526                               0, getLineBottom(startline), Path.Direction.CW);
1527             else
1528                 dest.addRect(getLineRight(startline), top,
1529                               width, getLineBottom(startline), Path.Direction.CW);
1530 
1531             for (int i = startline + 1; i < endline; i++) {
1532                 top = getLineTop(i);
1533                 bottom = getLineBottom(i);
1534                 dest.addRect(0, top, width, bottom, Path.Direction.CW);
1535             }
1536 
1537             top = getLineTop(endline);
1538             bottom = getLineBottom(endline);
1539 
1540             addSelection(endline, getLineStart(endline), end,
1541                          top, bottom, dest);
1542 
1543             if (getParagraphDirection(endline) == DIR_RIGHT_TO_LEFT)
1544                 dest.addRect(width, top, getLineRight(endline), bottom, Path.Direction.CW);
1545             else
1546                 dest.addRect(0, top, getLineLeft(endline), bottom, Path.Direction.CW);
1547         }
1548     }
1549 
1550     /**
1551      * Get the alignment of the specified paragraph, taking into account
1552      * markup attached to it.
1553      */
getParagraphAlignment(int line)1554     public final Alignment getParagraphAlignment(int line) {
1555         Alignment align = mAlignment;
1556 
1557         if (mSpannedText) {
1558             Spanned sp = (Spanned) mText;
1559             AlignmentSpan[] spans = getParagraphSpans(sp, getLineStart(line),
1560                                                 getLineEnd(line),
1561                                                 AlignmentSpan.class);
1562 
1563             int spanLength = spans.length;
1564             if (spanLength > 0) {
1565                 align = spans[spanLength-1].getAlignment();
1566             }
1567         }
1568 
1569         return align;
1570     }
1571 
1572     /**
1573      * Get the left edge of the specified paragraph, inset by left margins.
1574      */
getParagraphLeft(int line)1575     public final int getParagraphLeft(int line) {
1576         int left = 0;
1577         int dir = getParagraphDirection(line);
1578         if (dir == DIR_RIGHT_TO_LEFT || !mSpannedText) {
1579             return left; // leading margin has no impact, or no styles
1580         }
1581         return getParagraphLeadingMargin(line);
1582     }
1583 
1584     /**
1585      * Get the right edge of the specified paragraph, inset by right margins.
1586      */
getParagraphRight(int line)1587     public final int getParagraphRight(int line) {
1588         int right = mWidth;
1589         int dir = getParagraphDirection(line);
1590         if (dir == DIR_LEFT_TO_RIGHT || !mSpannedText) {
1591             return right; // leading margin has no impact, or no styles
1592         }
1593         return right - getParagraphLeadingMargin(line);
1594     }
1595 
1596     /**
1597      * Returns the effective leading margin (unsigned) for this line,
1598      * taking into account LeadingMarginSpan and LeadingMarginSpan2.
1599      * @param line the line index
1600      * @return the leading margin of this line
1601      */
getParagraphLeadingMargin(int line)1602     private int getParagraphLeadingMargin(int line) {
1603         if (!mSpannedText) {
1604             return 0;
1605         }
1606         Spanned spanned = (Spanned) mText;
1607 
1608         int lineStart = getLineStart(line);
1609         int lineEnd = getLineEnd(line);
1610         int spanEnd = spanned.nextSpanTransition(lineStart, lineEnd,
1611                 LeadingMarginSpan.class);
1612         LeadingMarginSpan[] spans = getParagraphSpans(spanned, lineStart, spanEnd,
1613                                                 LeadingMarginSpan.class);
1614         if (spans.length == 0) {
1615             return 0; // no leading margin span;
1616         }
1617 
1618         int margin = 0;
1619 
1620         boolean isFirstParaLine = lineStart == 0 ||
1621             spanned.charAt(lineStart - 1) == '\n';
1622 
1623         boolean useFirstLineMargin = isFirstParaLine;
1624         for (int i = 0; i < spans.length; i++) {
1625             if (spans[i] instanceof LeadingMarginSpan2) {
1626                 int spStart = spanned.getSpanStart(spans[i]);
1627                 int spanLine = getLineForOffset(spStart);
1628                 int count = ((LeadingMarginSpan2) spans[i]).getLeadingMarginLineCount();
1629                 // if there is more than one LeadingMarginSpan2, use the count that is greatest
1630                 useFirstLineMargin |= line < spanLine + count;
1631             }
1632         }
1633         for (int i = 0; i < spans.length; i++) {
1634             LeadingMarginSpan span = spans[i];
1635             margin += span.getLeadingMargin(useFirstLineMargin);
1636         }
1637 
1638         return margin;
1639     }
1640 
1641     /* package */
1642     static float measurePara(TextPaint paint, CharSequence text, int start, int end) {
1643 
1644         MeasuredText mt = MeasuredText.obtain();
1645         TextLine tl = TextLine.obtain();
1646         try {
1647             mt.setPara(text, start, end, TextDirectionHeuristics.LTR, null);
1648             Directions directions;
1649             int dir;
1650             if (mt.mEasy) {
1651                 directions = DIRS_ALL_LEFT_TO_RIGHT;
1652                 dir = Layout.DIR_LEFT_TO_RIGHT;
1653             } else {
1654                 directions = AndroidBidi.directions(mt.mDir, mt.mLevels,
1655                     0, mt.mChars, 0, mt.mLen);
1656                 dir = mt.mDir;
1657             }
1658             char[] chars = mt.mChars;
1659             int len = mt.mLen;
1660             boolean hasTabs = false;
1661             TabStops tabStops = null;
1662             // leading margins should be taken into account when measuring a paragraph
1663             int margin = 0;
1664             if (text instanceof Spanned) {
1665                 Spanned spanned = (Spanned) text;
1666                 LeadingMarginSpan[] spans = getParagraphSpans(spanned, start, end,
1667                         LeadingMarginSpan.class);
1668                 for (LeadingMarginSpan lms : spans) {
1669                     margin += lms.getLeadingMargin(true);
1670                 }
1671             }
1672             for (int i = 0; i < len; ++i) {
1673                 if (chars[i] == '\t') {
1674                     hasTabs = true;
1675                     if (text instanceof Spanned) {
1676                         Spanned spanned = (Spanned) text;
1677                         int spanEnd = spanned.nextSpanTransition(start, end,
1678                                 TabStopSpan.class);
1679                         TabStopSpan[] spans = getParagraphSpans(spanned, start, spanEnd,
1680                                 TabStopSpan.class);
1681                         if (spans.length > 0) {
1682                             tabStops = new TabStops(TAB_INCREMENT, spans);
1683                         }
1684                     }
1685                     break;
1686                 }
1687             }
1688             tl.set(paint, text, start, end, dir, directions, hasTabs, tabStops);
1689             return margin + tl.metrics(null);
1690         } finally {
1691             TextLine.recycle(tl);
1692             MeasuredText.recycle(mt);
1693         }
1694     }
1695 
1696     /**
1697      * @hide
1698      */
1699     /* package */ static class TabStops {
1700         private int[] mStops;
1701         private int mNumStops;
1702         private int mIncrement;
1703 
1704         TabStops(int increment, Object[] spans) {
1705             reset(increment, spans);
1706         }
1707 
1708         void reset(int increment, Object[] spans) {
1709             this.mIncrement = increment;
1710 
1711             int ns = 0;
1712             if (spans != null) {
1713                 int[] stops = this.mStops;
1714                 for (Object o : spans) {
1715                     if (o instanceof TabStopSpan) {
1716                         if (stops == null) {
1717                             stops = new int[10];
1718                         } else if (ns == stops.length) {
1719                             int[] nstops = new int[ns * 2];
1720                             for (int i = 0; i < ns; ++i) {
1721                                 nstops[i] = stops[i];
1722                             }
1723                             stops = nstops;
1724                         }
1725                         stops[ns++] = ((TabStopSpan) o).getTabStop();
1726                     }
1727                 }
1728                 if (ns > 1) {
1729                     Arrays.sort(stops, 0, ns);
1730                 }
1731                 if (stops != this.mStops) {
1732                     this.mStops = stops;
1733                 }
1734             }
1735             this.mNumStops = ns;
1736         }
1737 
1738         float nextTab(float h) {
1739             int ns = this.mNumStops;
1740             if (ns > 0) {
1741                 int[] stops = this.mStops;
1742                 for (int i = 0; i < ns; ++i) {
1743                     int stop = stops[i];
1744                     if (stop > h) {
1745                         return stop;
1746                     }
1747                 }
1748             }
1749             return nextDefaultStop(h, mIncrement);
1750         }
1751 
1752         public static float nextDefaultStop(float h, int inc) {
1753             return ((int) ((h + inc) / inc)) * inc;
1754         }
1755     }
1756 
1757     /**
1758      * Returns the position of the next tab stop after h on the line.
1759      *
1760      * @param text the text
1761      * @param start start of the line
1762      * @param end limit of the line
1763      * @param h the current horizontal offset
1764      * @param tabs the tabs, can be null.  If it is null, any tabs in effect
1765      * on the line will be used.  If there are no tabs, a default offset
1766      * will be used to compute the tab stop.
1767      * @return the offset of the next tab stop.
1768      */
1769     /* package */ static float nextTab(CharSequence text, int start, int end,
1770                                        float h, Object[] tabs) {
1771         float nh = Float.MAX_VALUE;
1772         boolean alltabs = false;
1773 
1774         if (text instanceof Spanned) {
1775             if (tabs == null) {
1776                 tabs = getParagraphSpans((Spanned) text, start, end, TabStopSpan.class);
1777                 alltabs = true;
1778             }
1779 
1780             for (int i = 0; i < tabs.length; i++) {
1781                 if (!alltabs) {
1782                     if (!(tabs[i] instanceof TabStopSpan))
1783                         continue;
1784                 }
1785 
1786                 int where = ((TabStopSpan) tabs[i]).getTabStop();
1787 
1788                 if (where < nh && where > h)
1789                     nh = where;
1790             }
1791 
1792             if (nh != Float.MAX_VALUE)
1793                 return nh;
1794         }
1795 
1796         return ((int) ((h + TAB_INCREMENT) / TAB_INCREMENT)) * TAB_INCREMENT;
1797     }
1798 
1799     protected final boolean isSpanned() {
1800         return mSpannedText;
1801     }
1802 
1803     /**
1804      * Returns the same as <code>text.getSpans()</code>, except where
1805      * <code>start</code> and <code>end</code> are the same and are not
1806      * at the very beginning of the text, in which case an empty array
1807      * is returned instead.
1808      * <p>
1809      * This is needed because of the special case that <code>getSpans()</code>
1810      * on an empty range returns the spans adjacent to that range, which is
1811      * primarily for the sake of <code>TextWatchers</code> so they will get
1812      * notifications when text goes from empty to non-empty.  But it also
1813      * has the unfortunate side effect that if the text ends with an empty
1814      * paragraph, that paragraph accidentally picks up the styles of the
1815      * preceding paragraph (even though those styles will not be picked up
1816      * by new text that is inserted into the empty paragraph).
1817      * <p>
1818      * The reason it just checks whether <code>start</code> and <code>end</code>
1819      * is the same is that the only time a line can contain 0 characters
1820      * is if it is the final paragraph of the Layout; otherwise any line will
1821      * contain at least one printing or newline character.  The reason for the
1822      * additional check if <code>start</code> is greater than 0 is that
1823      * if the empty paragraph is the entire content of the buffer, paragraph
1824      * styles that are already applied to the buffer will apply to text that
1825      * is inserted into it.
1826      */
1827     /* package */static <T> T[] getParagraphSpans(Spanned text, int start, int end, Class<T> type) {
1828         if (start == end && start > 0) {
1829             return ArrayUtils.emptyArray(type);
1830         }
1831 
1832         return text.getSpans(start, end, type);
1833     }
1834 
1835     private char getEllipsisChar(TextUtils.TruncateAt method) {
1836         return (method == TextUtils.TruncateAt.END_SMALL) ?
1837                 TextUtils.ELLIPSIS_TWO_DOTS[0] :
1838                 TextUtils.ELLIPSIS_NORMAL[0];
1839     }
1840 
1841     private void ellipsize(int start, int end, int line,
1842                            char[] dest, int destoff, TextUtils.TruncateAt method) {
1843         int ellipsisCount = getEllipsisCount(line);
1844 
1845         if (ellipsisCount == 0) {
1846             return;
1847         }
1848 
1849         int ellipsisStart = getEllipsisStart(line);
1850         int linestart = getLineStart(line);
1851 
1852         for (int i = ellipsisStart; i < ellipsisStart + ellipsisCount; i++) {
1853             char c;
1854 
1855             if (i == ellipsisStart) {
1856                 c = getEllipsisChar(method); // ellipsis
1857             } else {
1858                 c = '\uFEFF'; // 0-width space
1859             }
1860 
1861             int a = i + linestart;
1862 
1863             if (a >= start && a < end) {
1864                 dest[destoff + a - start] = c;
1865             }
1866         }
1867     }
1868 
1869     /**
1870      * Stores information about bidirectional (left-to-right or right-to-left)
1871      * text within the layout of a line.
1872      */
1873     public static class Directions {
1874         // Directions represents directional runs within a line of text.
1875         // Runs are pairs of ints listed in visual order, starting from the
1876         // leading margin.  The first int of each pair is the offset from
1877         // the first character of the line to the start of the run.  The
1878         // second int represents both the length and level of the run.
1879         // The length is in the lower bits, accessed by masking with
1880         // DIR_LENGTH_MASK.  The level is in the higher bits, accessed
1881         // by shifting by DIR_LEVEL_SHIFT and masking by DIR_LEVEL_MASK.
1882         // To simply test for an RTL direction, test the bit using
1883         // DIR_RTL_FLAG, if set then the direction is rtl.
1884 
1885         /* package */ int[] mDirections;
1886         /* package */ Directions(int[] dirs) {
1887             mDirections = dirs;
1888         }
1889     }
1890 
1891     /**
1892      * Return the offset of the first character to be ellipsized away,
1893      * relative to the start of the line.  (So 0 if the beginning of the
1894      * line is ellipsized, not getLineStart().)
1895      */
1896     public abstract int getEllipsisStart(int line);
1897 
1898     /**
1899      * Returns the number of characters to be ellipsized away, or 0 if
1900      * no ellipsis is to take place.
1901      */
1902     public abstract int getEllipsisCount(int line);
1903 
1904     /* package */ static class Ellipsizer implements CharSequence, GetChars {
1905         /* package */ CharSequence mText;
1906         /* package */ Layout mLayout;
1907         /* package */ int mWidth;
1908         /* package */ TextUtils.TruncateAt mMethod;
1909 
1910         public Ellipsizer(CharSequence s) {
1911             mText = s;
1912         }
1913 
1914         public char charAt(int off) {
1915             char[] buf = TextUtils.obtain(1);
1916             getChars(off, off + 1, buf, 0);
1917             char ret = buf[0];
1918 
1919             TextUtils.recycle(buf);
1920             return ret;
1921         }
1922 
1923         public void getChars(int start, int end, char[] dest, int destoff) {
1924             int line1 = mLayout.getLineForOffset(start);
1925             int line2 = mLayout.getLineForOffset(end);
1926 
1927             TextUtils.getChars(mText, start, end, dest, destoff);
1928 
1929             for (int i = line1; i <= line2; i++) {
1930                 mLayout.ellipsize(start, end, i, dest, destoff, mMethod);
1931             }
1932         }
1933 
1934         public int length() {
1935             return mText.length();
1936         }
1937 
1938         public CharSequence subSequence(int start, int end) {
1939             char[] s = new char[end - start];
1940             getChars(start, end, s, 0);
1941             return new String(s);
1942         }
1943 
1944         @Override
1945         public String toString() {
1946             char[] s = new char[length()];
1947             getChars(0, length(), s, 0);
1948             return new String(s);
1949         }
1950 
1951     }
1952 
1953     /* package */ static class SpannedEllipsizer extends Ellipsizer implements Spanned {
1954         private Spanned mSpanned;
1955 
1956         public SpannedEllipsizer(CharSequence display) {
1957             super(display);
1958             mSpanned = (Spanned) display;
1959         }
1960 
1961         public <T> T[] getSpans(int start, int end, Class<T> type) {
1962             return mSpanned.getSpans(start, end, type);
1963         }
1964 
1965         public int getSpanStart(Object tag) {
1966             return mSpanned.getSpanStart(tag);
1967         }
1968 
1969         public int getSpanEnd(Object tag) {
1970             return mSpanned.getSpanEnd(tag);
1971         }
1972 
1973         public int getSpanFlags(Object tag) {
1974             return mSpanned.getSpanFlags(tag);
1975         }
1976 
1977         @SuppressWarnings("rawtypes")
1978         public int nextSpanTransition(int start, int limit, Class type) {
1979             return mSpanned.nextSpanTransition(start, limit, type);
1980         }
1981 
1982         @Override
1983         public CharSequence subSequence(int start, int end) {
1984             char[] s = new char[end - start];
1985             getChars(start, end, s, 0);
1986 
1987             SpannableString ss = new SpannableString(new String(s));
1988             TextUtils.copySpansFrom(mSpanned, start, end, Object.class, ss, 0);
1989             return ss;
1990         }
1991     }
1992 
1993     private CharSequence mText;
1994     private TextPaint mPaint;
1995     private int mWidth;
1996     private Alignment mAlignment = Alignment.ALIGN_NORMAL;
1997     private float mSpacingMult;
1998     private float mSpacingAdd;
1999     private static final Rect sTempRect = new Rect();
2000     private boolean mSpannedText;
2001     private TextDirectionHeuristic mTextDir;
2002     private SpanSet<LineBackgroundSpan> mLineBackgroundSpans;
2003 
2004     public static final int DIR_LEFT_TO_RIGHT = 1;
2005     public static final int DIR_RIGHT_TO_LEFT = -1;
2006 
2007     /* package */ static final int DIR_REQUEST_LTR = 1;
2008     /* package */ static final int DIR_REQUEST_RTL = -1;
2009     /* package */ static final int DIR_REQUEST_DEFAULT_LTR = 2;
2010     /* package */ static final int DIR_REQUEST_DEFAULT_RTL = -2;
2011 
2012     /* package */ static final int RUN_LENGTH_MASK = 0x03ffffff;
2013     /* package */ static final int RUN_LEVEL_SHIFT = 26;
2014     /* package */ static final int RUN_LEVEL_MASK = 0x3f;
2015     /* package */ static final int RUN_RTL_FLAG = 1 << RUN_LEVEL_SHIFT;
2016 
2017     public enum Alignment {
2018         ALIGN_NORMAL,
2019         ALIGN_OPPOSITE,
2020         ALIGN_CENTER,
2021         /** @hide */
2022         ALIGN_LEFT,
2023         /** @hide */
2024         ALIGN_RIGHT,
2025     }
2026 
2027     private static final int TAB_INCREMENT = 20;
2028 
2029     /* package */ static final Directions DIRS_ALL_LEFT_TO_RIGHT =
2030         new Directions(new int[] { 0, RUN_LENGTH_MASK });
2031     /* package */ static final Directions DIRS_ALL_RIGHT_TO_LEFT =
2032         new Directions(new int[] { 0, RUN_LENGTH_MASK | RUN_RTL_FLAG });
2033 
2034 }
2035