OSDN Git Service

Fix Layout.primaryIsTrailingPreviousAllLineOffsets
[android-x86/frameworks-base.git] / core / java / android / text / Layout.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.IntDef;
20 import android.annotation.IntRange;
21 import android.annotation.UnsupportedAppUsage;
22 import android.graphics.Canvas;
23 import android.graphics.Paint;
24 import android.graphics.Path;
25 import android.graphics.Rect;
26 import android.graphics.text.LineBreaker;
27 import android.text.method.TextKeyListener;
28 import android.text.style.AlignmentSpan;
29 import android.text.style.LeadingMarginSpan;
30 import android.text.style.LeadingMarginSpan.LeadingMarginSpan2;
31 import android.text.style.LineBackgroundSpan;
32 import android.text.style.ParagraphStyle;
33 import android.text.style.ReplacementSpan;
34 import android.text.style.TabStopSpan;
35
36 import com.android.internal.annotations.VisibleForTesting;
37 import com.android.internal.util.ArrayUtils;
38 import com.android.internal.util.GrowingArrayUtils;
39
40 import java.lang.annotation.Retention;
41 import java.lang.annotation.RetentionPolicy;
42 import java.util.Arrays;
43
44 /**
45  * A base class that manages text layout in visual elements on
46  * the screen.
47  * <p>For text that will be edited, use a {@link DynamicLayout},
48  * which will be updated as the text changes.
49  * For text that will not change, use a {@link StaticLayout}.
50  */
51 public abstract class Layout {
52     /** @hide */
53     @IntDef(prefix = { "BREAK_STRATEGY_" }, value = {
54             LineBreaker.BREAK_STRATEGY_SIMPLE,
55             LineBreaker.BREAK_STRATEGY_HIGH_QUALITY,
56             LineBreaker.BREAK_STRATEGY_BALANCED
57     })
58     @Retention(RetentionPolicy.SOURCE)
59     public @interface BreakStrategy {}
60
61     /**
62      * Value for break strategy indicating simple line breaking. Automatic hyphens are not added
63      * (though soft hyphens are respected), and modifying text generally doesn't affect the layout
64      * before it (which yields a more consistent user experience when editing), but layout may not
65      * be the highest quality.
66      */
67     public static final int BREAK_STRATEGY_SIMPLE = LineBreaker.BREAK_STRATEGY_SIMPLE;
68
69     /**
70      * Value for break strategy indicating high quality line breaking, including automatic
71      * hyphenation and doing whole-paragraph optimization of line breaks.
72      */
73     public static final int BREAK_STRATEGY_HIGH_QUALITY = LineBreaker.BREAK_STRATEGY_HIGH_QUALITY;
74
75     /**
76      * Value for break strategy indicating balanced line breaking. The breaks are chosen to
77      * make all lines as close to the same length as possible, including automatic hyphenation.
78      */
79     public static final int BREAK_STRATEGY_BALANCED = LineBreaker.BREAK_STRATEGY_BALANCED;
80
81     /** @hide */
82     @IntDef(prefix = { "HYPHENATION_FREQUENCY_" }, value = {
83             HYPHENATION_FREQUENCY_NORMAL,
84             HYPHENATION_FREQUENCY_FULL,
85             HYPHENATION_FREQUENCY_NONE
86     })
87     @Retention(RetentionPolicy.SOURCE)
88     public @interface HyphenationFrequency {}
89
90     /**
91      * Value for hyphenation frequency indicating no automatic hyphenation. Useful
92      * for backward compatibility, and for cases where the automatic hyphenation algorithm results
93      * in incorrect hyphenation. Mid-word breaks may still happen when a word is wider than the
94      * layout and there is otherwise no valid break. Soft hyphens are ignored and will not be used
95      * as suggestions for potential line breaks.
96      */
97     public static final int HYPHENATION_FREQUENCY_NONE = LineBreaker.HYPHENATION_FREQUENCY_NONE;
98
99     /**
100      * Value for hyphenation frequency indicating a light amount of automatic hyphenation, which
101      * is a conservative default. Useful for informal cases, such as short sentences or chat
102      * messages.
103      */
104     public static final int HYPHENATION_FREQUENCY_NORMAL = LineBreaker.HYPHENATION_FREQUENCY_NORMAL;
105
106     /**
107      * Value for hyphenation frequency indicating the full amount of automatic hyphenation, typical
108      * in typography. Useful for running text and where it's important to put the maximum amount of
109      * text in a screen with limited space.
110      */
111     public static final int HYPHENATION_FREQUENCY_FULL = LineBreaker.HYPHENATION_FREQUENCY_FULL;
112
113     private static final ParagraphStyle[] NO_PARA_SPANS =
114         ArrayUtils.emptyArray(ParagraphStyle.class);
115
116     /** @hide */
117     @IntDef(prefix = { "JUSTIFICATION_MODE_" }, value = {
118             LineBreaker.JUSTIFICATION_MODE_NONE,
119             LineBreaker.JUSTIFICATION_MODE_INTER_WORD
120     })
121     @Retention(RetentionPolicy.SOURCE)
122     public @interface JustificationMode {}
123
124     /**
125      * Value for justification mode indicating no justification.
126      */
127     public static final int JUSTIFICATION_MODE_NONE = LineBreaker.JUSTIFICATION_MODE_NONE;
128
129     /**
130      * Value for justification mode indicating the text is justified by stretching word spacing.
131      */
132     public static final int JUSTIFICATION_MODE_INTER_WORD =
133             LineBreaker.JUSTIFICATION_MODE_INTER_WORD;
134
135     /*
136      * Line spacing multiplier for default line spacing.
137      */
138     public static final float DEFAULT_LINESPACING_MULTIPLIER = 1.0f;
139
140     /*
141      * Line spacing addition for default line spacing.
142      */
143     public static final float DEFAULT_LINESPACING_ADDITION = 0.0f;
144
145     /**
146      * Return how wide a layout must be in order to display the specified text with one line per
147      * paragraph.
148      *
149      * <p>As of O, Uses
150      * {@link TextDirectionHeuristics#FIRSTSTRONG_LTR} as the default text direction heuristics. In
151      * the earlier versions uses {@link TextDirectionHeuristics#LTR} as the default.</p>
152      */
153     public static float getDesiredWidth(CharSequence source,
154                                         TextPaint paint) {
155         return getDesiredWidth(source, 0, source.length(), paint);
156     }
157
158     /**
159      * Return how wide a layout must be in order to display the specified text slice with one
160      * line per paragraph.
161      *
162      * <p>As of O, Uses
163      * {@link TextDirectionHeuristics#FIRSTSTRONG_LTR} as the default text direction heuristics. In
164      * the earlier versions uses {@link TextDirectionHeuristics#LTR} as the default.</p>
165      */
166     public static float getDesiredWidth(CharSequence source, int start, int end, TextPaint paint) {
167         return getDesiredWidth(source, start, end, paint, TextDirectionHeuristics.FIRSTSTRONG_LTR);
168     }
169
170     /**
171      * Return how wide a layout must be in order to display the
172      * specified text slice with one line per paragraph.
173      *
174      * @hide
175      */
176     public static float getDesiredWidth(CharSequence source, int start, int end, TextPaint paint,
177             TextDirectionHeuristic textDir) {
178         return getDesiredWidthWithLimit(source, start, end, paint, textDir, Float.MAX_VALUE);
179     }
180     /**
181      * Return how wide a layout must be in order to display the
182      * specified text slice with one line per paragraph.
183      *
184      * If the measured width exceeds given limit, returns limit value instead.
185      * @hide
186      */
187     public static float getDesiredWidthWithLimit(CharSequence source, int start, int end,
188             TextPaint paint, TextDirectionHeuristic textDir, float upperLimit) {
189         float need = 0;
190
191         int next;
192         for (int i = start; i <= end; i = next) {
193             next = TextUtils.indexOf(source, '\n', i, end);
194
195             if (next < 0)
196                 next = end;
197
198             // note, omits trailing paragraph char
199             float w = measurePara(paint, source, i, next, textDir);
200             if (w > upperLimit) {
201                 return upperLimit;
202             }
203
204             if (w > need)
205                 need = w;
206
207             next++;
208         }
209
210         return need;
211     }
212
213     /**
214      * Subclasses of Layout use this constructor to set the display text,
215      * width, and other standard properties.
216      * @param text the text to render
217      * @param paint the default paint for the layout.  Styles can override
218      * various attributes of the paint.
219      * @param width the wrapping width for the text.
220      * @param align whether to left, right, or center the text.  Styles can
221      * override the alignment.
222      * @param spacingMult factor by which to scale the font size to get the
223      * default line spacing
224      * @param spacingAdd amount to add to the default line spacing
225      */
226     protected Layout(CharSequence text, TextPaint paint,
227                      int width, Alignment align,
228                      float spacingMult, float spacingAdd) {
229         this(text, paint, width, align, TextDirectionHeuristics.FIRSTSTRONG_LTR,
230                 spacingMult, spacingAdd);
231     }
232
233     /**
234      * Subclasses of Layout use this constructor to set the display text,
235      * width, and other standard properties.
236      * @param text the text to render
237      * @param paint the default paint for the layout.  Styles can override
238      * various attributes of the paint.
239      * @param width the wrapping width for the text.
240      * @param align whether to left, right, or center the text.  Styles can
241      * override the alignment.
242      * @param spacingMult factor by which to scale the font size to get the
243      * default line spacing
244      * @param spacingAdd amount to add to the default line spacing
245      *
246      * @hide
247      */
248     protected Layout(CharSequence text, TextPaint paint,
249                      int width, Alignment align, TextDirectionHeuristic textDir,
250                      float spacingMult, float spacingAdd) {
251
252         if (width < 0)
253             throw new IllegalArgumentException("Layout: " + width + " < 0");
254
255         // Ensure paint doesn't have baselineShift set.
256         // While normally we don't modify the paint the user passed in,
257         // we were already doing this in Styled.drawUniformRun with both
258         // baselineShift and bgColor.  We probably should reevaluate bgColor.
259         if (paint != null) {
260             paint.bgColor = 0;
261             paint.baselineShift = 0;
262         }
263
264         mText = text;
265         mPaint = paint;
266         mWidth = width;
267         mAlignment = align;
268         mSpacingMult = spacingMult;
269         mSpacingAdd = spacingAdd;
270         mSpannedText = text instanceof Spanned;
271         mTextDir = textDir;
272     }
273
274     /** @hide */
275     protected void setJustificationMode(@JustificationMode int justificationMode) {
276         mJustificationMode = justificationMode;
277     }
278
279     /**
280      * Replace constructor properties of this Layout with new ones.  Be careful.
281      */
282     /* package */ void replaceWith(CharSequence text, TextPaint paint,
283                               int width, Alignment align,
284                               float spacingmult, float spacingadd) {
285         if (width < 0) {
286             throw new IllegalArgumentException("Layout: " + width + " < 0");
287         }
288
289         mText = text;
290         mPaint = paint;
291         mWidth = width;
292         mAlignment = align;
293         mSpacingMult = spacingmult;
294         mSpacingAdd = spacingadd;
295         mSpannedText = text instanceof Spanned;
296     }
297
298     /**
299      * Draw this Layout on the specified Canvas.
300      */
301     public void draw(Canvas c) {
302         draw(c, null, null, 0);
303     }
304
305     /**
306      * Draw this Layout on the specified canvas, with the highlight path drawn
307      * between the background and the text.
308      *
309      * @param canvas the canvas
310      * @param highlight the path of the highlight or cursor; can be null
311      * @param highlightPaint the paint for the highlight
312      * @param cursorOffsetVertical the amount to temporarily translate the
313      *        canvas while rendering the highlight
314      */
315     public void draw(Canvas canvas, Path highlight, Paint highlightPaint,
316             int cursorOffsetVertical) {
317         final long lineRange = getLineRangeForDraw(canvas);
318         int firstLine = TextUtils.unpackRangeStartFromLong(lineRange);
319         int lastLine = TextUtils.unpackRangeEndFromLong(lineRange);
320         if (lastLine < 0) return;
321
322         drawBackground(canvas, highlight, highlightPaint, cursorOffsetVertical,
323                 firstLine, lastLine);
324         drawText(canvas, firstLine, lastLine);
325     }
326
327     private boolean isJustificationRequired(int lineNum) {
328         if (mJustificationMode == JUSTIFICATION_MODE_NONE) return false;
329         final int lineEnd = getLineEnd(lineNum);
330         return lineEnd < mText.length() && mText.charAt(lineEnd - 1) != '\n';
331     }
332
333     private float getJustifyWidth(int lineNum) {
334         Alignment paraAlign = mAlignment;
335
336         int left = 0;
337         int right = mWidth;
338
339         final int dir = getParagraphDirection(lineNum);
340
341         ParagraphStyle[] spans = NO_PARA_SPANS;
342         if (mSpannedText) {
343             Spanned sp = (Spanned) mText;
344             final int start = getLineStart(lineNum);
345
346             final boolean isFirstParaLine = (start == 0 || mText.charAt(start - 1) == '\n');
347
348             if (isFirstParaLine) {
349                 final int spanEnd = sp.nextSpanTransition(start, mText.length(),
350                         ParagraphStyle.class);
351                 spans = getParagraphSpans(sp, start, spanEnd, ParagraphStyle.class);
352
353                 for (int n = spans.length - 1; n >= 0; n--) {
354                     if (spans[n] instanceof AlignmentSpan) {
355                         paraAlign = ((AlignmentSpan) spans[n]).getAlignment();
356                         break;
357                     }
358                 }
359             }
360
361             final int length = spans.length;
362             boolean useFirstLineMargin = isFirstParaLine;
363             for (int n = 0; n < length; n++) {
364                 if (spans[n] instanceof LeadingMarginSpan2) {
365                     int count = ((LeadingMarginSpan2) spans[n]).getLeadingMarginLineCount();
366                     int startLine = getLineForOffset(sp.getSpanStart(spans[n]));
367                     if (lineNum < startLine + count) {
368                         useFirstLineMargin = true;
369                         break;
370                     }
371                 }
372             }
373             for (int n = 0; n < length; n++) {
374                 if (spans[n] instanceof LeadingMarginSpan) {
375                     LeadingMarginSpan margin = (LeadingMarginSpan) spans[n];
376                     if (dir == DIR_RIGHT_TO_LEFT) {
377                         right -= margin.getLeadingMargin(useFirstLineMargin);
378                     } else {
379                         left += margin.getLeadingMargin(useFirstLineMargin);
380                     }
381                 }
382             }
383         }
384
385         final Alignment align;
386         if (paraAlign == Alignment.ALIGN_LEFT) {
387             align = (dir == DIR_LEFT_TO_RIGHT) ?  Alignment.ALIGN_NORMAL : Alignment.ALIGN_OPPOSITE;
388         } else if (paraAlign == Alignment.ALIGN_RIGHT) {
389             align = (dir == DIR_LEFT_TO_RIGHT) ?  Alignment.ALIGN_OPPOSITE : Alignment.ALIGN_NORMAL;
390         } else {
391             align = paraAlign;
392         }
393
394         final int indentWidth;
395         if (align == Alignment.ALIGN_NORMAL) {
396             if (dir == DIR_LEFT_TO_RIGHT) {
397                 indentWidth = getIndentAdjust(lineNum, Alignment.ALIGN_LEFT);
398             } else {
399                 indentWidth = -getIndentAdjust(lineNum, Alignment.ALIGN_RIGHT);
400             }
401         } else if (align == Alignment.ALIGN_OPPOSITE) {
402             if (dir == DIR_LEFT_TO_RIGHT) {
403                 indentWidth = -getIndentAdjust(lineNum, Alignment.ALIGN_RIGHT);
404             } else {
405                 indentWidth = getIndentAdjust(lineNum, Alignment.ALIGN_LEFT);
406             }
407         } else { // Alignment.ALIGN_CENTER
408             indentWidth = getIndentAdjust(lineNum, Alignment.ALIGN_CENTER);
409         }
410
411         return right - left - indentWidth;
412     }
413
414     /**
415      * @hide
416      */
417     @UnsupportedAppUsage
418     public void drawText(Canvas canvas, int firstLine, int lastLine) {
419         int previousLineBottom = getLineTop(firstLine);
420         int previousLineEnd = getLineStart(firstLine);
421         ParagraphStyle[] spans = NO_PARA_SPANS;
422         int spanEnd = 0;
423         final TextPaint paint = mWorkPaint;
424         paint.set(mPaint);
425         CharSequence buf = mText;
426
427         Alignment paraAlign = mAlignment;
428         TabStops tabStops = null;
429         boolean tabStopsIsInitialized = false;
430
431         TextLine tl = TextLine.obtain();
432
433         // Draw the lines, one at a time.
434         // The baseline is the top of the following line minus the current line's descent.
435         for (int lineNum = firstLine; lineNum <= lastLine; lineNum++) {
436             int start = previousLineEnd;
437             previousLineEnd = getLineStart(lineNum + 1);
438             final boolean justify = isJustificationRequired(lineNum);
439             int end = getLineVisibleEnd(lineNum, start, previousLineEnd);
440             paint.setStartHyphenEdit(getStartHyphenEdit(lineNum));
441             paint.setEndHyphenEdit(getEndHyphenEdit(lineNum));
442
443             int ltop = previousLineBottom;
444             int lbottom = getLineTop(lineNum + 1);
445             previousLineBottom = lbottom;
446             int lbaseline = lbottom - getLineDescent(lineNum);
447
448             int dir = getParagraphDirection(lineNum);
449             int left = 0;
450             int right = mWidth;
451
452             if (mSpannedText) {
453                 Spanned sp = (Spanned) buf;
454                 int textLength = buf.length();
455                 boolean isFirstParaLine = (start == 0 || buf.charAt(start - 1) == '\n');
456
457                 // New batch of paragraph styles, collect into spans array.
458                 // Compute the alignment, last alignment style wins.
459                 // Reset tabStops, we'll rebuild if we encounter a line with
460                 // tabs.
461                 // We expect paragraph spans to be relatively infrequent, use
462                 // spanEnd so that we can check less frequently.  Since
463                 // paragraph styles ought to apply to entire paragraphs, we can
464                 // just collect the ones present at the start of the paragraph.
465                 // If spanEnd is before the end of the paragraph, that's not
466                 // our problem.
467                 if (start >= spanEnd && (lineNum == firstLine || isFirstParaLine)) {
468                     spanEnd = sp.nextSpanTransition(start, textLength,
469                                                     ParagraphStyle.class);
470                     spans = getParagraphSpans(sp, start, spanEnd, ParagraphStyle.class);
471
472                     paraAlign = mAlignment;
473                     for (int n = spans.length - 1; n >= 0; n--) {
474                         if (spans[n] instanceof AlignmentSpan) {
475                             paraAlign = ((AlignmentSpan) spans[n]).getAlignment();
476                             break;
477                         }
478                     }
479
480                     tabStopsIsInitialized = false;
481                 }
482
483                 // Draw all leading margin spans.  Adjust left or right according
484                 // to the paragraph direction of the line.
485                 final int length = spans.length;
486                 boolean useFirstLineMargin = isFirstParaLine;
487                 for (int n = 0; n < length; n++) {
488                     if (spans[n] instanceof LeadingMarginSpan2) {
489                         int count = ((LeadingMarginSpan2) spans[n]).getLeadingMarginLineCount();
490                         int startLine = getLineForOffset(sp.getSpanStart(spans[n]));
491                         // if there is more than one LeadingMarginSpan2, use
492                         // the count that is greatest
493                         if (lineNum < startLine + count) {
494                             useFirstLineMargin = true;
495                             break;
496                         }
497                     }
498                 }
499                 for (int n = 0; n < length; n++) {
500                     if (spans[n] instanceof LeadingMarginSpan) {
501                         LeadingMarginSpan margin = (LeadingMarginSpan) spans[n];
502                         if (dir == DIR_RIGHT_TO_LEFT) {
503                             margin.drawLeadingMargin(canvas, paint, right, dir, ltop,
504                                                      lbaseline, lbottom, buf,
505                                                      start, end, isFirstParaLine, this);
506                             right -= margin.getLeadingMargin(useFirstLineMargin);
507                         } else {
508                             margin.drawLeadingMargin(canvas, paint, left, dir, ltop,
509                                                      lbaseline, lbottom, buf,
510                                                      start, end, isFirstParaLine, this);
511                             left += margin.getLeadingMargin(useFirstLineMargin);
512                         }
513                     }
514                 }
515             }
516
517             boolean hasTab = getLineContainsTab(lineNum);
518             // Can't tell if we have tabs for sure, currently
519             if (hasTab && !tabStopsIsInitialized) {
520                 if (tabStops == null) {
521                     tabStops = new TabStops(TAB_INCREMENT, spans);
522                 } else {
523                     tabStops.reset(TAB_INCREMENT, spans);
524                 }
525                 tabStopsIsInitialized = true;
526             }
527
528             // Determine whether the line aligns to normal, opposite, or center.
529             Alignment align = paraAlign;
530             if (align == Alignment.ALIGN_LEFT) {
531                 align = (dir == DIR_LEFT_TO_RIGHT) ?
532                     Alignment.ALIGN_NORMAL : Alignment.ALIGN_OPPOSITE;
533             } else if (align == Alignment.ALIGN_RIGHT) {
534                 align = (dir == DIR_LEFT_TO_RIGHT) ?
535                     Alignment.ALIGN_OPPOSITE : Alignment.ALIGN_NORMAL;
536             }
537
538             int x;
539             final int indentWidth;
540             if (align == Alignment.ALIGN_NORMAL) {
541                 if (dir == DIR_LEFT_TO_RIGHT) {
542                     indentWidth = getIndentAdjust(lineNum, Alignment.ALIGN_LEFT);
543                     x = left + indentWidth;
544                 } else {
545                     indentWidth = -getIndentAdjust(lineNum, Alignment.ALIGN_RIGHT);
546                     x = right - indentWidth;
547                 }
548             } else {
549                 int max = (int)getLineExtent(lineNum, tabStops, false);
550                 if (align == Alignment.ALIGN_OPPOSITE) {
551                     if (dir == DIR_LEFT_TO_RIGHT) {
552                         indentWidth = -getIndentAdjust(lineNum, Alignment.ALIGN_RIGHT);
553                         x = right - max - indentWidth;
554                     } else {
555                         indentWidth = getIndentAdjust(lineNum, Alignment.ALIGN_LEFT);
556                         x = left - max + indentWidth;
557                     }
558                 } else { // Alignment.ALIGN_CENTER
559                     indentWidth = getIndentAdjust(lineNum, Alignment.ALIGN_CENTER);
560                     max = max & ~1;
561                     x = ((right + left - max) >> 1) + indentWidth;
562                 }
563             }
564
565             Directions directions = getLineDirections(lineNum);
566             if (directions == DIRS_ALL_LEFT_TO_RIGHT && !mSpannedText && !hasTab && !justify) {
567                 // XXX: assumes there's nothing additional to be done
568                 canvas.drawText(buf, start, end, x, lbaseline, paint);
569             } else {
570                 tl.set(paint, buf, start, end, dir, directions, hasTab, tabStops,
571                         getEllipsisStart(lineNum),
572                         getEllipsisStart(lineNum) + getEllipsisCount(lineNum));
573                 if (justify) {
574                     tl.justify(right - left - indentWidth);
575                 }
576                 tl.draw(canvas, x, ltop, lbaseline, lbottom);
577             }
578         }
579
580         TextLine.recycle(tl);
581     }
582
583     /**
584      * @hide
585      */
586     @UnsupportedAppUsage
587     public void drawBackground(Canvas canvas, Path highlight, Paint highlightPaint,
588             int cursorOffsetVertical, int firstLine, int lastLine) {
589         // First, draw LineBackgroundSpans.
590         // LineBackgroundSpans know nothing about the alignment, margins, or
591         // direction of the layout or line.  XXX: Should they?
592         // They are evaluated at each line.
593         if (mSpannedText) {
594             if (mLineBackgroundSpans == null) {
595                 mLineBackgroundSpans = new SpanSet<LineBackgroundSpan>(LineBackgroundSpan.class);
596             }
597
598             Spanned buffer = (Spanned) mText;
599             int textLength = buffer.length();
600             mLineBackgroundSpans.init(buffer, 0, textLength);
601
602             if (mLineBackgroundSpans.numberOfSpans > 0) {
603                 int previousLineBottom = getLineTop(firstLine);
604                 int previousLineEnd = getLineStart(firstLine);
605                 ParagraphStyle[] spans = NO_PARA_SPANS;
606                 int spansLength = 0;
607                 TextPaint paint = mPaint;
608                 int spanEnd = 0;
609                 final int width = mWidth;
610                 for (int i = firstLine; i <= lastLine; i++) {
611                     int start = previousLineEnd;
612                     int end = getLineStart(i + 1);
613                     previousLineEnd = end;
614
615                     int ltop = previousLineBottom;
616                     int lbottom = getLineTop(i + 1);
617                     previousLineBottom = lbottom;
618                     int lbaseline = lbottom - getLineDescent(i);
619
620                     if (end >= spanEnd) {
621                         // These should be infrequent, so we'll use this so that
622                         // we don't have to check as often.
623                         spanEnd = mLineBackgroundSpans.getNextTransition(start, textLength);
624                         // All LineBackgroundSpans on a line contribute to its background.
625                         spansLength = 0;
626                         // Duplication of the logic of getParagraphSpans
627                         if (start != end || start == 0) {
628                             // Equivalent to a getSpans(start, end), but filling the 'spans' local
629                             // array instead to reduce memory allocation
630                             for (int j = 0; j < mLineBackgroundSpans.numberOfSpans; j++) {
631                                 // equal test is valid since both intervals are not empty by
632                                 // construction
633                                 if (mLineBackgroundSpans.spanStarts[j] >= end ||
634                                         mLineBackgroundSpans.spanEnds[j] <= start) continue;
635                                 spans = GrowingArrayUtils.append(
636                                         spans, spansLength, mLineBackgroundSpans.spans[j]);
637                                 spansLength++;
638                             }
639                         }
640                     }
641
642                     for (int n = 0; n < spansLength; n++) {
643                         LineBackgroundSpan lineBackgroundSpan = (LineBackgroundSpan) spans[n];
644                         lineBackgroundSpan.drawBackground(canvas, paint, 0, width,
645                                 ltop, lbaseline, lbottom,
646                                 buffer, start, end, i);
647                     }
648                 }
649             }
650             mLineBackgroundSpans.recycle();
651         }
652
653         // There can be a highlight even without spans if we are drawing
654         // a non-spanned transformation of a spanned editing buffer.
655         if (highlight != null) {
656             if (cursorOffsetVertical != 0) canvas.translate(0, cursorOffsetVertical);
657             canvas.drawPath(highlight, highlightPaint);
658             if (cursorOffsetVertical != 0) canvas.translate(0, -cursorOffsetVertical);
659         }
660     }
661
662     /**
663      * @param canvas
664      * @return The range of lines that need to be drawn, possibly empty.
665      * @hide
666      */
667     @UnsupportedAppUsage
668     public long getLineRangeForDraw(Canvas canvas) {
669         int dtop, dbottom;
670
671         synchronized (sTempRect) {
672             if (!canvas.getClipBounds(sTempRect)) {
673                 // Negative range end used as a special flag
674                 return TextUtils.packRangeInLong(0, -1);
675             }
676
677             dtop = sTempRect.top;
678             dbottom = sTempRect.bottom;
679         }
680
681         final int top = Math.max(dtop, 0);
682         final int bottom = Math.min(getLineTop(getLineCount()), dbottom);
683
684         if (top >= bottom) return TextUtils.packRangeInLong(0, -1);
685         return TextUtils.packRangeInLong(getLineForVertical(top), getLineForVertical(bottom));
686     }
687
688     /**
689      * Return the start position of the line, given the left and right bounds
690      * of the margins.
691      *
692      * @param line the line index
693      * @param left the left bounds (0, or leading margin if ltr para)
694      * @param right the right bounds (width, minus leading margin if rtl para)
695      * @return the start position of the line (to right of line if rtl para)
696      */
697     private int getLineStartPos(int line, int left, int right) {
698         // Adjust the point at which to start rendering depending on the
699         // alignment of the paragraph.
700         Alignment align = getParagraphAlignment(line);
701         int dir = getParagraphDirection(line);
702
703         if (align == Alignment.ALIGN_LEFT) {
704             align = (dir == DIR_LEFT_TO_RIGHT) ? Alignment.ALIGN_NORMAL : Alignment.ALIGN_OPPOSITE;
705         } else if (align == Alignment.ALIGN_RIGHT) {
706             align = (dir == DIR_LEFT_TO_RIGHT) ? Alignment.ALIGN_OPPOSITE : Alignment.ALIGN_NORMAL;
707         }
708
709         int x;
710         if (align == Alignment.ALIGN_NORMAL) {
711             if (dir == DIR_LEFT_TO_RIGHT) {
712                 x = left + getIndentAdjust(line, Alignment.ALIGN_LEFT);
713             } else {
714                 x = right + getIndentAdjust(line, Alignment.ALIGN_RIGHT);
715             }
716         } else {
717             TabStops tabStops = null;
718             if (mSpannedText && getLineContainsTab(line)) {
719                 Spanned spanned = (Spanned) mText;
720                 int start = getLineStart(line);
721                 int spanEnd = spanned.nextSpanTransition(start, spanned.length(),
722                         TabStopSpan.class);
723                 TabStopSpan[] tabSpans = getParagraphSpans(spanned, start, spanEnd,
724                         TabStopSpan.class);
725                 if (tabSpans.length > 0) {
726                     tabStops = new TabStops(TAB_INCREMENT, tabSpans);
727                 }
728             }
729             int max = (int)getLineExtent(line, tabStops, false);
730             if (align == Alignment.ALIGN_OPPOSITE) {
731                 if (dir == DIR_LEFT_TO_RIGHT) {
732                     x = right - max + getIndentAdjust(line, Alignment.ALIGN_RIGHT);
733                 } else {
734                     // max is negative here
735                     x = left - max + getIndentAdjust(line, Alignment.ALIGN_LEFT);
736                 }
737             } else { // Alignment.ALIGN_CENTER
738                 max = max & ~1;
739                 x = (left + right - max) >> 1 + getIndentAdjust(line, Alignment.ALIGN_CENTER);
740             }
741         }
742         return x;
743     }
744
745     /**
746      * Return the text that is displayed by this Layout.
747      */
748     public final CharSequence getText() {
749         return mText;
750     }
751
752     /**
753      * Return the base Paint properties for this layout.
754      * Do NOT change the paint, which may result in funny
755      * drawing for this layout.
756      */
757     public final TextPaint getPaint() {
758         return mPaint;
759     }
760
761     /**
762      * Return the width of this layout.
763      */
764     public final int getWidth() {
765         return mWidth;
766     }
767
768     /**
769      * Return the width to which this Layout is ellipsizing, or
770      * {@link #getWidth} if it is not doing anything special.
771      */
772     public int getEllipsizedWidth() {
773         return mWidth;
774     }
775
776     /**
777      * Increase the width of this layout to the specified width.
778      * Be careful to use this only when you know it is appropriate&mdash;
779      * it does not cause the text to reflow to use the full new width.
780      */
781     public final void increaseWidthTo(int wid) {
782         if (wid < mWidth) {
783             throw new RuntimeException("attempted to reduce Layout width");
784         }
785
786         mWidth = wid;
787     }
788
789     /**
790      * Return the total height of this layout.
791      */
792     public int getHeight() {
793         return getLineTop(getLineCount());
794     }
795
796     /**
797      * Return the total height of this layout.
798      *
799      * @param cap if true and max lines is set, returns the height of the layout at the max lines.
800      *
801      * @hide
802      */
803     public int getHeight(boolean cap) {
804         return getHeight();
805     }
806
807     /**
808      * Return the base alignment of this layout.
809      */
810     public final Alignment getAlignment() {
811         return mAlignment;
812     }
813
814     /**
815      * Return what the text height is multiplied by to get the line height.
816      */
817     public final float getSpacingMultiplier() {
818         return mSpacingMult;
819     }
820
821     /**
822      * Return the number of units of leading that are added to each line.
823      */
824     public final float getSpacingAdd() {
825         return mSpacingAdd;
826     }
827
828     /**
829      * Return the heuristic used to determine paragraph text direction.
830      * @hide
831      */
832     public final TextDirectionHeuristic getTextDirectionHeuristic() {
833         return mTextDir;
834     }
835
836     /**
837      * Return the number of lines of text in this layout.
838      */
839     public abstract int getLineCount();
840
841     /**
842      * Return the baseline for the specified line (0&hellip;getLineCount() - 1)
843      * If bounds is not null, return the top, left, right, bottom extents
844      * of the specified line in it.
845      * @param line which line to examine (0..getLineCount() - 1)
846      * @param bounds Optional. If not null, it returns the extent of the line
847      * @return the Y-coordinate of the baseline
848      */
849     public int getLineBounds(int line, Rect bounds) {
850         if (bounds != null) {
851             bounds.left = 0;     // ???
852             bounds.top = getLineTop(line);
853             bounds.right = mWidth;   // ???
854             bounds.bottom = getLineTop(line + 1);
855         }
856         return getLineBaseline(line);
857     }
858
859     /**
860      * Return the vertical position of the top of the specified line
861      * (0&hellip;getLineCount()).
862      * If the specified line is equal to the line count, returns the
863      * bottom of the last line.
864      */
865     public abstract int getLineTop(int line);
866
867     /**
868      * Return the descent of the specified line(0&hellip;getLineCount() - 1).
869      */
870     public abstract int getLineDescent(int line);
871
872     /**
873      * Return the text offset of the beginning of the specified line (
874      * 0&hellip;getLineCount()). If the specified line is equal to the line
875      * count, returns the length of the text.
876      */
877     public abstract int getLineStart(int line);
878
879     /**
880      * Returns the primary directionality of the paragraph containing the
881      * specified line, either 1 for left-to-right lines, or -1 for right-to-left
882      * lines (see {@link #DIR_LEFT_TO_RIGHT}, {@link #DIR_RIGHT_TO_LEFT}).
883      */
884     public abstract int getParagraphDirection(int line);
885
886     /**
887      * Returns whether the specified line contains one or more
888      * characters that need to be handled specially, like tabs.
889      */
890     public abstract boolean getLineContainsTab(int line);
891
892     /**
893      * Returns the directional run information for the specified line.
894      * The array alternates counts of characters in left-to-right
895      * and right-to-left segments of the line.
896      *
897      * <p>NOTE: this is inadequate to support bidirectional text, and will change.
898      */
899     public abstract Directions getLineDirections(int line);
900
901     /**
902      * Returns the (negative) number of extra pixels of ascent padding in the
903      * top line of the Layout.
904      */
905     public abstract int getTopPadding();
906
907     /**
908      * Returns the number of extra pixels of descent padding in the
909      * bottom line of the Layout.
910      */
911     public abstract int getBottomPadding();
912
913     /**
914      * Returns the start hyphen edit for a line.
915      *
916      * @hide
917      */
918     public @Paint.StartHyphenEdit int getStartHyphenEdit(int line) {
919         return Paint.START_HYPHEN_EDIT_NO_EDIT;
920     }
921
922     /**
923      * Returns the end hyphen edit for a line.
924      *
925      * @hide
926      */
927     public @Paint.EndHyphenEdit int getEndHyphenEdit(int line) {
928         return Paint.END_HYPHEN_EDIT_NO_EDIT;
929     }
930
931     /**
932      * Returns the left indent for a line.
933      *
934      * @hide
935      */
936     public int getIndentAdjust(int line, Alignment alignment) {
937         return 0;
938     }
939
940     /**
941      * Returns true if the character at offset and the preceding character
942      * are at different run levels (and thus there's a split caret).
943      * @param offset the offset
944      * @return true if at a level boundary
945      * @hide
946      */
947     @UnsupportedAppUsage
948     public boolean isLevelBoundary(int offset) {
949         int line = getLineForOffset(offset);
950         Directions dirs = getLineDirections(line);
951         if (dirs == DIRS_ALL_LEFT_TO_RIGHT || dirs == DIRS_ALL_RIGHT_TO_LEFT) {
952             return false;
953         }
954
955         int[] runs = dirs.mDirections;
956         int lineStart = getLineStart(line);
957         int lineEnd = getLineEnd(line);
958         if (offset == lineStart || offset == lineEnd) {
959             int paraLevel = getParagraphDirection(line) == 1 ? 0 : 1;
960             int runIndex = offset == lineStart ? 0 : runs.length - 2;
961             return ((runs[runIndex + 1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK) != paraLevel;
962         }
963
964         offset -= lineStart;
965         for (int i = 0; i < runs.length; i += 2) {
966             if (offset == runs[i]) {
967                 return true;
968             }
969         }
970         return false;
971     }
972
973     /**
974      * Returns true if the character at offset is right to left (RTL).
975      * @param offset the offset
976      * @return true if the character is RTL, false if it is LTR
977      */
978     public boolean isRtlCharAt(int offset) {
979         int line = getLineForOffset(offset);
980         Directions dirs = getLineDirections(line);
981         if (dirs == DIRS_ALL_LEFT_TO_RIGHT) {
982             return false;
983         }
984         if (dirs == DIRS_ALL_RIGHT_TO_LEFT) {
985             return  true;
986         }
987         int[] runs = dirs.mDirections;
988         int lineStart = getLineStart(line);
989         for (int i = 0; i < runs.length; i += 2) {
990             int start = lineStart + runs[i];
991             int limit = start + (runs[i+1] & RUN_LENGTH_MASK);
992             if (offset >= start && offset < limit) {
993                 int level = (runs[i+1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK;
994                 return ((level & 1) != 0);
995             }
996         }
997         // Should happen only if the offset is "out of bounds"
998         return false;
999     }
1000
1001     /**
1002      * Returns the range of the run that the character at offset belongs to.
1003      * @param offset the offset
1004      * @return The range of the run
1005      * @hide
1006      */
1007     public long getRunRange(int offset) {
1008         int line = getLineForOffset(offset);
1009         Directions dirs = getLineDirections(line);
1010         if (dirs == DIRS_ALL_LEFT_TO_RIGHT || dirs == DIRS_ALL_RIGHT_TO_LEFT) {
1011             return TextUtils.packRangeInLong(0, getLineEnd(line));
1012         }
1013         int[] runs = dirs.mDirections;
1014         int lineStart = getLineStart(line);
1015         for (int i = 0; i < runs.length; i += 2) {
1016             int start = lineStart + runs[i];
1017             int limit = start + (runs[i+1] & RUN_LENGTH_MASK);
1018             if (offset >= start && offset < limit) {
1019                 return TextUtils.packRangeInLong(start, limit);
1020             }
1021         }
1022         // Should happen only if the offset is "out of bounds"
1023         return TextUtils.packRangeInLong(0, getLineEnd(line));
1024     }
1025
1026     /**
1027      * Checks if the trailing BiDi level should be used for an offset
1028      *
1029      * This method is useful when the offset is at the BiDi level transition point and determine
1030      * which run need to be used. For example, let's think about following input: (L* denotes
1031      * Left-to-Right characters, R* denotes Right-to-Left characters.)
1032      * Input (Logical Order): L1 L2 L3 R1 R2 R3 L4 L5 L6
1033      * Input (Display Order): L1 L2 L3 R3 R2 R1 L4 L5 L6
1034      *
1035      * Then, think about selecting the range (3, 6). The offset=3 and offset=6 are ambiguous here
1036      * since they are at the BiDi transition point.  In Android, the offset is considered to be
1037      * associated with the trailing run if the BiDi level of the trailing run is higher than of the
1038      * previous run.  In this case, the BiDi level of the input text is as follows:
1039      *
1040      * Input (Logical Order): L1 L2 L3 R1 R2 R3 L4 L5 L6
1041      *              BiDi Run: [ Run 0 ][ Run 1 ][ Run 2 ]
1042      *            BiDi Level:  0  0  0  1  1  1  0  0  0
1043      *
1044      * Thus, offset = 3 is part of Run 1 and this method returns true for offset = 3, since the BiDi
1045      * level of Run 1 is higher than the level of Run 0.  Similarly, the offset = 6 is a part of Run
1046      * 1 and this method returns false for the offset = 6 since the BiDi level of Run 1 is higher
1047      * than the level of Run 2.
1048      *
1049      * @returns true if offset is at the BiDi level transition point and trailing BiDi level is
1050      *          higher than previous BiDi level. See above for the detail.
1051      * @hide
1052      */
1053     @VisibleForTesting
1054     public boolean primaryIsTrailingPrevious(int offset) {
1055         int line = getLineForOffset(offset);
1056         int lineStart = getLineStart(line);
1057         int lineEnd = getLineEnd(line);
1058         int[] runs = getLineDirections(line).mDirections;
1059
1060         int levelAt = -1;
1061         for (int i = 0; i < runs.length; i += 2) {
1062             int start = lineStart + runs[i];
1063             int limit = start + (runs[i+1] & RUN_LENGTH_MASK);
1064             if (limit > lineEnd) {
1065                 limit = lineEnd;
1066             }
1067             if (offset >= start && offset < limit) {
1068                 if (offset > start) {
1069                     // Previous character is at same level, so don't use trailing.
1070                     return false;
1071                 }
1072                 levelAt = (runs[i+1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK;
1073                 break;
1074             }
1075         }
1076         if (levelAt == -1) {
1077             // Offset was limit of line.
1078             levelAt = getParagraphDirection(line) == 1 ? 0 : 1;
1079         }
1080
1081         // At level boundary, check previous level.
1082         int levelBefore = -1;
1083         if (offset == lineStart) {
1084             levelBefore = getParagraphDirection(line) == 1 ? 0 : 1;
1085         } else {
1086             offset -= 1;
1087             for (int i = 0; i < runs.length; i += 2) {
1088                 int start = lineStart + runs[i];
1089                 int limit = start + (runs[i+1] & RUN_LENGTH_MASK);
1090                 if (limit > lineEnd) {
1091                     limit = lineEnd;
1092                 }
1093                 if (offset >= start && offset < limit) {
1094                     levelBefore = (runs[i+1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK;
1095                     break;
1096                 }
1097             }
1098         }
1099
1100         return levelBefore < levelAt;
1101     }
1102
1103     /**
1104      * Computes in linear time the results of calling
1105      * #primaryIsTrailingPrevious for all offsets on a line.
1106      * @param line The line giving the offsets we compute the information for
1107      * @return The array of results, indexed from 0, where 0 corresponds to the line start offset
1108      * @hide
1109      */
1110     @VisibleForTesting
1111     public boolean[] primaryIsTrailingPreviousAllLineOffsets(int line) {
1112         int lineStart = getLineStart(line);
1113         int lineEnd = getLineEnd(line);
1114         int[] runs = getLineDirections(line).mDirections;
1115
1116         boolean[] trailing = new boolean[lineEnd - lineStart + 1];
1117
1118         byte[] level = new byte[lineEnd - lineStart + 1];
1119         for (int i = 0; i < runs.length; i += 2) {
1120             int start = lineStart + runs[i];
1121             int limit = start + (runs[i + 1] & RUN_LENGTH_MASK);
1122             if (limit > lineEnd) {
1123                 limit = lineEnd;
1124             }
1125             if (limit == start) {
1126                 continue;
1127             }
1128             level[limit - lineStart - 1] =
1129                     (byte) ((runs[i + 1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK);
1130         }
1131
1132         for (int i = 0; i < runs.length; i += 2) {
1133             int start = lineStart + runs[i];
1134             byte currentLevel = (byte) ((runs[i + 1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK);
1135             trailing[start - lineStart] = currentLevel > (start == lineStart
1136                     ? (getParagraphDirection(line) == 1 ? 0 : 1)
1137                     : level[start - lineStart - 1]);
1138         }
1139
1140         return trailing;
1141     }
1142
1143     /**
1144      * Get the primary horizontal position for the specified text offset.
1145      * This is the location where a new character would be inserted in
1146      * the paragraph's primary direction.
1147      */
1148     public float getPrimaryHorizontal(int offset) {
1149         return getPrimaryHorizontal(offset, false /* not clamped */);
1150     }
1151
1152     /**
1153      * Get the primary horizontal position for the specified text offset, but
1154      * optionally clamp it so that it doesn't exceed the width of the layout.
1155      * @hide
1156      */
1157     @UnsupportedAppUsage
1158     public float getPrimaryHorizontal(int offset, boolean clamped) {
1159         boolean trailing = primaryIsTrailingPrevious(offset);
1160         return getHorizontal(offset, trailing, clamped);
1161     }
1162
1163     /**
1164      * Get the secondary horizontal position for the specified text offset.
1165      * This is the location where a new character would be inserted in
1166      * the direction other than the paragraph's primary direction.
1167      */
1168     public float getSecondaryHorizontal(int offset) {
1169         return getSecondaryHorizontal(offset, false /* not clamped */);
1170     }
1171
1172     /**
1173      * Get the secondary horizontal position for the specified text offset, but
1174      * optionally clamp it so that it doesn't exceed the width of the layout.
1175      * @hide
1176      */
1177     @UnsupportedAppUsage
1178     public float getSecondaryHorizontal(int offset, boolean clamped) {
1179         boolean trailing = primaryIsTrailingPrevious(offset);
1180         return getHorizontal(offset, !trailing, clamped);
1181     }
1182
1183     private float getHorizontal(int offset, boolean primary) {
1184         return primary ? getPrimaryHorizontal(offset) : getSecondaryHorizontal(offset);
1185     }
1186
1187     private float getHorizontal(int offset, boolean trailing, boolean clamped) {
1188         int line = getLineForOffset(offset);
1189
1190         return getHorizontal(offset, trailing, line, clamped);
1191     }
1192
1193     private float getHorizontal(int offset, boolean trailing, int line, boolean clamped) {
1194         int start = getLineStart(line);
1195         int end = getLineEnd(line);
1196         int dir = getParagraphDirection(line);
1197         boolean hasTab = getLineContainsTab(line);
1198         Directions directions = getLineDirections(line);
1199
1200         TabStops tabStops = null;
1201         if (hasTab && mText instanceof Spanned) {
1202             // Just checking this line should be good enough, tabs should be
1203             // consistent across all lines in a paragraph.
1204             TabStopSpan[] tabs = getParagraphSpans((Spanned) mText, start, end, TabStopSpan.class);
1205             if (tabs.length > 0) {
1206                 tabStops = new TabStops(TAB_INCREMENT, tabs); // XXX should reuse
1207             }
1208         }
1209
1210         TextLine tl = TextLine.obtain();
1211         tl.set(mPaint, mText, start, end, dir, directions, hasTab, tabStops,
1212                 getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line));
1213         float wid = tl.measure(offset - start, trailing, null);
1214         TextLine.recycle(tl);
1215
1216         if (clamped && wid > mWidth) {
1217             wid = mWidth;
1218         }
1219         int left = getParagraphLeft(line);
1220         int right = getParagraphRight(line);
1221
1222         return getLineStartPos(line, left, right) + wid;
1223     }
1224
1225     /**
1226      * Computes in linear time the results of calling #getHorizontal for all offsets on a line.
1227      *
1228      * @param line The line giving the offsets we compute information for
1229      * @param clamped Whether to clamp the results to the width of the layout
1230      * @param primary Whether the results should be the primary or the secondary horizontal
1231      * @return The array of results, indexed from 0, where 0 corresponds to the line start offset
1232      */
1233     private float[] getLineHorizontals(int line, boolean clamped, boolean primary) {
1234         int start = getLineStart(line);
1235         int end = getLineEnd(line);
1236         int dir = getParagraphDirection(line);
1237         boolean hasTab = getLineContainsTab(line);
1238         Directions directions = getLineDirections(line);
1239
1240         TabStops tabStops = null;
1241         if (hasTab && mText instanceof Spanned) {
1242             // Just checking this line should be good enough, tabs should be
1243             // consistent across all lines in a paragraph.
1244             TabStopSpan[] tabs = getParagraphSpans((Spanned) mText, start, end, TabStopSpan.class);
1245             if (tabs.length > 0) {
1246                 tabStops = new TabStops(TAB_INCREMENT, tabs); // XXX should reuse
1247             }
1248         }
1249
1250         TextLine tl = TextLine.obtain();
1251         tl.set(mPaint, mText, start, end, dir, directions, hasTab, tabStops,
1252                 getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line));
1253         boolean[] trailings = primaryIsTrailingPreviousAllLineOffsets(line);
1254         if (!primary) {
1255             for (int offset = 0; offset < trailings.length; ++offset) {
1256                 trailings[offset] = !trailings[offset];
1257             }
1258         }
1259         float[] wid = tl.measureAllOffsets(trailings, null);
1260         TextLine.recycle(tl);
1261
1262         if (clamped) {
1263             for (int offset = 0; offset < wid.length; ++offset) {
1264                 if (wid[offset] > mWidth) {
1265                     wid[offset] = mWidth;
1266                 }
1267             }
1268         }
1269         int left = getParagraphLeft(line);
1270         int right = getParagraphRight(line);
1271
1272         int lineStartPos = getLineStartPos(line, left, right);
1273         float[] horizontal = new float[end - start + 1];
1274         for (int offset = 0; offset < horizontal.length; ++offset) {
1275             horizontal[offset] = lineStartPos + wid[offset];
1276         }
1277         return horizontal;
1278     }
1279
1280     /**
1281      * Get the leftmost position that should be exposed for horizontal
1282      * scrolling on the specified line.
1283      */
1284     public float getLineLeft(int line) {
1285         final int dir = getParagraphDirection(line);
1286         Alignment align = getParagraphAlignment(line);
1287         // Before Q, StaticLayout.Builder.setAlignment didn't check whether the input alignment
1288         // is null. And when it is null, the old behavior is the same as ALIGN_CENTER.
1289         // To keep consistency, we convert a null alignment to ALIGN_CENTER.
1290         if (align == null) {
1291             align = Alignment.ALIGN_CENTER;
1292         }
1293
1294         // First convert combinations of alignment and direction settings to
1295         // three basic cases: ALIGN_LEFT, ALIGN_RIGHT and ALIGN_CENTER.
1296         // For unexpected cases, it will fallback to ALIGN_LEFT.
1297         final Alignment resultAlign;
1298         switch(align) {
1299             case ALIGN_NORMAL:
1300                 resultAlign =
1301                         dir == DIR_RIGHT_TO_LEFT ? Alignment.ALIGN_RIGHT : Alignment.ALIGN_LEFT;
1302                 break;
1303             case ALIGN_OPPOSITE:
1304                 resultAlign =
1305                         dir == DIR_RIGHT_TO_LEFT ? Alignment.ALIGN_LEFT : Alignment.ALIGN_RIGHT;
1306                 break;
1307             case ALIGN_CENTER:
1308                 resultAlign = Alignment.ALIGN_CENTER;
1309                 break;
1310             case ALIGN_RIGHT:
1311                 resultAlign = Alignment.ALIGN_RIGHT;
1312                 break;
1313             default: /* align == Alignment.ALIGN_LEFT */
1314                 resultAlign = Alignment.ALIGN_LEFT;
1315         }
1316
1317         // Here we must use getLineMax() to do the computation, because it maybe overridden by
1318         // derived class. And also note that line max equals the width of the text in that line
1319         // plus the leading margin.
1320         switch (resultAlign) {
1321             case ALIGN_CENTER:
1322                 final int left = getParagraphLeft(line);
1323                 final float max = getLineMax(line);
1324                 // This computation only works when mWidth equals leadingMargin plus
1325                 // the width of text in this line. If this condition doesn't meet anymore,
1326                 // please change here too.
1327                 return (float) Math.floor(left + (mWidth - max) / 2);
1328             case ALIGN_RIGHT:
1329                 return mWidth - getLineMax(line);
1330             default: /* resultAlign == Alignment.ALIGN_LEFT */
1331                 return 0;
1332         }
1333     }
1334
1335     /**
1336      * Get the rightmost position that should be exposed for horizontal
1337      * scrolling on the specified line.
1338      */
1339     public float getLineRight(int line) {
1340         final int dir = getParagraphDirection(line);
1341         Alignment align = getParagraphAlignment(line);
1342         // Before Q, StaticLayout.Builder.setAlignment didn't check whether the input alignment
1343         // is null. And when it is null, the old behavior is the same as ALIGN_CENTER.
1344         // To keep consistency, we convert a null alignment to ALIGN_CENTER.
1345         if (align == null) {
1346             align = Alignment.ALIGN_CENTER;
1347         }
1348
1349         final Alignment resultAlign;
1350         switch(align) {
1351             case ALIGN_NORMAL:
1352                 resultAlign =
1353                         dir == DIR_RIGHT_TO_LEFT ? Alignment.ALIGN_RIGHT : Alignment.ALIGN_LEFT;
1354                 break;
1355             case ALIGN_OPPOSITE:
1356                 resultAlign =
1357                         dir == DIR_RIGHT_TO_LEFT ? Alignment.ALIGN_LEFT : Alignment.ALIGN_RIGHT;
1358                 break;
1359             case ALIGN_CENTER:
1360                 resultAlign = Alignment.ALIGN_CENTER;
1361                 break;
1362             case ALIGN_RIGHT:
1363                 resultAlign = Alignment.ALIGN_RIGHT;
1364                 break;
1365             default: /* align == Alignment.ALIGN_LEFT */
1366                 resultAlign = Alignment.ALIGN_LEFT;
1367         }
1368
1369         switch (resultAlign) {
1370             case ALIGN_CENTER:
1371                 final int right = getParagraphRight(line);
1372                 final float max = getLineMax(line);
1373                 // This computation only works when mWidth equals leadingMargin plus width of the
1374                 // text in this line. If this condition doesn't meet anymore, please change here.
1375                 return (float) Math.ceil(right - (mWidth - max) / 2);
1376             case ALIGN_RIGHT:
1377                 return mWidth;
1378             default: /* resultAlign == Alignment.ALIGN_LEFT */
1379                 return getLineMax(line);
1380         }
1381     }
1382
1383     /**
1384      * Gets the unsigned horizontal extent of the specified line, including
1385      * leading margin indent, but excluding trailing whitespace.
1386      */
1387     public float getLineMax(int line) {
1388         float margin = getParagraphLeadingMargin(line);
1389         float signedExtent = getLineExtent(line, false);
1390         return margin + (signedExtent >= 0 ? signedExtent : -signedExtent);
1391     }
1392
1393     /**
1394      * Gets the unsigned horizontal extent of the specified line, including
1395      * leading margin indent and trailing whitespace.
1396      */
1397     public float getLineWidth(int line) {
1398         float margin = getParagraphLeadingMargin(line);
1399         float signedExtent = getLineExtent(line, true);
1400         return margin + (signedExtent >= 0 ? signedExtent : -signedExtent);
1401     }
1402
1403     /**
1404      * Like {@link #getLineExtent(int,TabStops,boolean)} but determines the
1405      * tab stops instead of using the ones passed in.
1406      * @param line the index of the line
1407      * @param full whether to include trailing whitespace
1408      * @return the extent of the line
1409      */
1410     private float getLineExtent(int line, boolean full) {
1411         final int start = getLineStart(line);
1412         final int end = full ? getLineEnd(line) : getLineVisibleEnd(line);
1413
1414         final boolean hasTabs = getLineContainsTab(line);
1415         TabStops tabStops = null;
1416         if (hasTabs && mText instanceof Spanned) {
1417             // Just checking this line should be good enough, tabs should be
1418             // consistent across all lines in a paragraph.
1419             TabStopSpan[] tabs = getParagraphSpans((Spanned) mText, start, end, TabStopSpan.class);
1420             if (tabs.length > 0) {
1421                 tabStops = new TabStops(TAB_INCREMENT, tabs); // XXX should reuse
1422             }
1423         }
1424         final Directions directions = getLineDirections(line);
1425         // Returned directions can actually be null
1426         if (directions == null) {
1427             return 0f;
1428         }
1429         final int dir = getParagraphDirection(line);
1430
1431         final TextLine tl = TextLine.obtain();
1432         final TextPaint paint = mWorkPaint;
1433         paint.set(mPaint);
1434         paint.setStartHyphenEdit(getStartHyphenEdit(line));
1435         paint.setEndHyphenEdit(getEndHyphenEdit(line));
1436         tl.set(paint, mText, start, end, dir, directions, hasTabs, tabStops,
1437                 getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line));
1438         if (isJustificationRequired(line)) {
1439             tl.justify(getJustifyWidth(line));
1440         }
1441         final float width = tl.metrics(null);
1442         TextLine.recycle(tl);
1443         return width;
1444     }
1445
1446     /**
1447      * Returns the signed horizontal extent of the specified line, excluding
1448      * leading margin.  If full is false, excludes trailing whitespace.
1449      * @param line the index of the line
1450      * @param tabStops the tab stops, can be null if we know they're not used.
1451      * @param full whether to include trailing whitespace
1452      * @return the extent of the text on this line
1453      */
1454     private float getLineExtent(int line, TabStops tabStops, boolean full) {
1455         final int start = getLineStart(line);
1456         final int end = full ? getLineEnd(line) : getLineVisibleEnd(line);
1457         final boolean hasTabs = getLineContainsTab(line);
1458         final Directions directions = getLineDirections(line);
1459         final int dir = getParagraphDirection(line);
1460
1461         final TextLine tl = TextLine.obtain();
1462         final TextPaint paint = mWorkPaint;
1463         paint.set(mPaint);
1464         paint.setStartHyphenEdit(getStartHyphenEdit(line));
1465         paint.setEndHyphenEdit(getEndHyphenEdit(line));
1466         tl.set(paint, mText, start, end, dir, directions, hasTabs, tabStops,
1467                 getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line));
1468         if (isJustificationRequired(line)) {
1469             tl.justify(getJustifyWidth(line));
1470         }
1471         final float width = tl.metrics(null);
1472         TextLine.recycle(tl);
1473         return width;
1474     }
1475
1476     /**
1477      * Get the line number corresponding to the specified vertical position.
1478      * If you ask for a position above 0, you get 0; if you ask for a position
1479      * below the bottom of the text, you get the last line.
1480      */
1481     // FIXME: It may be faster to do a linear search for layouts without many lines.
1482     public int getLineForVertical(int vertical) {
1483         int high = getLineCount(), low = -1, guess;
1484
1485         while (high - low > 1) {
1486             guess = (high + low) / 2;
1487
1488             if (getLineTop(guess) > vertical)
1489                 high = guess;
1490             else
1491                 low = guess;
1492         }
1493
1494         if (low < 0)
1495             return 0;
1496         else
1497             return low;
1498     }
1499
1500     /**
1501      * Get the line number on which the specified text offset appears.
1502      * If you ask for a position before 0, you get 0; if you ask for a position
1503      * beyond the end of the text, you get the last line.
1504      */
1505     public int getLineForOffset(int offset) {
1506         int high = getLineCount(), low = -1, guess;
1507
1508         while (high - low > 1) {
1509             guess = (high + low) / 2;
1510
1511             if (getLineStart(guess) > offset)
1512                 high = guess;
1513             else
1514                 low = guess;
1515         }
1516
1517         if (low < 0) {
1518             return 0;
1519         } else {
1520             return low;
1521         }
1522     }
1523
1524     /**
1525      * Get the character offset on the specified line whose position is
1526      * closest to the specified horizontal position.
1527      */
1528     public int getOffsetForHorizontal(int line, float horiz) {
1529         return getOffsetForHorizontal(line, horiz, true);
1530     }
1531
1532     /**
1533      * Get the character offset on the specified line whose position is
1534      * closest to the specified horizontal position.
1535      *
1536      * @param line the line used to find the closest offset
1537      * @param horiz the horizontal position used to find the closest offset
1538      * @param primary whether to use the primary position or secondary position to find the offset
1539      *
1540      * @hide
1541      */
1542     public int getOffsetForHorizontal(int line, float horiz, boolean primary) {
1543         // TODO: use Paint.getOffsetForAdvance to avoid binary search
1544         final int lineEndOffset = getLineEnd(line);
1545         final int lineStartOffset = getLineStart(line);
1546
1547         Directions dirs = getLineDirections(line);
1548
1549         TextLine tl = TextLine.obtain();
1550         // XXX: we don't care about tabs as we just use TextLine#getOffsetToLeftRightOf here.
1551         tl.set(mPaint, mText, lineStartOffset, lineEndOffset, getParagraphDirection(line), dirs,
1552                 false, null,
1553                 getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line));
1554         final HorizontalMeasurementProvider horizontal =
1555                 new HorizontalMeasurementProvider(line, primary);
1556
1557         final int max;
1558         if (line == getLineCount() - 1) {
1559             max = lineEndOffset;
1560         } else {
1561             max = tl.getOffsetToLeftRightOf(lineEndOffset - lineStartOffset,
1562                     !isRtlCharAt(lineEndOffset - 1)) + lineStartOffset;
1563         }
1564         int best = lineStartOffset;
1565         float bestdist = Math.abs(horizontal.get(lineStartOffset) - horiz);
1566
1567         for (int i = 0; i < dirs.mDirections.length; i += 2) {
1568             int here = lineStartOffset + dirs.mDirections[i];
1569             int there = here + (dirs.mDirections[i+1] & RUN_LENGTH_MASK);
1570             boolean isRtl = (dirs.mDirections[i+1] & RUN_RTL_FLAG) != 0;
1571             int swap = isRtl ? -1 : 1;
1572
1573             if (there > max)
1574                 there = max;
1575             int high = there - 1 + 1, low = here + 1 - 1, guess;
1576
1577             while (high - low > 1) {
1578                 guess = (high + low) / 2;
1579                 int adguess = getOffsetAtStartOf(guess);
1580
1581                 if (horizontal.get(adguess) * swap >= horiz * swap) {
1582                     high = guess;
1583                 } else {
1584                     low = guess;
1585                 }
1586             }
1587
1588             if (low < here + 1)
1589                 low = here + 1;
1590
1591             if (low < there) {
1592                 int aft = tl.getOffsetToLeftRightOf(low - lineStartOffset, isRtl) + lineStartOffset;
1593                 low = tl.getOffsetToLeftRightOf(aft - lineStartOffset, !isRtl) + lineStartOffset;
1594                 if (low >= here && low < there) {
1595                     float dist = Math.abs(horizontal.get(low) - horiz);
1596                     if (aft < there) {
1597                         float other = Math.abs(horizontal.get(aft) - horiz);
1598
1599                         if (other < dist) {
1600                             dist = other;
1601                             low = aft;
1602                         }
1603                     }
1604
1605                     if (dist < bestdist) {
1606                         bestdist = dist;
1607                         best = low;
1608                     }
1609                 }
1610             }
1611
1612             float dist = Math.abs(horizontal.get(here) - horiz);
1613
1614             if (dist < bestdist) {
1615                 bestdist = dist;
1616                 best = here;
1617             }
1618         }
1619
1620         float dist = Math.abs(horizontal.get(max) - horiz);
1621
1622         if (dist <= bestdist) {
1623             best = max;
1624         }
1625
1626         TextLine.recycle(tl);
1627         return best;
1628     }
1629
1630     /**
1631      * Responds to #getHorizontal queries, by selecting the better strategy between:
1632      * - calling #getHorizontal explicitly for each query
1633      * - precomputing all #getHorizontal measurements, and responding to any query in constant time
1634      * The first strategy is used for LTR-only text, while the second is used for all other cases.
1635      * The class is currently only used in #getOffsetForHorizontal, so reuse with care in other
1636      * contexts.
1637      */
1638     private class HorizontalMeasurementProvider {
1639         private final int mLine;
1640         private final boolean mPrimary;
1641
1642         private float[] mHorizontals;
1643         private int mLineStartOffset;
1644
1645         HorizontalMeasurementProvider(final int line, final boolean primary) {
1646             mLine = line;
1647             mPrimary = primary;
1648             init();
1649         }
1650
1651         private void init() {
1652             final Directions dirs = getLineDirections(mLine);
1653             if (dirs == DIRS_ALL_LEFT_TO_RIGHT) {
1654                 return;
1655             }
1656
1657             mHorizontals = getLineHorizontals(mLine, false, mPrimary);
1658             mLineStartOffset = getLineStart(mLine);
1659         }
1660
1661         float get(final int offset) {
1662             final int index = offset - mLineStartOffset;
1663             if (mHorizontals == null || index < 0 || index >= mHorizontals.length) {
1664                 return getHorizontal(offset, mPrimary);
1665             } else {
1666                 return mHorizontals[index];
1667             }
1668         }
1669     }
1670
1671     /**
1672      * Return the text offset after the last character on the specified line.
1673      */
1674     public final int getLineEnd(int line) {
1675         return getLineStart(line + 1);
1676     }
1677
1678     /**
1679      * Return the text offset after the last visible character (so whitespace
1680      * is not counted) on the specified line.
1681      */
1682     public int getLineVisibleEnd(int line) {
1683         return getLineVisibleEnd(line, getLineStart(line), getLineStart(line+1));
1684     }
1685
1686     private int getLineVisibleEnd(int line, int start, int end) {
1687         CharSequence text = mText;
1688         char ch;
1689         if (line == getLineCount() - 1) {
1690             return end;
1691         }
1692
1693         for (; end > start; end--) {
1694             ch = text.charAt(end - 1);
1695
1696             if (ch == '\n') {
1697                 return end - 1;
1698             }
1699
1700             if (!TextLine.isLineEndSpace(ch)) {
1701                 break;
1702             }
1703
1704         }
1705
1706         return end;
1707     }
1708
1709     /**
1710      * Return the vertical position of the bottom of the specified line.
1711      */
1712     public final int getLineBottom(int line) {
1713         return getLineTop(line + 1);
1714     }
1715
1716     /**
1717      * Return the vertical position of the bottom of the specified line without the line spacing
1718      * added.
1719      *
1720      * @hide
1721      */
1722     public final int getLineBottomWithoutSpacing(int line) {
1723         return getLineTop(line + 1) - getLineExtra(line);
1724     }
1725
1726     /**
1727      * Return the vertical position of the baseline of the specified line.
1728      */
1729     public final int getLineBaseline(int line) {
1730         // getLineTop(line+1) == getLineBottom(line)
1731         return getLineTop(line+1) - getLineDescent(line);
1732     }
1733
1734     /**
1735      * Get the ascent of the text on the specified line.
1736      * The return value is negative to match the Paint.ascent() convention.
1737      */
1738     public final int getLineAscent(int line) {
1739         // getLineTop(line+1) - getLineDescent(line) == getLineBaseLine(line)
1740         return getLineTop(line) - (getLineTop(line+1) - getLineDescent(line));
1741     }
1742
1743     /**
1744      * Return the extra space added as a result of line spacing attributes
1745      * {@link #getSpacingAdd()} and {@link #getSpacingMultiplier()}. Default value is {@code zero}.
1746      *
1747      * @param line the index of the line, the value should be equal or greater than {@code zero}
1748      * @hide
1749      */
1750     public int getLineExtra(@IntRange(from = 0) int line) {
1751         return 0;
1752     }
1753
1754     public int getOffsetToLeftOf(int offset) {
1755         return getOffsetToLeftRightOf(offset, true);
1756     }
1757
1758     public int getOffsetToRightOf(int offset) {
1759         return getOffsetToLeftRightOf(offset, false);
1760     }
1761
1762     private int getOffsetToLeftRightOf(int caret, boolean toLeft) {
1763         int line = getLineForOffset(caret);
1764         int lineStart = getLineStart(line);
1765         int lineEnd = getLineEnd(line);
1766         int lineDir = getParagraphDirection(line);
1767
1768         boolean lineChanged = false;
1769         boolean advance = toLeft == (lineDir == DIR_RIGHT_TO_LEFT);
1770         // if walking off line, look at the line we're headed to
1771         if (advance) {
1772             if (caret == lineEnd) {
1773                 if (line < getLineCount() - 1) {
1774                     lineChanged = true;
1775                     ++line;
1776                 } else {
1777                     return caret; // at very end, don't move
1778                 }
1779             }
1780         } else {
1781             if (caret == lineStart) {
1782                 if (line > 0) {
1783                     lineChanged = true;
1784                     --line;
1785                 } else {
1786                     return caret; // at very start, don't move
1787                 }
1788             }
1789         }
1790
1791         if (lineChanged) {
1792             lineStart = getLineStart(line);
1793             lineEnd = getLineEnd(line);
1794             int newDir = getParagraphDirection(line);
1795             if (newDir != lineDir) {
1796                 // unusual case.  we want to walk onto the line, but it runs
1797                 // in a different direction than this one, so we fake movement
1798                 // in the opposite direction.
1799                 toLeft = !toLeft;
1800                 lineDir = newDir;
1801             }
1802         }
1803
1804         Directions directions = getLineDirections(line);
1805
1806         TextLine tl = TextLine.obtain();
1807         // XXX: we don't care about tabs
1808         tl.set(mPaint, mText, lineStart, lineEnd, lineDir, directions, false, null,
1809                 getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line));
1810         caret = lineStart + tl.getOffsetToLeftRightOf(caret - lineStart, toLeft);
1811         TextLine.recycle(tl);
1812         return caret;
1813     }
1814
1815     private int getOffsetAtStartOf(int offset) {
1816         // XXX this probably should skip local reorderings and
1817         // zero-width characters, look at callers
1818         if (offset == 0)
1819             return 0;
1820
1821         CharSequence text = mText;
1822         char c = text.charAt(offset);
1823
1824         if (c >= '\uDC00' && c <= '\uDFFF') {
1825             char c1 = text.charAt(offset - 1);
1826
1827             if (c1 >= '\uD800' && c1 <= '\uDBFF')
1828                 offset -= 1;
1829         }
1830
1831         if (mSpannedText) {
1832             ReplacementSpan[] spans = ((Spanned) text).getSpans(offset, offset,
1833                                                        ReplacementSpan.class);
1834
1835             for (int i = 0; i < spans.length; i++) {
1836                 int start = ((Spanned) text).getSpanStart(spans[i]);
1837                 int end = ((Spanned) text).getSpanEnd(spans[i]);
1838
1839                 if (start < offset && end > offset)
1840                     offset = start;
1841             }
1842         }
1843
1844         return offset;
1845     }
1846
1847     /**
1848      * Determine whether we should clamp cursor position. Currently it's
1849      * only robust for left-aligned displays.
1850      * @hide
1851      */
1852     @UnsupportedAppUsage
1853     public boolean shouldClampCursor(int line) {
1854         // Only clamp cursor position in left-aligned displays.
1855         switch (getParagraphAlignment(line)) {
1856             case ALIGN_LEFT:
1857                 return true;
1858             case ALIGN_NORMAL:
1859                 return getParagraphDirection(line) > 0;
1860             default:
1861                 return false;
1862         }
1863
1864     }
1865
1866     /**
1867      * Fills in the specified Path with a representation of a cursor
1868      * at the specified offset.  This will often be a vertical line
1869      * but can be multiple discontinuous lines in text with multiple
1870      * directionalities.
1871      */
1872     public void getCursorPath(final int point, final Path dest, final CharSequence editingBuffer) {
1873         dest.reset();
1874
1875         int line = getLineForOffset(point);
1876         int top = getLineTop(line);
1877         int bottom = getLineBottomWithoutSpacing(line);
1878
1879         boolean clamped = shouldClampCursor(line);
1880         float h1 = getPrimaryHorizontal(point, clamped) - 0.5f;
1881
1882         int caps = TextKeyListener.getMetaState(editingBuffer, TextKeyListener.META_SHIFT_ON) |
1883                    TextKeyListener.getMetaState(editingBuffer, TextKeyListener.META_SELECTING);
1884         int fn = TextKeyListener.getMetaState(editingBuffer, TextKeyListener.META_ALT_ON);
1885         int dist = 0;
1886
1887         if (caps != 0 || fn != 0) {
1888             dist = (bottom - top) >> 2;
1889
1890             if (fn != 0)
1891                 top += dist;
1892             if (caps != 0)
1893                 bottom -= dist;
1894         }
1895
1896         if (h1 < 0.5f)
1897             h1 = 0.5f;
1898
1899         dest.moveTo(h1, top);
1900         dest.lineTo(h1, bottom);
1901
1902         if (caps == 2) {
1903             dest.moveTo(h1, bottom);
1904             dest.lineTo(h1 - dist, bottom + dist);
1905             dest.lineTo(h1, bottom);
1906             dest.lineTo(h1 + dist, bottom + dist);
1907         } else if (caps == 1) {
1908             dest.moveTo(h1, bottom);
1909             dest.lineTo(h1 - dist, bottom + dist);
1910
1911             dest.moveTo(h1 - dist, bottom + dist - 0.5f);
1912             dest.lineTo(h1 + dist, bottom + dist - 0.5f);
1913
1914             dest.moveTo(h1 + dist, bottom + dist);
1915             dest.lineTo(h1, bottom);
1916         }
1917
1918         if (fn == 2) {
1919             dest.moveTo(h1, top);
1920             dest.lineTo(h1 - dist, top - dist);
1921             dest.lineTo(h1, top);
1922             dest.lineTo(h1 + dist, top - dist);
1923         } else if (fn == 1) {
1924             dest.moveTo(h1, top);
1925             dest.lineTo(h1 - dist, top - dist);
1926
1927             dest.moveTo(h1 - dist, top - dist + 0.5f);
1928             dest.lineTo(h1 + dist, top - dist + 0.5f);
1929
1930             dest.moveTo(h1 + dist, top - dist);
1931             dest.lineTo(h1, top);
1932         }
1933     }
1934
1935     private void addSelection(int line, int start, int end,
1936             int top, int bottom, SelectionRectangleConsumer consumer) {
1937         int linestart = getLineStart(line);
1938         int lineend = getLineEnd(line);
1939         Directions dirs = getLineDirections(line);
1940
1941         if (lineend > linestart && mText.charAt(lineend - 1) == '\n') {
1942             lineend--;
1943         }
1944
1945         for (int i = 0; i < dirs.mDirections.length; i += 2) {
1946             int here = linestart + dirs.mDirections[i];
1947             int there = here + (dirs.mDirections[i + 1] & RUN_LENGTH_MASK);
1948
1949             if (there > lineend) {
1950                 there = lineend;
1951             }
1952
1953             if (start <= there && end >= here) {
1954                 int st = Math.max(start, here);
1955                 int en = Math.min(end, there);
1956
1957                 if (st != en) {
1958                     float h1 = getHorizontal(st, false, line, false /* not clamped */);
1959                     float h2 = getHorizontal(en, true, line, false /* not clamped */);
1960
1961                     float left = Math.min(h1, h2);
1962                     float right = Math.max(h1, h2);
1963
1964                     final @TextSelectionLayout int layout =
1965                             ((dirs.mDirections[i + 1] & RUN_RTL_FLAG) != 0)
1966                                     ? TEXT_SELECTION_LAYOUT_RIGHT_TO_LEFT
1967                                     : TEXT_SELECTION_LAYOUT_LEFT_TO_RIGHT;
1968
1969                     consumer.accept(left, top, right, bottom, layout);
1970                 }
1971             }
1972         }
1973     }
1974
1975     /**
1976      * Fills in the specified Path with a representation of a highlight
1977      * between the specified offsets.  This will often be a rectangle
1978      * or a potentially discontinuous set of rectangles.  If the start
1979      * and end are the same, the returned path is empty.
1980      */
1981     public void getSelectionPath(int start, int end, Path dest) {
1982         dest.reset();
1983         getSelection(start, end, (left, top, right, bottom, textSelectionLayout) ->
1984                 dest.addRect(left, top, right, bottom, Path.Direction.CW));
1985     }
1986
1987     /**
1988      * Calculates the rectangles which should be highlighted to indicate a selection between start
1989      * and end and feeds them into the given {@link SelectionRectangleConsumer}.
1990      *
1991      * @param start    the starting index of the selection
1992      * @param end      the ending index of the selection
1993      * @param consumer the {@link SelectionRectangleConsumer} which will receive the generated
1994      *                 rectangles. It will be called every time a rectangle is generated.
1995      * @hide
1996      * @see #getSelectionPath(int, int, Path)
1997      */
1998     public final void getSelection(int start, int end, final SelectionRectangleConsumer consumer) {
1999         if (start == end) {
2000             return;
2001         }
2002
2003         if (end < start) {
2004             int temp = end;
2005             end = start;
2006             start = temp;
2007         }
2008
2009         final int startline = getLineForOffset(start);
2010         final int endline = getLineForOffset(end);
2011
2012         int top = getLineTop(startline);
2013         int bottom = getLineBottomWithoutSpacing(endline);
2014
2015         if (startline == endline) {
2016             addSelection(startline, start, end, top, bottom, consumer);
2017         } else {
2018             final float width = mWidth;
2019
2020             addSelection(startline, start, getLineEnd(startline),
2021                     top, getLineBottom(startline), consumer);
2022
2023             if (getParagraphDirection(startline) == DIR_RIGHT_TO_LEFT) {
2024                 consumer.accept(getLineLeft(startline), top, 0, getLineBottom(startline),
2025                         TEXT_SELECTION_LAYOUT_RIGHT_TO_LEFT);
2026             } else {
2027                 consumer.accept(getLineRight(startline), top, width, getLineBottom(startline),
2028                         TEXT_SELECTION_LAYOUT_LEFT_TO_RIGHT);
2029             }
2030
2031             for (int i = startline + 1; i < endline; i++) {
2032                 top = getLineTop(i);
2033                 bottom = getLineBottom(i);
2034                 if (getParagraphDirection(i) == DIR_RIGHT_TO_LEFT) {
2035                     consumer.accept(0, top, width, bottom, TEXT_SELECTION_LAYOUT_RIGHT_TO_LEFT);
2036                 } else {
2037                     consumer.accept(0, top, width, bottom, TEXT_SELECTION_LAYOUT_LEFT_TO_RIGHT);
2038                 }
2039             }
2040
2041             top = getLineTop(endline);
2042             bottom = getLineBottomWithoutSpacing(endline);
2043
2044             addSelection(endline, getLineStart(endline), end, top, bottom, consumer);
2045
2046             if (getParagraphDirection(endline) == DIR_RIGHT_TO_LEFT) {
2047                 consumer.accept(width, top, getLineRight(endline), bottom,
2048                         TEXT_SELECTION_LAYOUT_RIGHT_TO_LEFT);
2049             } else {
2050                 consumer.accept(0, top, getLineLeft(endline), bottom,
2051                         TEXT_SELECTION_LAYOUT_LEFT_TO_RIGHT);
2052             }
2053         }
2054     }
2055
2056     /**
2057      * Get the alignment of the specified paragraph, taking into account
2058      * markup attached to it.
2059      */
2060     public final Alignment getParagraphAlignment(int line) {
2061         Alignment align = mAlignment;
2062
2063         if (mSpannedText) {
2064             Spanned sp = (Spanned) mText;
2065             AlignmentSpan[] spans = getParagraphSpans(sp, getLineStart(line),
2066                                                 getLineEnd(line),
2067                                                 AlignmentSpan.class);
2068
2069             int spanLength = spans.length;
2070             if (spanLength > 0) {
2071                 align = spans[spanLength-1].getAlignment();
2072             }
2073         }
2074
2075         return align;
2076     }
2077
2078     /**
2079      * Get the left edge of the specified paragraph, inset by left margins.
2080      */
2081     public final int getParagraphLeft(int line) {
2082         int left = 0;
2083         int dir = getParagraphDirection(line);
2084         if (dir == DIR_RIGHT_TO_LEFT || !mSpannedText) {
2085             return left; // leading margin has no impact, or no styles
2086         }
2087         return getParagraphLeadingMargin(line);
2088     }
2089
2090     /**
2091      * Get the right edge of the specified paragraph, inset by right margins.
2092      */
2093     public final int getParagraphRight(int line) {
2094         int right = mWidth;
2095         int dir = getParagraphDirection(line);
2096         if (dir == DIR_LEFT_TO_RIGHT || !mSpannedText) {
2097             return right; // leading margin has no impact, or no styles
2098         }
2099         return right - getParagraphLeadingMargin(line);
2100     }
2101
2102     /**
2103      * Returns the effective leading margin (unsigned) for this line,
2104      * taking into account LeadingMarginSpan and LeadingMarginSpan2.
2105      * @param line the line index
2106      * @return the leading margin of this line
2107      */
2108     private int getParagraphLeadingMargin(int line) {
2109         if (!mSpannedText) {
2110             return 0;
2111         }
2112         Spanned spanned = (Spanned) mText;
2113
2114         int lineStart = getLineStart(line);
2115         int lineEnd = getLineEnd(line);
2116         int spanEnd = spanned.nextSpanTransition(lineStart, lineEnd,
2117                 LeadingMarginSpan.class);
2118         LeadingMarginSpan[] spans = getParagraphSpans(spanned, lineStart, spanEnd,
2119                                                 LeadingMarginSpan.class);
2120         if (spans.length == 0) {
2121             return 0; // no leading margin span;
2122         }
2123
2124         int margin = 0;
2125
2126         boolean useFirstLineMargin = lineStart == 0 || spanned.charAt(lineStart - 1) == '\n';
2127         for (int i = 0; i < spans.length; i++) {
2128             if (spans[i] instanceof LeadingMarginSpan2) {
2129                 int spStart = spanned.getSpanStart(spans[i]);
2130                 int spanLine = getLineForOffset(spStart);
2131                 int count = ((LeadingMarginSpan2) spans[i]).getLeadingMarginLineCount();
2132                 // if there is more than one LeadingMarginSpan2, use the count that is greatest
2133                 useFirstLineMargin |= line < spanLine + count;
2134             }
2135         }
2136         for (int i = 0; i < spans.length; i++) {
2137             LeadingMarginSpan span = spans[i];
2138             margin += span.getLeadingMargin(useFirstLineMargin);
2139         }
2140
2141         return margin;
2142     }
2143
2144     private static float measurePara(TextPaint paint, CharSequence text, int start, int end,
2145             TextDirectionHeuristic textDir) {
2146         MeasuredParagraph mt = null;
2147         TextLine tl = TextLine.obtain();
2148         try {
2149             mt = MeasuredParagraph.buildForBidi(text, start, end, textDir, mt);
2150             final char[] chars = mt.getChars();
2151             final int len = chars.length;
2152             final Directions directions = mt.getDirections(0, len);
2153             final int dir = mt.getParagraphDir();
2154             boolean hasTabs = false;
2155             TabStops tabStops = null;
2156             // leading margins should be taken into account when measuring a paragraph
2157             int margin = 0;
2158             if (text instanceof Spanned) {
2159                 Spanned spanned = (Spanned) text;
2160                 LeadingMarginSpan[] spans = getParagraphSpans(spanned, start, end,
2161                         LeadingMarginSpan.class);
2162                 for (LeadingMarginSpan lms : spans) {
2163                     margin += lms.getLeadingMargin(true);
2164                 }
2165             }
2166             for (int i = 0; i < len; ++i) {
2167                 if (chars[i] == '\t') {
2168                     hasTabs = true;
2169                     if (text instanceof Spanned) {
2170                         Spanned spanned = (Spanned) text;
2171                         int spanEnd = spanned.nextSpanTransition(start, end,
2172                                 TabStopSpan.class);
2173                         TabStopSpan[] spans = getParagraphSpans(spanned, start, spanEnd,
2174                                 TabStopSpan.class);
2175                         if (spans.length > 0) {
2176                             tabStops = new TabStops(TAB_INCREMENT, spans);
2177                         }
2178                     }
2179                     break;
2180                 }
2181             }
2182             tl.set(paint, text, start, end, dir, directions, hasTabs, tabStops,
2183                     0 /* ellipsisStart */, 0 /* ellipsisEnd */);
2184             return margin + Math.abs(tl.metrics(null));
2185         } finally {
2186             TextLine.recycle(tl);
2187             if (mt != null) {
2188                 mt.recycle();
2189             }
2190         }
2191     }
2192
2193     /**
2194      * @hide
2195      */
2196     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
2197     public static class TabStops {
2198         private float[] mStops;
2199         private int mNumStops;
2200         private float mIncrement;
2201
2202         public TabStops(float increment, Object[] spans) {
2203             reset(increment, spans);
2204         }
2205
2206         void reset(float increment, Object[] spans) {
2207             this.mIncrement = increment;
2208
2209             int ns = 0;
2210             if (spans != null) {
2211                 float[] stops = this.mStops;
2212                 for (Object o : spans) {
2213                     if (o instanceof TabStopSpan) {
2214                         if (stops == null) {
2215                             stops = new float[10];
2216                         } else if (ns == stops.length) {
2217                             float[] nstops = new float[ns * 2];
2218                             for (int i = 0; i < ns; ++i) {
2219                                 nstops[i] = stops[i];
2220                             }
2221                             stops = nstops;
2222                         }
2223                         stops[ns++] = ((TabStopSpan) o).getTabStop();
2224                     }
2225                 }
2226                 if (ns > 1) {
2227                     Arrays.sort(stops, 0, ns);
2228                 }
2229                 if (stops != this.mStops) {
2230                     this.mStops = stops;
2231                 }
2232             }
2233             this.mNumStops = ns;
2234         }
2235
2236         float nextTab(float h) {
2237             int ns = this.mNumStops;
2238             if (ns > 0) {
2239                 float[] stops = this.mStops;
2240                 for (int i = 0; i < ns; ++i) {
2241                     float stop = stops[i];
2242                     if (stop > h) {
2243                         return stop;
2244                     }
2245                 }
2246             }
2247             return nextDefaultStop(h, mIncrement);
2248         }
2249
2250         /**
2251          * Returns the position of next tab stop.
2252          */
2253         public static float nextDefaultStop(float h, float inc) {
2254             return ((int) ((h + inc) / inc)) * inc;
2255         }
2256     }
2257
2258     /**
2259      * Returns the position of the next tab stop after h on the line.
2260      *
2261      * @param text the text
2262      * @param start start of the line
2263      * @param end limit of the line
2264      * @param h the current horizontal offset
2265      * @param tabs the tabs, can be null.  If it is null, any tabs in effect
2266      * on the line will be used.  If there are no tabs, a default offset
2267      * will be used to compute the tab stop.
2268      * @return the offset of the next tab stop.
2269      */
2270     /* package */ static float nextTab(CharSequence text, int start, int end,
2271                                        float h, Object[] tabs) {
2272         float nh = Float.MAX_VALUE;
2273         boolean alltabs = false;
2274
2275         if (text instanceof Spanned) {
2276             if (tabs == null) {
2277                 tabs = getParagraphSpans((Spanned) text, start, end, TabStopSpan.class);
2278                 alltabs = true;
2279             }
2280
2281             for (int i = 0; i < tabs.length; i++) {
2282                 if (!alltabs) {
2283                     if (!(tabs[i] instanceof TabStopSpan))
2284                         continue;
2285                 }
2286
2287                 int where = ((TabStopSpan) tabs[i]).getTabStop();
2288
2289                 if (where < nh && where > h)
2290                     nh = where;
2291             }
2292
2293             if (nh != Float.MAX_VALUE)
2294                 return nh;
2295         }
2296
2297         return ((int) ((h + TAB_INCREMENT) / TAB_INCREMENT)) * TAB_INCREMENT;
2298     }
2299
2300     protected final boolean isSpanned() {
2301         return mSpannedText;
2302     }
2303
2304     /**
2305      * Returns the same as <code>text.getSpans()</code>, except where
2306      * <code>start</code> and <code>end</code> are the same and are not
2307      * at the very beginning of the text, in which case an empty array
2308      * is returned instead.
2309      * <p>
2310      * This is needed because of the special case that <code>getSpans()</code>
2311      * on an empty range returns the spans adjacent to that range, which is
2312      * primarily for the sake of <code>TextWatchers</code> so they will get
2313      * notifications when text goes from empty to non-empty.  But it also
2314      * has the unfortunate side effect that if the text ends with an empty
2315      * paragraph, that paragraph accidentally picks up the styles of the
2316      * preceding paragraph (even though those styles will not be picked up
2317      * by new text that is inserted into the empty paragraph).
2318      * <p>
2319      * The reason it just checks whether <code>start</code> and <code>end</code>
2320      * is the same is that the only time a line can contain 0 characters
2321      * is if it is the final paragraph of the Layout; otherwise any line will
2322      * contain at least one printing or newline character.  The reason for the
2323      * additional check if <code>start</code> is greater than 0 is that
2324      * if the empty paragraph is the entire content of the buffer, paragraph
2325      * styles that are already applied to the buffer will apply to text that
2326      * is inserted into it.
2327      */
2328     /* package */static <T> T[] getParagraphSpans(Spanned text, int start, int end, Class<T> type) {
2329         if (start == end && start > 0) {
2330             return ArrayUtils.emptyArray(type);
2331         }
2332
2333         if(text instanceof SpannableStringBuilder) {
2334             return ((SpannableStringBuilder) text).getSpans(start, end, type, false);
2335         } else {
2336             return text.getSpans(start, end, type);
2337         }
2338     }
2339
2340     private void ellipsize(int start, int end, int line,
2341                            char[] dest, int destoff, TextUtils.TruncateAt method) {
2342         final int ellipsisCount = getEllipsisCount(line);
2343         if (ellipsisCount == 0) {
2344             return;
2345         }
2346         final int ellipsisStart = getEllipsisStart(line);
2347         final int lineStart = getLineStart(line);
2348
2349         final String ellipsisString = TextUtils.getEllipsisString(method);
2350         final int ellipsisStringLen = ellipsisString.length();
2351         // Use the ellipsis string only if there are that at least as many characters to replace.
2352         final boolean useEllipsisString = ellipsisCount >= ellipsisStringLen;
2353         for (int i = 0; i < ellipsisCount; i++) {
2354             final char c;
2355             if (useEllipsisString && i < ellipsisStringLen) {
2356                 c = ellipsisString.charAt(i);
2357             } else {
2358                 c = TextUtils.ELLIPSIS_FILLER;
2359             }
2360
2361             final int a = i + ellipsisStart + lineStart;
2362             if (start <= a && a < end) {
2363                 dest[destoff + a - start] = c;
2364             }
2365         }
2366     }
2367
2368     /**
2369      * Stores information about bidirectional (left-to-right or right-to-left)
2370      * text within the layout of a line.
2371      */
2372     public static class Directions {
2373         /**
2374          * Directions represents directional runs within a line of text. Runs are pairs of ints
2375          * listed in visual order, starting from the leading margin.  The first int of each pair is
2376          * the offset from the first character of the line to the start of the run.  The second int
2377          * represents both the length and level of the run. The length is in the lower bits,
2378          * accessed by masking with RUN_LENGTH_MASK.  The level is in the higher bits, accessed by
2379          * shifting by RUN_LEVEL_SHIFT and masking by RUN_LEVEL_MASK. To simply test for an RTL
2380          * direction, test the bit using RUN_RTL_FLAG, if set then the direction is rtl.
2381          * @hide
2382          */
2383         @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
2384         public int[] mDirections;
2385
2386         /**
2387          * @hide
2388          */
2389         @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
2390         public Directions(int[] dirs) {
2391             mDirections = dirs;
2392         }
2393
2394         /**
2395          * Returns number of BiDi runs.
2396          *
2397          * @hide
2398          */
2399         public @IntRange(from = 0) int getRunCount() {
2400             return mDirections.length / 2;
2401         }
2402
2403         /**
2404          * Returns the start offset of the BiDi run.
2405          *
2406          * @param runIndex the index of the BiDi run
2407          * @return the start offset of the BiDi run.
2408          * @hide
2409          */
2410         public @IntRange(from = 0) int getRunStart(@IntRange(from = 0) int runIndex) {
2411             return mDirections[runIndex * 2];
2412         }
2413
2414         /**
2415          * Returns the length of the BiDi run.
2416          *
2417          * Note that this method may return too large number due to reducing the number of object
2418          * allocations. The too large number means the remaining part is assigned to this run. The
2419          * caller must clamp the returned value.
2420          *
2421          * @param runIndex the index of the BiDi run
2422          * @return the length of the BiDi run.
2423          * @hide
2424          */
2425         public @IntRange(from = 0) int getRunLength(@IntRange(from = 0) int runIndex) {
2426             return mDirections[runIndex * 2 + 1] & RUN_LENGTH_MASK;
2427         }
2428
2429         /**
2430          * Returns true if the BiDi run is RTL.
2431          *
2432          * @param runIndex the index of the BiDi run
2433          * @return true if the BiDi run is RTL.
2434          * @hide
2435          */
2436         public boolean isRunRtl(int runIndex) {
2437             return (mDirections[runIndex * 2 + 1] & RUN_RTL_FLAG) != 0;
2438         }
2439     }
2440
2441     /**
2442      * Return the offset of the first character to be ellipsized away,
2443      * relative to the start of the line.  (So 0 if the beginning of the
2444      * line is ellipsized, not getLineStart().)
2445      */
2446     public abstract int getEllipsisStart(int line);
2447
2448     /**
2449      * Returns the number of characters to be ellipsized away, or 0 if
2450      * no ellipsis is to take place.
2451      */
2452     public abstract int getEllipsisCount(int line);
2453
2454     /* package */ static class Ellipsizer implements CharSequence, GetChars {
2455         /* package */ CharSequence mText;
2456         /* package */ Layout mLayout;
2457         /* package */ int mWidth;
2458         /* package */ TextUtils.TruncateAt mMethod;
2459
2460         public Ellipsizer(CharSequence s) {
2461             mText = s;
2462         }
2463
2464         public char charAt(int off) {
2465             char[] buf = TextUtils.obtain(1);
2466             getChars(off, off + 1, buf, 0);
2467             char ret = buf[0];
2468
2469             TextUtils.recycle(buf);
2470             return ret;
2471         }
2472
2473         public void getChars(int start, int end, char[] dest, int destoff) {
2474             int line1 = mLayout.getLineForOffset(start);
2475             int line2 = mLayout.getLineForOffset(end);
2476
2477             TextUtils.getChars(mText, start, end, dest, destoff);
2478
2479             for (int i = line1; i <= line2; i++) {
2480                 mLayout.ellipsize(start, end, i, dest, destoff, mMethod);
2481             }
2482         }
2483
2484         public int length() {
2485             return mText.length();
2486         }
2487
2488         public CharSequence subSequence(int start, int end) {
2489             char[] s = new char[end - start];
2490             getChars(start, end, s, 0);
2491             return new String(s);
2492         }
2493
2494         @Override
2495         public String toString() {
2496             char[] s = new char[length()];
2497             getChars(0, length(), s, 0);
2498             return new String(s);
2499         }
2500
2501     }
2502
2503     /* package */ static class SpannedEllipsizer extends Ellipsizer implements Spanned {
2504         private Spanned mSpanned;
2505
2506         public SpannedEllipsizer(CharSequence display) {
2507             super(display);
2508             mSpanned = (Spanned) display;
2509         }
2510
2511         public <T> T[] getSpans(int start, int end, Class<T> type) {
2512             return mSpanned.getSpans(start, end, type);
2513         }
2514
2515         public int getSpanStart(Object tag) {
2516             return mSpanned.getSpanStart(tag);
2517         }
2518
2519         public int getSpanEnd(Object tag) {
2520             return mSpanned.getSpanEnd(tag);
2521         }
2522
2523         public int getSpanFlags(Object tag) {
2524             return mSpanned.getSpanFlags(tag);
2525         }
2526
2527         @SuppressWarnings("rawtypes")
2528         public int nextSpanTransition(int start, int limit, Class type) {
2529             return mSpanned.nextSpanTransition(start, limit, type);
2530         }
2531
2532         @Override
2533         public CharSequence subSequence(int start, int end) {
2534             char[] s = new char[end - start];
2535             getChars(start, end, s, 0);
2536
2537             SpannableString ss = new SpannableString(new String(s));
2538             TextUtils.copySpansFrom(mSpanned, start, end, Object.class, ss, 0);
2539             return ss;
2540         }
2541     }
2542
2543     private CharSequence mText;
2544     @UnsupportedAppUsage
2545     private TextPaint mPaint;
2546     private TextPaint mWorkPaint = new TextPaint();
2547     private int mWidth;
2548     private Alignment mAlignment = Alignment.ALIGN_NORMAL;
2549     private float mSpacingMult;
2550     private float mSpacingAdd;
2551     private static final Rect sTempRect = new Rect();
2552     private boolean mSpannedText;
2553     private TextDirectionHeuristic mTextDir;
2554     private SpanSet<LineBackgroundSpan> mLineBackgroundSpans;
2555     private int mJustificationMode;
2556
2557     /** @hide */
2558     @IntDef(prefix = { "DIR_" }, value = {
2559             DIR_LEFT_TO_RIGHT,
2560             DIR_RIGHT_TO_LEFT
2561     })
2562     @Retention(RetentionPolicy.SOURCE)
2563     public @interface Direction {}
2564
2565     public static final int DIR_LEFT_TO_RIGHT = 1;
2566     public static final int DIR_RIGHT_TO_LEFT = -1;
2567
2568     /* package */ static final int DIR_REQUEST_LTR = 1;
2569     /* package */ static final int DIR_REQUEST_RTL = -1;
2570     @UnsupportedAppUsage
2571     /* package */ static final int DIR_REQUEST_DEFAULT_LTR = 2;
2572     /* package */ static final int DIR_REQUEST_DEFAULT_RTL = -2;
2573
2574     /* package */ static final int RUN_LENGTH_MASK = 0x03ffffff;
2575     /* package */ static final int RUN_LEVEL_SHIFT = 26;
2576     /* package */ static final int RUN_LEVEL_MASK = 0x3f;
2577     /* package */ static final int RUN_RTL_FLAG = 1 << RUN_LEVEL_SHIFT;
2578
2579     public enum Alignment {
2580         ALIGN_NORMAL,
2581         ALIGN_OPPOSITE,
2582         ALIGN_CENTER,
2583         /** @hide */
2584         @UnsupportedAppUsage
2585         ALIGN_LEFT,
2586         /** @hide */
2587         @UnsupportedAppUsage
2588         ALIGN_RIGHT,
2589     }
2590
2591     private static final float TAB_INCREMENT = 20;
2592
2593     /** @hide */
2594     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
2595     @UnsupportedAppUsage
2596     public static final Directions DIRS_ALL_LEFT_TO_RIGHT =
2597         new Directions(new int[] { 0, RUN_LENGTH_MASK });
2598
2599     /** @hide */
2600     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
2601     @UnsupportedAppUsage
2602     public static final Directions DIRS_ALL_RIGHT_TO_LEFT =
2603         new Directions(new int[] { 0, RUN_LENGTH_MASK | RUN_RTL_FLAG });
2604
2605     /** @hide */
2606     @Retention(RetentionPolicy.SOURCE)
2607     @IntDef(prefix = { "TEXT_SELECTION_LAYOUT_" }, value = {
2608             TEXT_SELECTION_LAYOUT_RIGHT_TO_LEFT,
2609             TEXT_SELECTION_LAYOUT_LEFT_TO_RIGHT
2610     })
2611     public @interface TextSelectionLayout {}
2612
2613     /** @hide */
2614     public static final int TEXT_SELECTION_LAYOUT_RIGHT_TO_LEFT = 0;
2615     /** @hide */
2616     public static final int TEXT_SELECTION_LAYOUT_LEFT_TO_RIGHT = 1;
2617
2618     /** @hide */
2619     @FunctionalInterface
2620     public interface SelectionRectangleConsumer {
2621         /**
2622          * Performs this operation on the given rectangle.
2623          *
2624          * @param left   the left edge of the rectangle
2625          * @param top    the top edge of the rectangle
2626          * @param right  the right edge of the rectangle
2627          * @param bottom the bottom edge of the rectangle
2628          * @param textSelectionLayout the layout (RTL or LTR) of the text covered by this
2629          *                            selection rectangle
2630          */
2631         void accept(float left, float top, float right, float bottom,
2632                 @TextSelectionLayout int textSelectionLayout);
2633     }
2634
2635 }