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