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