OSDN Git Service

DO NOT MERGE. KEY_INTENT shouldn't grant permissions. am: ca7ffa06bc -s ours am...
[android-x86/frameworks-base.git] / core / java / android / text / Layout.java
1 /*
2  * Copyright (C) 2006 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package android.text;
18
19 import android.annotation.IntDef;
20 import android.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      */
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      */
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      */
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      */
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      */
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 */
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      */
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      */
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      */
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
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
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      */
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      */
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      */
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      */
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      */
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      */
719     public final TextPaint getPaint() {
720         return mPaint;
721     }
722
723     /**
724      * Return the width of this layout.
725      */
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      */
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      */
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      */
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      */
765     public int getHeight(boolean cap) {
766         return getHeight();
767     }
768
769     /**
770      * Return the base alignment of this layout.
771      */
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      */
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      */
786     public final float getSpacingAdd() {
787         return mSpacingAdd;
788     }
789
790     /**
791      * Return the heuristic used to determine paragraph text direction.
792      * @hide
793      */
794     public final TextDirectionHeuristic getTextDirectionHeuristic() {
795         return mTextDir;
796     }
797
798     /**
799      * Return the number of lines of text in this layout.
800      */
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      */
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      */
827     public abstract int getLineTop(int line);
828
829     /**
830      * Return the descent of the specified line(0&hellip;getLineCount() - 1).
831      */
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      */
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      */
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      */
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      */
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      */
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      */
873     public abstract int getBottomPadding();
874
875     /**
876      * Returns the hyphen edit for a line.
877      *
878      * @hide
879      */
880     public int getHyphen(int line) {
881         return 0;
882     }
883
884     /**
885      * Returns the left indent for a line.
886      *
887      * @hide
888      */
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      */
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      */
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      */
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     private boolean primaryIsTrailingPrevious(int offset) {
979         int line = getLineForOffset(offset);
980         int lineStart = getLineStart(line);
981         int lineEnd = getLineEnd(line);
982         int[] runs = getLineDirections(line).mDirections;
983
984         int levelAt = -1;
985         for (int i = 0; i < runs.length; i += 2) {
986             int start = lineStart + runs[i];
987             int limit = start + (runs[i+1] & RUN_LENGTH_MASK);
988             if (limit > lineEnd) {
989                 limit = lineEnd;
990             }
991             if (offset >= start && offset < limit) {
992                 if (offset > start) {
993                     // Previous character is at same level, so don't use trailing.
994                     return false;
995                 }
996                 levelAt = (runs[i+1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK;
997                 break;
998             }
999         }
1000         if (levelAt == -1) {
1001             // Offset was limit of line.
1002             levelAt = getParagraphDirection(line) == 1 ? 0 : 1;
1003         }
1004
1005         // At level boundary, check previous level.
1006         int levelBefore = -1;
1007         if (offset == lineStart) {
1008             levelBefore = getParagraphDirection(line) == 1 ? 0 : 1;
1009         } else {
1010             offset -= 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                     levelBefore = (runs[i+1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK;
1019                     break;
1020                 }
1021             }
1022         }
1023
1024         return levelBefore < levelAt;
1025     }
1026
1027     /**
1028      * Get the primary horizontal position for the specified text offset.
1029      * This is the location where a new character would be inserted in
1030      * the paragraph's primary direction.
1031      */
1032     public float getPrimaryHorizontal(int offset) {
1033         return getPrimaryHorizontal(offset, false /* not clamped */);
1034     }
1035
1036     /**
1037      * Get the primary horizontal position for the specified text offset, but
1038      * optionally clamp it so that it doesn't exceed the width of the layout.
1039      * @hide
1040      */
1041     public float getPrimaryHorizontal(int offset, boolean clamped) {
1042         boolean trailing = primaryIsTrailingPrevious(offset);
1043         return getHorizontal(offset, trailing, clamped);
1044     }
1045
1046     /**
1047      * Get the secondary horizontal position for the specified text offset.
1048      * This is the location where a new character would be inserted in
1049      * the direction other than the paragraph's primary direction.
1050      */
1051     public float getSecondaryHorizontal(int offset) {
1052         return getSecondaryHorizontal(offset, false /* not clamped */);
1053     }
1054
1055     /**
1056      * Get the secondary horizontal position for the specified text offset, but
1057      * optionally clamp it so that it doesn't exceed the width of the layout.
1058      * @hide
1059      */
1060     public float getSecondaryHorizontal(int offset, boolean clamped) {
1061         boolean trailing = primaryIsTrailingPrevious(offset);
1062         return getHorizontal(offset, !trailing, clamped);
1063     }
1064
1065     private float getHorizontal(int offset, boolean primary) {
1066         return primary ? getPrimaryHorizontal(offset) : getSecondaryHorizontal(offset);
1067     }
1068
1069     private float getHorizontal(int offset, boolean trailing, boolean clamped) {
1070         int line = getLineForOffset(offset);
1071
1072         return getHorizontal(offset, trailing, line, clamped);
1073     }
1074
1075     private float getHorizontal(int offset, boolean trailing, int line, boolean clamped) {
1076         int start = getLineStart(line);
1077         int end = getLineEnd(line);
1078         int dir = getParagraphDirection(line);
1079         boolean hasTab = getLineContainsTab(line);
1080         Directions directions = getLineDirections(line);
1081
1082         TabStops tabStops = null;
1083         if (hasTab && mText instanceof Spanned) {
1084             // Just checking this line should be good enough, tabs should be
1085             // consistent across all lines in a paragraph.
1086             TabStopSpan[] tabs = getParagraphSpans((Spanned) mText, start, end, TabStopSpan.class);
1087             if (tabs.length > 0) {
1088                 tabStops = new TabStops(TAB_INCREMENT, tabs); // XXX should reuse
1089             }
1090         }
1091
1092         TextLine tl = TextLine.obtain();
1093         tl.set(mPaint, mText, start, end, dir, directions, hasTab, tabStops);
1094         float wid = tl.measure(offset - start, trailing, null);
1095         TextLine.recycle(tl);
1096
1097         if (clamped && wid > mWidth) {
1098             wid = mWidth;
1099         }
1100         int left = getParagraphLeft(line);
1101         int right = getParagraphRight(line);
1102
1103         return getLineStartPos(line, left, right) + wid;
1104     }
1105
1106     /**
1107      * Get the leftmost position that should be exposed for horizontal
1108      * scrolling on the specified line.
1109      */
1110     public float getLineLeft(int line) {
1111         int dir = getParagraphDirection(line);
1112         Alignment align = getParagraphAlignment(line);
1113
1114         if (align == Alignment.ALIGN_LEFT) {
1115             return 0;
1116         } else if (align == Alignment.ALIGN_NORMAL) {
1117             if (dir == DIR_RIGHT_TO_LEFT)
1118                 return getParagraphRight(line) - getLineMax(line);
1119             else
1120                 return 0;
1121         } else if (align == Alignment.ALIGN_RIGHT) {
1122             return mWidth - getLineMax(line);
1123         } else if (align == Alignment.ALIGN_OPPOSITE) {
1124             if (dir == DIR_RIGHT_TO_LEFT)
1125                 return 0;
1126             else
1127                 return mWidth - getLineMax(line);
1128         } else { /* align == Alignment.ALIGN_CENTER */
1129             int left = getParagraphLeft(line);
1130             int right = getParagraphRight(line);
1131             int max = ((int) getLineMax(line)) & ~1;
1132
1133             return left + ((right - left) - max) / 2;
1134         }
1135     }
1136
1137     /**
1138      * Get the rightmost position that should be exposed for horizontal
1139      * scrolling on the specified line.
1140      */
1141     public float getLineRight(int line) {
1142         int dir = getParagraphDirection(line);
1143         Alignment align = getParagraphAlignment(line);
1144
1145         if (align == Alignment.ALIGN_LEFT) {
1146             return getParagraphLeft(line) + getLineMax(line);
1147         } else if (align == Alignment.ALIGN_NORMAL) {
1148             if (dir == DIR_RIGHT_TO_LEFT)
1149                 return mWidth;
1150             else
1151                 return getParagraphLeft(line) + getLineMax(line);
1152         } else if (align == Alignment.ALIGN_RIGHT) {
1153             return mWidth;
1154         } else if (align == Alignment.ALIGN_OPPOSITE) {
1155             if (dir == DIR_RIGHT_TO_LEFT)
1156                 return getLineMax(line);
1157             else
1158                 return mWidth;
1159         } else { /* align == Alignment.ALIGN_CENTER */
1160             int left = getParagraphLeft(line);
1161             int right = getParagraphRight(line);
1162             int max = ((int) getLineMax(line)) & ~1;
1163
1164             return right - ((right - left) - max) / 2;
1165         }
1166     }
1167
1168     /**
1169      * Gets the unsigned horizontal extent of the specified line, including
1170      * leading margin indent, but excluding trailing whitespace.
1171      */
1172     public float getLineMax(int line) {
1173         float margin = getParagraphLeadingMargin(line);
1174         float signedExtent = getLineExtent(line, false);
1175         return margin + (signedExtent >= 0 ? signedExtent : -signedExtent);
1176     }
1177
1178     /**
1179      * Gets the unsigned horizontal extent of the specified line, including
1180      * leading margin indent and trailing whitespace.
1181      */
1182     public float getLineWidth(int line) {
1183         float margin = getParagraphLeadingMargin(line);
1184         float signedExtent = getLineExtent(line, true);
1185         return margin + (signedExtent >= 0 ? signedExtent : -signedExtent);
1186     }
1187
1188     /**
1189      * Like {@link #getLineExtent(int,TabStops,boolean)} but determines the
1190      * tab stops instead of using the ones passed in.
1191      * @param line the index of the line
1192      * @param full whether to include trailing whitespace
1193      * @return the extent of the line
1194      */
1195     private float getLineExtent(int line, boolean full) {
1196         int start = getLineStart(line);
1197         int end = full ? getLineEnd(line) : getLineVisibleEnd(line);
1198
1199         boolean hasTabs = getLineContainsTab(line);
1200         TabStops tabStops = null;
1201         if (hasTabs && mText instanceof Spanned) {
1202             // Just checking this line should be good enough, tabs should be
1203             // consistent across all lines in a paragraph.
1204             TabStopSpan[] tabs = getParagraphSpans((Spanned) mText, start, end, TabStopSpan.class);
1205             if (tabs.length > 0) {
1206                 tabStops = new TabStops(TAB_INCREMENT, tabs); // XXX should reuse
1207             }
1208         }
1209         Directions directions = getLineDirections(line);
1210         // Returned directions can actually be null
1211         if (directions == null) {
1212             return 0f;
1213         }
1214         int dir = getParagraphDirection(line);
1215
1216         TextLine tl = TextLine.obtain();
1217         mPaint.setHyphenEdit(getHyphen(line));
1218         tl.set(mPaint, mText, start, end, dir, directions, hasTabs, tabStops);
1219         if (isJustificationRequired(line)) {
1220             tl.justify(getJustifyWidth(line));
1221         }
1222         float width = tl.metrics(null);
1223         mPaint.setHyphenEdit(0);
1224         TextLine.recycle(tl);
1225         return width;
1226     }
1227
1228     /**
1229      * Returns the signed horizontal extent of the specified line, excluding
1230      * leading margin.  If full is false, excludes trailing whitespace.
1231      * @param line the index of the line
1232      * @param tabStops the tab stops, can be null if we know they're not used.
1233      * @param full whether to include trailing whitespace
1234      * @return the extent of the text on this line
1235      */
1236     private float getLineExtent(int line, TabStops tabStops, boolean full) {
1237         int start = getLineStart(line);
1238         int end = full ? getLineEnd(line) : getLineVisibleEnd(line);
1239         boolean hasTabs = getLineContainsTab(line);
1240         Directions directions = getLineDirections(line);
1241         int dir = getParagraphDirection(line);
1242
1243         TextLine tl = TextLine.obtain();
1244         mPaint.setHyphenEdit(getHyphen(line));
1245         tl.set(mPaint, mText, start, end, dir, directions, hasTabs, tabStops);
1246         if (isJustificationRequired(line)) {
1247             tl.justify(getJustifyWidth(line));
1248         }
1249         float width = tl.metrics(null);
1250         mPaint.setHyphenEdit(0);
1251         TextLine.recycle(tl);
1252         return width;
1253     }
1254
1255     /**
1256      * Get the line number corresponding to the specified vertical position.
1257      * If you ask for a position above 0, you get 0; if you ask for a position
1258      * below the bottom of the text, you get the last line.
1259      */
1260     // FIXME: It may be faster to do a linear search for layouts without many lines.
1261     public int getLineForVertical(int vertical) {
1262         int high = getLineCount(), low = -1, guess;
1263
1264         while (high - low > 1) {
1265             guess = (high + low) / 2;
1266
1267             if (getLineTop(guess) > vertical)
1268                 high = guess;
1269             else
1270                 low = guess;
1271         }
1272
1273         if (low < 0)
1274             return 0;
1275         else
1276             return low;
1277     }
1278
1279     /**
1280      * Get the line number on which the specified text offset appears.
1281      * If you ask for a position before 0, you get 0; if you ask for a position
1282      * beyond the end of the text, you get the last line.
1283      */
1284     public int getLineForOffset(int offset) {
1285         int high = getLineCount(), low = -1, guess;
1286
1287         while (high - low > 1) {
1288             guess = (high + low) / 2;
1289
1290             if (getLineStart(guess) > offset)
1291                 high = guess;
1292             else
1293                 low = guess;
1294         }
1295
1296         if (low < 0) {
1297             return 0;
1298         } else {
1299             return low;
1300         }
1301     }
1302
1303     /**
1304      * Get the character offset on the specified line whose position is
1305      * closest to the specified horizontal position.
1306      */
1307     public int getOffsetForHorizontal(int line, float horiz) {
1308         return getOffsetForHorizontal(line, horiz, true);
1309     }
1310
1311     /**
1312      * Get the character offset on the specified line whose position is
1313      * closest to the specified horizontal position.
1314      *
1315      * @param line the line used to find the closest offset
1316      * @param horiz the horizontal position used to find the closest offset
1317      * @param primary whether to use the primary position or secondary position to find the offset
1318      *
1319      * @hide
1320      */
1321     public int getOffsetForHorizontal(int line, float horiz, boolean primary) {
1322         // TODO: use Paint.getOffsetForAdvance to avoid binary search
1323         final int lineEndOffset = getLineEnd(line);
1324         final int lineStartOffset = getLineStart(line);
1325
1326         Directions dirs = getLineDirections(line);
1327
1328         TextLine tl = TextLine.obtain();
1329         // XXX: we don't care about tabs as we just use TextLine#getOffsetToLeftRightOf here.
1330         tl.set(mPaint, mText, lineStartOffset, lineEndOffset, getParagraphDirection(line), dirs,
1331                 false, null);
1332
1333         final int max;
1334         if (line == getLineCount() - 1) {
1335             max = lineEndOffset;
1336         } else {
1337             max = tl.getOffsetToLeftRightOf(lineEndOffset - lineStartOffset,
1338                     !isRtlCharAt(lineEndOffset - 1)) + lineStartOffset;
1339         }
1340         int best = lineStartOffset;
1341         float bestdist = Math.abs(getHorizontal(best, primary) - horiz);
1342
1343         for (int i = 0; i < dirs.mDirections.length; i += 2) {
1344             int here = lineStartOffset + dirs.mDirections[i];
1345             int there = here + (dirs.mDirections[i+1] & RUN_LENGTH_MASK);
1346             boolean isRtl = (dirs.mDirections[i+1] & RUN_RTL_FLAG) != 0;
1347             int swap = isRtl ? -1 : 1;
1348
1349             if (there > max)
1350                 there = max;
1351             int high = there - 1 + 1, low = here + 1 - 1, guess;
1352
1353             while (high - low > 1) {
1354                 guess = (high + low) / 2;
1355                 int adguess = getOffsetAtStartOf(guess);
1356
1357                 if (getHorizontal(adguess, primary) * swap >= horiz * swap) {
1358                     high = guess;
1359                 } else {
1360                     low = guess;
1361                 }
1362             }
1363
1364             if (low < here + 1)
1365                 low = here + 1;
1366
1367             if (low < there) {
1368                 int aft = tl.getOffsetToLeftRightOf(low - lineStartOffset, isRtl) + lineStartOffset;
1369                 low = tl.getOffsetToLeftRightOf(aft - lineStartOffset, !isRtl) + lineStartOffset;
1370                 if (low >= here && low < there) {
1371                     float dist = Math.abs(getHorizontal(low, primary) - horiz);
1372                     if (aft < there) {
1373                         float other = Math.abs(getHorizontal(aft, primary) - horiz);
1374
1375                         if (other < dist) {
1376                             dist = other;
1377                             low = aft;
1378                         }
1379                     }
1380
1381                     if (dist < bestdist) {
1382                         bestdist = dist;
1383                         best = low;
1384                     }
1385                 }
1386             }
1387
1388             float dist = Math.abs(getHorizontal(here, primary) - horiz);
1389
1390             if (dist < bestdist) {
1391                 bestdist = dist;
1392                 best = here;
1393             }
1394         }
1395
1396         float dist = Math.abs(getHorizontal(max, primary) - horiz);
1397
1398         if (dist <= bestdist) {
1399             bestdist = dist;
1400             best = max;
1401         }
1402
1403         TextLine.recycle(tl);
1404         return best;
1405     }
1406
1407     /**
1408      * Return the text offset after the last character on the specified line.
1409      */
1410     public final int getLineEnd(int line) {
1411         return getLineStart(line + 1);
1412     }
1413
1414     /**
1415      * Return the text offset after the last visible character (so whitespace
1416      * is not counted) on the specified line.
1417      */
1418     public int getLineVisibleEnd(int line) {
1419         return getLineVisibleEnd(line, getLineStart(line), getLineStart(line+1));
1420     }
1421
1422     private int getLineVisibleEnd(int line, int start, int end) {
1423         CharSequence text = mText;
1424         char ch;
1425         if (line == getLineCount() - 1) {
1426             return end;
1427         }
1428
1429         for (; end > start; end--) {
1430             ch = text.charAt(end - 1);
1431
1432             if (ch == '\n') {
1433                 return end - 1;
1434             }
1435
1436             if (!TextLine.isLineEndSpace(ch)) {
1437                 break;
1438             }
1439
1440         }
1441
1442         return end;
1443     }
1444
1445     /**
1446      * Return the vertical position of the bottom of the specified line.
1447      */
1448     public final int getLineBottom(int line) {
1449         return getLineTop(line + 1);
1450     }
1451
1452     /**
1453      * Return the vertical position of the baseline of the specified line.
1454      */
1455     public final int getLineBaseline(int line) {
1456         // getLineTop(line+1) == getLineTop(line)
1457         return getLineTop(line+1) - getLineDescent(line);
1458     }
1459
1460     /**
1461      * Get the ascent of the text on the specified line.
1462      * The return value is negative to match the Paint.ascent() convention.
1463      */
1464     public final int getLineAscent(int line) {
1465         // getLineTop(line+1) - getLineDescent(line) == getLineBaseLine(line)
1466         return getLineTop(line) - (getLineTop(line+1) - getLineDescent(line));
1467     }
1468
1469     public int getOffsetToLeftOf(int offset) {
1470         return getOffsetToLeftRightOf(offset, true);
1471     }
1472
1473     public int getOffsetToRightOf(int offset) {
1474         return getOffsetToLeftRightOf(offset, false);
1475     }
1476
1477     private int getOffsetToLeftRightOf(int caret, boolean toLeft) {
1478         int line = getLineForOffset(caret);
1479         int lineStart = getLineStart(line);
1480         int lineEnd = getLineEnd(line);
1481         int lineDir = getParagraphDirection(line);
1482
1483         boolean lineChanged = false;
1484         boolean advance = toLeft == (lineDir == DIR_RIGHT_TO_LEFT);
1485         // if walking off line, look at the line we're headed to
1486         if (advance) {
1487             if (caret == lineEnd) {
1488                 if (line < getLineCount() - 1) {
1489                     lineChanged = true;
1490                     ++line;
1491                 } else {
1492                     return caret; // at very end, don't move
1493                 }
1494             }
1495         } else {
1496             if (caret == lineStart) {
1497                 if (line > 0) {
1498                     lineChanged = true;
1499                     --line;
1500                 } else {
1501                     return caret; // at very start, don't move
1502                 }
1503             }
1504         }
1505
1506         if (lineChanged) {
1507             lineStart = getLineStart(line);
1508             lineEnd = getLineEnd(line);
1509             int newDir = getParagraphDirection(line);
1510             if (newDir != lineDir) {
1511                 // unusual case.  we want to walk onto the line, but it runs
1512                 // in a different direction than this one, so we fake movement
1513                 // in the opposite direction.
1514                 toLeft = !toLeft;
1515                 lineDir = newDir;
1516             }
1517         }
1518
1519         Directions directions = getLineDirections(line);
1520
1521         TextLine tl = TextLine.obtain();
1522         // XXX: we don't care about tabs
1523         tl.set(mPaint, mText, lineStart, lineEnd, lineDir, directions, false, null);
1524         caret = lineStart + tl.getOffsetToLeftRightOf(caret - lineStart, toLeft);
1525         tl = TextLine.recycle(tl);
1526         return caret;
1527     }
1528
1529     private int getOffsetAtStartOf(int offset) {
1530         // XXX this probably should skip local reorderings and
1531         // zero-width characters, look at callers
1532         if (offset == 0)
1533             return 0;
1534
1535         CharSequence text = mText;
1536         char c = text.charAt(offset);
1537
1538         if (c >= '\uDC00' && c <= '\uDFFF') {
1539             char c1 = text.charAt(offset - 1);
1540
1541             if (c1 >= '\uD800' && c1 <= '\uDBFF')
1542                 offset -= 1;
1543         }
1544
1545         if (mSpannedText) {
1546             ReplacementSpan[] spans = ((Spanned) text).getSpans(offset, offset,
1547                                                        ReplacementSpan.class);
1548
1549             for (int i = 0; i < spans.length; i++) {
1550                 int start = ((Spanned) text).getSpanStart(spans[i]);
1551                 int end = ((Spanned) text).getSpanEnd(spans[i]);
1552
1553                 if (start < offset && end > offset)
1554                     offset = start;
1555             }
1556         }
1557
1558         return offset;
1559     }
1560
1561     /**
1562      * Determine whether we should clamp cursor position. Currently it's
1563      * only robust for left-aligned displays.
1564      * @hide
1565      */
1566     public boolean shouldClampCursor(int line) {
1567         // Only clamp cursor position in left-aligned displays.
1568         switch (getParagraphAlignment(line)) {
1569             case ALIGN_LEFT:
1570                 return true;
1571             case ALIGN_NORMAL:
1572                 return getParagraphDirection(line) > 0;
1573             default:
1574                 return false;
1575         }
1576
1577     }
1578     /**
1579      * Fills in the specified Path with a representation of a cursor
1580      * at the specified offset.  This will often be a vertical line
1581      * but can be multiple discontinuous lines in text with multiple
1582      * directionalities.
1583      */
1584     public void getCursorPath(int point, Path dest,
1585                               CharSequence editingBuffer) {
1586         dest.reset();
1587
1588         int line = getLineForOffset(point);
1589         int top = getLineTop(line);
1590         int bottom = getLineTop(line+1);
1591
1592         boolean clamped = shouldClampCursor(line);
1593         float h1 = getPrimaryHorizontal(point, clamped) - 0.5f;
1594         float h2 = isLevelBoundary(point) ? getSecondaryHorizontal(point, clamped) - 0.5f : h1;
1595
1596         int caps = TextKeyListener.getMetaState(editingBuffer, TextKeyListener.META_SHIFT_ON) |
1597                    TextKeyListener.getMetaState(editingBuffer, TextKeyListener.META_SELECTING);
1598         int fn = TextKeyListener.getMetaState(editingBuffer, TextKeyListener.META_ALT_ON);
1599         int dist = 0;
1600
1601         if (caps != 0 || fn != 0) {
1602             dist = (bottom - top) >> 2;
1603
1604             if (fn != 0)
1605                 top += dist;
1606             if (caps != 0)
1607                 bottom -= dist;
1608         }
1609
1610         if (h1 < 0.5f)
1611             h1 = 0.5f;
1612         if (h2 < 0.5f)
1613             h2 = 0.5f;
1614
1615         if (Float.compare(h1, h2) == 0) {
1616             dest.moveTo(h1, top);
1617             dest.lineTo(h1, bottom);
1618         } else {
1619             dest.moveTo(h1, top);
1620             dest.lineTo(h1, (top + bottom) >> 1);
1621
1622             dest.moveTo(h2, (top + bottom) >> 1);
1623             dest.lineTo(h2, bottom);
1624         }
1625
1626         if (caps == 2) {
1627             dest.moveTo(h2, bottom);
1628             dest.lineTo(h2 - dist, bottom + dist);
1629             dest.lineTo(h2, bottom);
1630             dest.lineTo(h2 + dist, bottom + dist);
1631         } else if (caps == 1) {
1632             dest.moveTo(h2, bottom);
1633             dest.lineTo(h2 - dist, bottom + dist);
1634
1635             dest.moveTo(h2 - dist, bottom + dist - 0.5f);
1636             dest.lineTo(h2 + dist, bottom + dist - 0.5f);
1637
1638             dest.moveTo(h2 + dist, bottom + dist);
1639             dest.lineTo(h2, bottom);
1640         }
1641
1642         if (fn == 2) {
1643             dest.moveTo(h1, top);
1644             dest.lineTo(h1 - dist, top - dist);
1645             dest.lineTo(h1, top);
1646             dest.lineTo(h1 + dist, top - dist);
1647         } else if (fn == 1) {
1648             dest.moveTo(h1, top);
1649             dest.lineTo(h1 - dist, top - dist);
1650
1651             dest.moveTo(h1 - dist, top - dist + 0.5f);
1652             dest.lineTo(h1 + dist, top - dist + 0.5f);
1653
1654             dest.moveTo(h1 + dist, top - dist);
1655             dest.lineTo(h1, top);
1656         }
1657     }
1658
1659     private void addSelection(int line, int start, int end,
1660                               int top, int bottom, Path dest) {
1661         int linestart = getLineStart(line);
1662         int lineend = getLineEnd(line);
1663         Directions dirs = getLineDirections(line);
1664
1665         if (lineend > linestart && mText.charAt(lineend - 1) == '\n')
1666             lineend--;
1667
1668         for (int i = 0; i < dirs.mDirections.length; i += 2) {
1669             int here = linestart + dirs.mDirections[i];
1670             int there = here + (dirs.mDirections[i+1] & RUN_LENGTH_MASK);
1671
1672             if (there > lineend)
1673                 there = lineend;
1674
1675             if (start <= there && end >= here) {
1676                 int st = Math.max(start, here);
1677                 int en = Math.min(end, there);
1678
1679                 if (st != en) {
1680                     float h1 = getHorizontal(st, false, line, false /* not clamped */);
1681                     float h2 = getHorizontal(en, true, line, false /* not clamped */);
1682
1683                     float left = Math.min(h1, h2);
1684                     float right = Math.max(h1, h2);
1685
1686                     dest.addRect(left, top, right, bottom, Path.Direction.CW);
1687                 }
1688             }
1689         }
1690     }
1691
1692     /**
1693      * Fills in the specified Path with a representation of a highlight
1694      * between the specified offsets.  This will often be a rectangle
1695      * or a potentially discontinuous set of rectangles.  If the start
1696      * and end are the same, the returned path is empty.
1697      */
1698     public void getSelectionPath(int start, int end, Path dest) {
1699         dest.reset();
1700
1701         if (start == end)
1702             return;
1703
1704         if (end < start) {
1705             int temp = end;
1706             end = start;
1707             start = temp;
1708         }
1709
1710         int startline = getLineForOffset(start);
1711         int endline = getLineForOffset(end);
1712
1713         int top = getLineTop(startline);
1714         int bottom = getLineBottom(endline);
1715
1716         if (startline == endline) {
1717             addSelection(startline, start, end, top, bottom, dest);
1718         } else {
1719             final float width = mWidth;
1720
1721             addSelection(startline, start, getLineEnd(startline),
1722                          top, getLineBottom(startline), dest);
1723
1724             if (getParagraphDirection(startline) == DIR_RIGHT_TO_LEFT)
1725                 dest.addRect(getLineLeft(startline), top,
1726                               0, getLineBottom(startline), Path.Direction.CW);
1727             else
1728                 dest.addRect(getLineRight(startline), top,
1729                               width, getLineBottom(startline), Path.Direction.CW);
1730
1731             for (int i = startline + 1; i < endline; i++) {
1732                 top = getLineTop(i);
1733                 bottom = getLineBottom(i);
1734                 dest.addRect(0, top, width, bottom, Path.Direction.CW);
1735             }
1736
1737             top = getLineTop(endline);
1738             bottom = getLineBottom(endline);
1739
1740             addSelection(endline, getLineStart(endline), end,
1741                          top, bottom, dest);
1742
1743             if (getParagraphDirection(endline) == DIR_RIGHT_TO_LEFT)
1744                 dest.addRect(width, top, getLineRight(endline), bottom, Path.Direction.CW);
1745             else
1746                 dest.addRect(0, top, getLineLeft(endline), bottom, Path.Direction.CW);
1747         }
1748     }
1749
1750     /**
1751      * Get the alignment of the specified paragraph, taking into account
1752      * markup attached to it.
1753      */
1754     public final Alignment getParagraphAlignment(int line) {
1755         Alignment align = mAlignment;
1756
1757         if (mSpannedText) {
1758             Spanned sp = (Spanned) mText;
1759             AlignmentSpan[] spans = getParagraphSpans(sp, getLineStart(line),
1760                                                 getLineEnd(line),
1761                                                 AlignmentSpan.class);
1762
1763             int spanLength = spans.length;
1764             if (spanLength > 0) {
1765                 align = spans[spanLength-1].getAlignment();
1766             }
1767         }
1768
1769         return align;
1770     }
1771
1772     /**
1773      * Get the left edge of the specified paragraph, inset by left margins.
1774      */
1775     public final int getParagraphLeft(int line) {
1776         int left = 0;
1777         int dir = getParagraphDirection(line);
1778         if (dir == DIR_RIGHT_TO_LEFT || !mSpannedText) {
1779             return left; // leading margin has no impact, or no styles
1780         }
1781         return getParagraphLeadingMargin(line);
1782     }
1783
1784     /**
1785      * Get the right edge of the specified paragraph, inset by right margins.
1786      */
1787     public final int getParagraphRight(int line) {
1788         int right = mWidth;
1789         int dir = getParagraphDirection(line);
1790         if (dir == DIR_LEFT_TO_RIGHT || !mSpannedText) {
1791             return right; // leading margin has no impact, or no styles
1792         }
1793         return right - getParagraphLeadingMargin(line);
1794     }
1795
1796     /**
1797      * Returns the effective leading margin (unsigned) for this line,
1798      * taking into account LeadingMarginSpan and LeadingMarginSpan2.
1799      * @param line the line index
1800      * @return the leading margin of this line
1801      */
1802     private int getParagraphLeadingMargin(int line) {
1803         if (!mSpannedText) {
1804             return 0;
1805         }
1806         Spanned spanned = (Spanned) mText;
1807
1808         int lineStart = getLineStart(line);
1809         int lineEnd = getLineEnd(line);
1810         int spanEnd = spanned.nextSpanTransition(lineStart, lineEnd,
1811                 LeadingMarginSpan.class);
1812         LeadingMarginSpan[] spans = getParagraphSpans(spanned, lineStart, spanEnd,
1813                                                 LeadingMarginSpan.class);
1814         if (spans.length == 0) {
1815             return 0; // no leading margin span;
1816         }
1817
1818         int margin = 0;
1819
1820         boolean isFirstParaLine = lineStart == 0 ||
1821             spanned.charAt(lineStart - 1) == '\n';
1822
1823         boolean useFirstLineMargin = isFirstParaLine;
1824         for (int i = 0; i < spans.length; i++) {
1825             if (spans[i] instanceof LeadingMarginSpan2) {
1826                 int spStart = spanned.getSpanStart(spans[i]);
1827                 int spanLine = getLineForOffset(spStart);
1828                 int count = ((LeadingMarginSpan2) spans[i]).getLeadingMarginLineCount();
1829                 // if there is more than one LeadingMarginSpan2, use the count that is greatest
1830                 useFirstLineMargin |= line < spanLine + count;
1831             }
1832         }
1833         for (int i = 0; i < spans.length; i++) {
1834             LeadingMarginSpan span = spans[i];
1835             margin += span.getLeadingMargin(useFirstLineMargin);
1836         }
1837
1838         return margin;
1839     }
1840
1841     /* package */
1842     static float measurePara(TextPaint paint, CharSequence text, int start, int end,
1843             TextDirectionHeuristic textDir) {
1844         MeasuredText mt = MeasuredText.obtain();
1845         TextLine tl = TextLine.obtain();
1846         try {
1847             mt.setPara(text, start, end, textDir, null);
1848             Directions directions;
1849             int dir;
1850             if (mt.mEasy) {
1851                 directions = DIRS_ALL_LEFT_TO_RIGHT;
1852                 dir = Layout.DIR_LEFT_TO_RIGHT;
1853             } else {
1854                 directions = AndroidBidi.directions(mt.mDir, mt.mLevels,
1855                     0, mt.mChars, 0, mt.mLen);
1856                 dir = mt.mDir;
1857             }
1858             char[] chars = mt.mChars;
1859             int len = mt.mLen;
1860             boolean hasTabs = false;
1861             TabStops tabStops = null;
1862             // leading margins should be taken into account when measuring a paragraph
1863             int margin = 0;
1864             if (text instanceof Spanned) {
1865                 Spanned spanned = (Spanned) text;
1866                 LeadingMarginSpan[] spans = getParagraphSpans(spanned, start, end,
1867                         LeadingMarginSpan.class);
1868                 for (LeadingMarginSpan lms : spans) {
1869                     margin += lms.getLeadingMargin(true);
1870                 }
1871             }
1872             for (int i = 0; i < len; ++i) {
1873                 if (chars[i] == '\t') {
1874                     hasTabs = true;
1875                     if (text instanceof Spanned) {
1876                         Spanned spanned = (Spanned) text;
1877                         int spanEnd = spanned.nextSpanTransition(start, end,
1878                                 TabStopSpan.class);
1879                         TabStopSpan[] spans = getParagraphSpans(spanned, start, spanEnd,
1880                                 TabStopSpan.class);
1881                         if (spans.length > 0) {
1882                             tabStops = new TabStops(TAB_INCREMENT, spans);
1883                         }
1884                     }
1885                     break;
1886                 }
1887             }
1888             tl.set(paint, text, start, end, dir, directions, hasTabs, tabStops);
1889             return margin + Math.abs(tl.metrics(null));
1890         } finally {
1891             TextLine.recycle(tl);
1892             MeasuredText.recycle(mt);
1893         }
1894     }
1895
1896     /**
1897      * @hide
1898      */
1899     /* package */ static class TabStops {
1900         private int[] mStops;
1901         private int mNumStops;
1902         private int mIncrement;
1903
1904         TabStops(int increment, Object[] spans) {
1905             reset(increment, spans);
1906         }
1907
1908         void reset(int increment, Object[] spans) {
1909             this.mIncrement = increment;
1910
1911             int ns = 0;
1912             if (spans != null) {
1913                 int[] stops = this.mStops;
1914                 for (Object o : spans) {
1915                     if (o instanceof TabStopSpan) {
1916                         if (stops == null) {
1917                             stops = new int[10];
1918                         } else if (ns == stops.length) {
1919                             int[] nstops = new int[ns * 2];
1920                             for (int i = 0; i < ns; ++i) {
1921                                 nstops[i] = stops[i];
1922                             }
1923                             stops = nstops;
1924                         }
1925                         stops[ns++] = ((TabStopSpan) o).getTabStop();
1926                     }
1927                 }
1928                 if (ns > 1) {
1929                     Arrays.sort(stops, 0, ns);
1930                 }
1931                 if (stops != this.mStops) {
1932                     this.mStops = stops;
1933                 }
1934             }
1935             this.mNumStops = ns;
1936         }
1937
1938         float nextTab(float h) {
1939             int ns = this.mNumStops;
1940             if (ns > 0) {
1941                 int[] stops = this.mStops;
1942                 for (int i = 0; i < ns; ++i) {
1943                     int stop = stops[i];
1944                     if (stop > h) {
1945                         return stop;
1946                     }
1947                 }
1948             }
1949             return nextDefaultStop(h, mIncrement);
1950         }
1951
1952         public static float nextDefaultStop(float h, int inc) {
1953             return ((int) ((h + inc) / inc)) * inc;
1954         }
1955     }
1956
1957     /**
1958      * Returns the position of the next tab stop after h on the line.
1959      *
1960      * @param text the text
1961      * @param start start of the line
1962      * @param end limit of the line
1963      * @param h the current horizontal offset
1964      * @param tabs the tabs, can be null.  If it is null, any tabs in effect
1965      * on the line will be used.  If there are no tabs, a default offset
1966      * will be used to compute the tab stop.
1967      * @return the offset of the next tab stop.
1968      */
1969     /* package */ static float nextTab(CharSequence text, int start, int end,
1970                                        float h, Object[] tabs) {
1971         float nh = Float.MAX_VALUE;
1972         boolean alltabs = false;
1973
1974         if (text instanceof Spanned) {
1975             if (tabs == null) {
1976                 tabs = getParagraphSpans((Spanned) text, start, end, TabStopSpan.class);
1977                 alltabs = true;
1978             }
1979
1980             for (int i = 0; i < tabs.length; i++) {
1981                 if (!alltabs) {
1982                     if (!(tabs[i] instanceof TabStopSpan))
1983                         continue;
1984                 }
1985
1986                 int where = ((TabStopSpan) tabs[i]).getTabStop();
1987
1988                 if (where < nh && where > h)
1989                     nh = where;
1990             }
1991
1992             if (nh != Float.MAX_VALUE)
1993                 return nh;
1994         }
1995
1996         return ((int) ((h + TAB_INCREMENT) / TAB_INCREMENT)) * TAB_INCREMENT;
1997     }
1998
1999     protected final boolean isSpanned() {
2000         return mSpannedText;
2001     }
2002
2003     /**
2004      * Returns the same as <code>text.getSpans()</code>, except where
2005      * <code>start</code> and <code>end</code> are the same and are not
2006      * at the very beginning of the text, in which case an empty array
2007      * is returned instead.
2008      * <p>
2009      * This is needed because of the special case that <code>getSpans()</code>
2010      * on an empty range returns the spans adjacent to that range, which is
2011      * primarily for the sake of <code>TextWatchers</code> so they will get
2012      * notifications when text goes from empty to non-empty.  But it also
2013      * has the unfortunate side effect that if the text ends with an empty
2014      * paragraph, that paragraph accidentally picks up the styles of the
2015      * preceding paragraph (even though those styles will not be picked up
2016      * by new text that is inserted into the empty paragraph).
2017      * <p>
2018      * The reason it just checks whether <code>start</code> and <code>end</code>
2019      * is the same is that the only time a line can contain 0 characters
2020      * is if it is the final paragraph of the Layout; otherwise any line will
2021      * contain at least one printing or newline character.  The reason for the
2022      * additional check if <code>start</code> is greater than 0 is that
2023      * if the empty paragraph is the entire content of the buffer, paragraph
2024      * styles that are already applied to the buffer will apply to text that
2025      * is inserted into it.
2026      */
2027     /* package */static <T> T[] getParagraphSpans(Spanned text, int start, int end, Class<T> type) {
2028         if (start == end && start > 0) {
2029             return ArrayUtils.emptyArray(type);
2030         }
2031
2032         if(text instanceof SpannableStringBuilder) {
2033             return ((SpannableStringBuilder) text).getSpans(start, end, type, false);
2034         } else {
2035             return text.getSpans(start, end, type);
2036         }
2037     }
2038
2039     private char getEllipsisChar(TextUtils.TruncateAt method) {
2040         return (method == TextUtils.TruncateAt.END_SMALL) ?
2041                 TextUtils.ELLIPSIS_TWO_DOTS[0] :
2042                 TextUtils.ELLIPSIS_NORMAL[0];
2043     }
2044
2045     private void ellipsize(int start, int end, int line,
2046                            char[] dest, int destoff, TextUtils.TruncateAt method) {
2047         int ellipsisCount = getEllipsisCount(line);
2048
2049         if (ellipsisCount == 0) {
2050             return;
2051         }
2052
2053         int ellipsisStart = getEllipsisStart(line);
2054         int linestart = getLineStart(line);
2055
2056         for (int i = ellipsisStart; i < ellipsisStart + ellipsisCount; i++) {
2057             char c;
2058
2059             if (i == ellipsisStart) {
2060                 c = getEllipsisChar(method); // ellipsis
2061             } else {
2062                 c = '\uFEFF'; // 0-width space
2063             }
2064
2065             int a = i + linestart;
2066
2067             if (a >= start && a < end) {
2068                 dest[destoff + a - start] = c;
2069             }
2070         }
2071     }
2072
2073     /**
2074      * Stores information about bidirectional (left-to-right or right-to-left)
2075      * text within the layout of a line.
2076      */
2077     public static class Directions {
2078         // Directions represents directional runs within a line of text.
2079         // Runs are pairs of ints listed in visual order, starting from the
2080         // leading margin.  The first int of each pair is the offset from
2081         // the first character of the line to the start of the run.  The
2082         // second int represents both the length and level of the run.
2083         // The length is in the lower bits, accessed by masking with
2084         // DIR_LENGTH_MASK.  The level is in the higher bits, accessed
2085         // by shifting by DIR_LEVEL_SHIFT and masking by DIR_LEVEL_MASK.
2086         // To simply test for an RTL direction, test the bit using
2087         // DIR_RTL_FLAG, if set then the direction is rtl.
2088
2089         /**
2090          * @hide
2091          */
2092         @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
2093         public int[] mDirections;
2094
2095         /**
2096          * @hide
2097          */
2098         @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
2099         public Directions(int[] dirs) {
2100             mDirections = dirs;
2101         }
2102     }
2103
2104     /**
2105      * Return the offset of the first character to be ellipsized away,
2106      * relative to the start of the line.  (So 0 if the beginning of the
2107      * line is ellipsized, not getLineStart().)
2108      */
2109     public abstract int getEllipsisStart(int line);
2110
2111     /**
2112      * Returns the number of characters to be ellipsized away, or 0 if
2113      * no ellipsis is to take place.
2114      */
2115     public abstract int getEllipsisCount(int line);
2116
2117     /* package */ static class Ellipsizer implements CharSequence, GetChars {
2118         /* package */ CharSequence mText;
2119         /* package */ Layout mLayout;
2120         /* package */ int mWidth;
2121         /* package */ TextUtils.TruncateAt mMethod;
2122
2123         public Ellipsizer(CharSequence s) {
2124             mText = s;
2125         }
2126
2127         public char charAt(int off) {
2128             char[] buf = TextUtils.obtain(1);
2129             getChars(off, off + 1, buf, 0);
2130             char ret = buf[0];
2131
2132             TextUtils.recycle(buf);
2133             return ret;
2134         }
2135
2136         public void getChars(int start, int end, char[] dest, int destoff) {
2137             int line1 = mLayout.getLineForOffset(start);
2138             int line2 = mLayout.getLineForOffset(end);
2139
2140             TextUtils.getChars(mText, start, end, dest, destoff);
2141
2142             for (int i = line1; i <= line2; i++) {
2143                 mLayout.ellipsize(start, end, i, dest, destoff, mMethod);
2144             }
2145         }
2146
2147         public int length() {
2148             return mText.length();
2149         }
2150
2151         public CharSequence subSequence(int start, int end) {
2152             char[] s = new char[end - start];
2153             getChars(start, end, s, 0);
2154             return new String(s);
2155         }
2156
2157         @Override
2158         public String toString() {
2159             char[] s = new char[length()];
2160             getChars(0, length(), s, 0);
2161             return new String(s);
2162         }
2163
2164     }
2165
2166     /* package */ static class SpannedEllipsizer extends Ellipsizer implements Spanned {
2167         private Spanned mSpanned;
2168
2169         public SpannedEllipsizer(CharSequence display) {
2170             super(display);
2171             mSpanned = (Spanned) display;
2172         }
2173
2174         public <T> T[] getSpans(int start, int end, Class<T> type) {
2175             return mSpanned.getSpans(start, end, type);
2176         }
2177
2178         public int getSpanStart(Object tag) {
2179             return mSpanned.getSpanStart(tag);
2180         }
2181
2182         public int getSpanEnd(Object tag) {
2183             return mSpanned.getSpanEnd(tag);
2184         }
2185
2186         public int getSpanFlags(Object tag) {
2187             return mSpanned.getSpanFlags(tag);
2188         }
2189
2190         @SuppressWarnings("rawtypes")
2191         public int nextSpanTransition(int start, int limit, Class type) {
2192             return mSpanned.nextSpanTransition(start, limit, type);
2193         }
2194
2195         @Override
2196         public CharSequence subSequence(int start, int end) {
2197             char[] s = new char[end - start];
2198             getChars(start, end, s, 0);
2199
2200             SpannableString ss = new SpannableString(new String(s));
2201             TextUtils.copySpansFrom(mSpanned, start, end, Object.class, ss, 0);
2202             return ss;
2203         }
2204     }
2205
2206     private CharSequence mText;
2207     private TextPaint mPaint;
2208     private int mWidth;
2209     private Alignment mAlignment = Alignment.ALIGN_NORMAL;
2210     private float mSpacingMult;
2211     private float mSpacingAdd;
2212     private static final Rect sTempRect = new Rect();
2213     private boolean mSpannedText;
2214     private TextDirectionHeuristic mTextDir;
2215     private SpanSet<LineBackgroundSpan> mLineBackgroundSpans;
2216     private int mJustificationMode;
2217
2218     public static final int DIR_LEFT_TO_RIGHT = 1;
2219     public static final int DIR_RIGHT_TO_LEFT = -1;
2220
2221     /* package */ static final int DIR_REQUEST_LTR = 1;
2222     /* package */ static final int DIR_REQUEST_RTL = -1;
2223     /* package */ static final int DIR_REQUEST_DEFAULT_LTR = 2;
2224     /* package */ static final int DIR_REQUEST_DEFAULT_RTL = -2;
2225
2226     /* package */ static final int RUN_LENGTH_MASK = 0x03ffffff;
2227     /* package */ static final int RUN_LEVEL_SHIFT = 26;
2228     /* package */ static final int RUN_LEVEL_MASK = 0x3f;
2229     /* package */ static final int RUN_RTL_FLAG = 1 << RUN_LEVEL_SHIFT;
2230
2231     public enum Alignment {
2232         ALIGN_NORMAL,
2233         ALIGN_OPPOSITE,
2234         ALIGN_CENTER,
2235         /** @hide */
2236         ALIGN_LEFT,
2237         /** @hide */
2238         ALIGN_RIGHT,
2239     }
2240
2241     private static final int TAB_INCREMENT = 20;
2242
2243     /** @hide */
2244     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
2245     public static final Directions DIRS_ALL_LEFT_TO_RIGHT =
2246         new Directions(new int[] { 0, RUN_LENGTH_MASK });
2247
2248     /** @hide */
2249     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
2250     public static final Directions DIRS_ALL_RIGHT_TO_LEFT =
2251         new Directions(new int[] { 0, RUN_LENGTH_MASK | RUN_RTL_FLAG });
2252
2253 }