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
17package android.text;
18
19import android.annotation.IntDef;
20import android.graphics.Canvas;
21import android.graphics.Paint;
22import android.graphics.Path;
23import android.graphics.Rect;
24import android.text.method.TextKeyListener;
25import android.text.style.AlignmentSpan;
26import android.text.style.LeadingMarginSpan;
27import android.text.style.LeadingMarginSpan.LeadingMarginSpan2;
28import android.text.style.LineBackgroundSpan;
29import android.text.style.ParagraphStyle;
30import android.text.style.ReplacementSpan;
31import android.text.style.TabStopSpan;
32
33import com.android.internal.annotations.VisibleForTesting;
34import com.android.internal.util.ArrayUtils;
35import com.android.internal.util.GrowingArrayUtils;
36
37import java.lang.annotation.Retention;
38import java.lang.annotation.RetentionPolicy;
39import 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 */
48public 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}
2254