Layout.java revision 4e0c5e55e171532760d5f51e0165563827129d4e
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 com.android.internal.util.ArrayUtils;
20
21import android.emoji.EmojiFactory;
22import android.graphics.Bitmap;
23import android.graphics.Canvas;
24import android.graphics.Paint;
25import android.graphics.Path;
26import android.graphics.Rect;
27import android.graphics.RectF;
28import android.text.method.TextKeyListener;
29import android.text.style.AlignmentSpan;
30import android.text.style.LeadingMarginSpan;
31import android.text.style.LineBackgroundSpan;
32import android.text.style.ParagraphStyle;
33import android.text.style.ReplacementSpan;
34import android.text.style.TabStopSpan;
35import android.view.KeyEvent;
36
37import junit.framework.Assert;
38
39/**
40 * A base class that manages text layout in visual elements on
41 * the screen.
42 * <p>For text that will be edited, use a {@link DynamicLayout},
43 * which will be updated as the text changes.
44 * For text that will not change, use a {@link StaticLayout}.
45 */
46public abstract class Layout {
47    private static final boolean DEBUG = false;
48    private static final ParagraphStyle[] NO_PARA_SPANS =
49        ArrayUtils.emptyArray(ParagraphStyle.class);
50
51    /* package */ static final EmojiFactory EMOJI_FACTORY =
52        EmojiFactory.newAvailableInstance();
53    /* package */ static final int MIN_EMOJI, MAX_EMOJI;
54
55    static {
56        if (EMOJI_FACTORY != null) {
57            MIN_EMOJI = EMOJI_FACTORY.getMinimumAndroidPua();
58            MAX_EMOJI = EMOJI_FACTORY.getMaximumAndroidPua();
59        } else {
60            MIN_EMOJI = -1;
61            MAX_EMOJI = -1;
62        }
63    };
64
65    private RectF mEmojiRect;
66
67    /**
68     * Return how wide a layout must be in order to display the
69     * specified text with one line per paragraph.
70     */
71    public static float getDesiredWidth(CharSequence source,
72                                        TextPaint paint) {
73        return getDesiredWidth(source, 0, source.length(), paint);
74    }
75
76    /**
77     * Return how wide a layout must be in order to display the
78     * specified text slice with one line per paragraph.
79     */
80    public static float getDesiredWidth(CharSequence source,
81                                        int start, int end,
82                                        TextPaint paint) {
83        float need = 0;
84        TextPaint workPaint = new TextPaint();
85
86        int next;
87        for (int i = start; i <= end; i = next) {
88            next = TextUtils.indexOf(source, '\n', i, end);
89
90            if (next < 0)
91                next = end;
92
93            // note, omits trailing paragraph char
94            float w = measureText(paint, workPaint,
95                                  source, i, next, null, true, null);
96
97            if (w > need)
98                need = w;
99
100            next++;
101        }
102
103        return need;
104    }
105
106    /**
107     * Subclasses of Layout use this constructor to set the display text,
108     * width, and other standard properties.
109     * @param text the text to render
110     * @param paint the default paint for the layout.  Styles can override
111     * various attributes of the paint.
112     * @param width the wrapping width for the text.
113     * @param align whether to left, right, or center the text.  Styles can
114     * override the alignment.
115     * @param spacingMult factor by which to scale the font size to get the
116     * default line spacing
117     * @param spacingAdd amount to add to the default line spacing
118     */
119    protected Layout(CharSequence text, TextPaint paint,
120                     int width, Alignment align,
121                     float spacingMult, float spacingAdd) {
122        if (width < 0)
123            throw new IllegalArgumentException("Layout: " + width + " < 0");
124
125        mText = text;
126        mPaint = paint;
127        mWorkPaint = new TextPaint();
128        mWidth = width;
129        mAlignment = align;
130        mSpacingMult = spacingMult;
131        mSpacingAdd = spacingAdd;
132        mSpannedText = text instanceof Spanned;
133    }
134
135    /**
136     * Replace constructor properties of this Layout with new ones.  Be careful.
137     */
138    /* package */ void replaceWith(CharSequence text, TextPaint paint,
139                              int width, Alignment align,
140                              float spacingmult, float spacingadd) {
141        if (width < 0) {
142            throw new IllegalArgumentException("Layout: " + width + " < 0");
143        }
144
145        mText = text;
146        mPaint = paint;
147        mWidth = width;
148        mAlignment = align;
149        mSpacingMult = spacingmult;
150        mSpacingAdd = spacingadd;
151        mSpannedText = text instanceof Spanned;
152    }
153
154    /**
155     * Draw this Layout on the specified Canvas.
156     */
157    public void draw(Canvas c) {
158        draw(c, null, null, 0);
159    }
160
161    /**
162     * Draw this Layout on the specified canvas, with the highlight path drawn
163     * between the background and the text.
164     *
165     * @param c the canvas
166     * @param highlight the path of the highlight or cursor; can be null
167     * @param highlightPaint the paint for the highlight
168     * @param cursorOffsetVertical the amount to temporarily translate the
169     *        canvas while rendering the highlight
170     */
171    public void draw(Canvas c, Path highlight, Paint highlightPaint,
172                     int cursorOffsetVertical) {
173        int dtop, dbottom;
174
175        synchronized (sTempRect) {
176            if (!c.getClipBounds(sTempRect)) {
177                return;
178            }
179
180            dtop = sTempRect.top;
181            dbottom = sTempRect.bottom;
182        }
183
184
185        int top = 0;
186        int bottom = getLineTop(getLineCount());
187
188        if (dtop > top) {
189            top = dtop;
190        }
191        if (dbottom < bottom) {
192            bottom = dbottom;
193        }
194
195        int first = getLineForVertical(top);
196        int last = getLineForVertical(bottom);
197
198        int previousLineBottom = getLineTop(first);
199        int previousLineEnd = getLineStart(first);
200
201        TextPaint paint = mPaint;
202        CharSequence buf = mText;
203        int width = mWidth;
204        boolean spannedText = mSpannedText;
205
206        ParagraphStyle[] spans = NO_PARA_SPANS;
207        int spanend = 0;
208        int textLength = 0;
209
210        // First, draw LineBackgroundSpans.
211        // LineBackgroundSpans know nothing about the alignment or direction of
212        // the layout or line.  XXX: Should they?
213        if (spannedText) {
214            textLength = buf.length();
215            for (int i = first; i <= last; i++) {
216                int start = previousLineEnd;
217                int end = getLineStart(i+1);
218                previousLineEnd = end;
219
220                int ltop = previousLineBottom;
221                int lbottom = getLineTop(i+1);
222                previousLineBottom = lbottom;
223                int lbaseline = lbottom - getLineDescent(i);
224
225                if (start >= spanend) {
226                   Spanned sp = (Spanned) buf;
227                   spanend = sp.nextSpanTransition(start, textLength,
228                                                   LineBackgroundSpan.class);
229                   spans = sp.getSpans(start, spanend,
230                                       LineBackgroundSpan.class);
231                }
232
233                for (int n = 0; n < spans.length; n++) {
234                    LineBackgroundSpan back = (LineBackgroundSpan) spans[n];
235
236                    back.drawBackground(c, paint, 0, width,
237                                       ltop, lbaseline, lbottom,
238                                       buf, start, end,
239                                       i);
240                }
241            }
242            // reset to their original values
243            spanend = 0;
244            previousLineBottom = getLineTop(first);
245            previousLineEnd = getLineStart(first);
246            spans = NO_PARA_SPANS;
247        }
248
249        // There can be a highlight even without spans if we are drawing
250        // a non-spanned transformation of a spanned editing buffer.
251        if (highlight != null) {
252            if (cursorOffsetVertical != 0) {
253                c.translate(0, cursorOffsetVertical);
254            }
255
256            c.drawPath(highlight, highlightPaint);
257
258            if (cursorOffsetVertical != 0) {
259                c.translate(0, -cursorOffsetVertical);
260            }
261        }
262
263        Alignment align = mAlignment;
264
265        // Next draw the lines, one at a time.
266        // the baseline is the top of the following line minus the current
267        // line's descent.
268        for (int i = first; i <= last; i++) {
269            int start = previousLineEnd;
270
271            previousLineEnd = getLineStart(i+1);
272            int end = getLineVisibleEnd(i, start, previousLineEnd);
273
274            int ltop = previousLineBottom;
275            int lbottom = getLineTop(i+1);
276            previousLineBottom = lbottom;
277            int lbaseline = lbottom - getLineDescent(i);
278
279            boolean isFirstParaLine = false;
280            if (spannedText) {
281                if (start == 0 || buf.charAt(start - 1) == '\n') {
282                    isFirstParaLine = true;
283                }
284                // New batch of paragraph styles, compute the alignment.
285                // Last alignment style wins.
286                if (start >= spanend) {
287                    Spanned sp = (Spanned) buf;
288                    spanend = sp.nextSpanTransition(start, textLength,
289                                                    ParagraphStyle.class);
290                    spans = sp.getSpans(start, spanend, ParagraphStyle.class);
291
292                    align = mAlignment;
293                    for (int n = spans.length-1; n >= 0; n--) {
294                        if (spans[n] instanceof AlignmentSpan) {
295                            align = ((AlignmentSpan) spans[n]).getAlignment();
296                            break;
297                        }
298                    }
299                }
300            }
301
302            int dir = getParagraphDirection(i);
303            int left = 0;
304            int right = mWidth;
305
306            // Draw all leading margin spans.  Adjust left or right according
307            // to the paragraph direction of the line.
308            if (spannedText) {
309                final int length = spans.length;
310                for (int n = 0; n < length; n++) {
311                    if (spans[n] instanceof LeadingMarginSpan) {
312                        LeadingMarginSpan margin = (LeadingMarginSpan) spans[n];
313
314                        if (dir == DIR_RIGHT_TO_LEFT) {
315                            margin.drawLeadingMargin(c, paint, right, dir, ltop,
316                                                     lbaseline, lbottom, buf,
317                                                     start, end, isFirstParaLine, this);
318
319                            right -= margin.getLeadingMargin(isFirstParaLine);
320                        } else {
321                            margin.drawLeadingMargin(c, paint, left, dir, ltop,
322                                                     lbaseline, lbottom, buf,
323                                                     start, end, isFirstParaLine, this);
324
325                            boolean useMargin = isFirstParaLine;
326                            if (margin instanceof LeadingMarginSpan.LeadingMarginSpan2) {
327                                int count = ((LeadingMarginSpan.LeadingMarginSpan2)margin).getLeadingMarginLineCount();
328                                useMargin = count > i;
329                            }
330                            left += margin.getLeadingMargin(useMargin);
331                        }
332                    }
333                }
334            }
335
336            // Adjust the point at which to start rendering depending on the
337            // alignment of the paragraph.
338            int x;
339            if (align == Alignment.ALIGN_NORMAL) {
340                if (dir == DIR_LEFT_TO_RIGHT) {
341                    x = left;
342                } else {
343                    x = right;
344                }
345            } else {
346                int max = (int)getLineMax(i, spans, false);
347                if (align == Alignment.ALIGN_OPPOSITE) {
348                    if (dir == DIR_RIGHT_TO_LEFT) {
349                        x = left + max;
350                    } else {
351                        x = right - max;
352                    }
353                } else {
354                    // Alignment.ALIGN_CENTER
355                    max = max & ~1;
356                    int half = (right - left - max) >> 1;
357                    if (dir == DIR_RIGHT_TO_LEFT) {
358                        x = right - half;
359                    } else {
360                        x = left + half;
361                    }
362                }
363            }
364
365            Directions directions = getLineDirections(i);
366            boolean hasTab = getLineContainsTab(i);
367            if (directions == DIRS_ALL_LEFT_TO_RIGHT &&
368                    !spannedText && !hasTab) {
369                if (DEBUG) {
370                    Assert.assertTrue(dir == DIR_LEFT_TO_RIGHT);
371                    Assert.assertNotNull(c);
372                }
373                // XXX: assumes there's nothing additional to be done
374                c.drawText(buf, start, end, x, lbaseline, paint);
375            } else {
376                drawText(c, buf, start, end, dir, directions,
377                    x, ltop, lbaseline, lbottom, paint, mWorkPaint,
378                    hasTab, spans);
379            }
380        }
381    }
382
383    /**
384     * Return the text that is displayed by this Layout.
385     */
386    public final CharSequence getText() {
387        return mText;
388    }
389
390    /**
391     * Return the base Paint properties for this layout.
392     * Do NOT change the paint, which may result in funny
393     * drawing for this layout.
394     */
395    public final TextPaint getPaint() {
396        return mPaint;
397    }
398
399    /**
400     * Return the width of this layout.
401     */
402    public final int getWidth() {
403        return mWidth;
404    }
405
406    /**
407     * Return the width to which this Layout is ellipsizing, or
408     * {@link #getWidth} if it is not doing anything special.
409     */
410    public int getEllipsizedWidth() {
411        return mWidth;
412    }
413
414    /**
415     * Increase the width of this layout to the specified width.
416     * Be careful to use this only when you know it is appropriate&mdash;
417     * it does not cause the text to reflow to use the full new width.
418     */
419    public final void increaseWidthTo(int wid) {
420        if (wid < mWidth) {
421            throw new RuntimeException("attempted to reduce Layout width");
422        }
423
424        mWidth = wid;
425    }
426
427    /**
428     * Return the total height of this layout.
429     */
430    public int getHeight() {
431        return getLineTop(getLineCount());
432    }
433
434    /**
435     * Return the base alignment of this layout.
436     */
437    public final Alignment getAlignment() {
438        return mAlignment;
439    }
440
441    /**
442     * Return what the text height is multiplied by to get the line height.
443     */
444    public final float getSpacingMultiplier() {
445        return mSpacingMult;
446    }
447
448    /**
449     * Return the number of units of leading that are added to each line.
450     */
451    public final float getSpacingAdd() {
452        return mSpacingAdd;
453    }
454
455    /**
456     * Return the number of lines of text in this layout.
457     */
458    public abstract int getLineCount();
459
460    /**
461     * Return the baseline for the specified line (0&hellip;getLineCount() - 1)
462     * If bounds is not null, return the top, left, right, bottom extents
463     * of the specified line in it.
464     * @param line which line to examine (0..getLineCount() - 1)
465     * @param bounds Optional. If not null, it returns the extent of the line
466     * @return the Y-coordinate of the baseline
467     */
468    public int getLineBounds(int line, Rect bounds) {
469        if (bounds != null) {
470            bounds.left = 0;     // ???
471            bounds.top = getLineTop(line);
472            bounds.right = mWidth;   // ???
473            bounds.bottom = getLineTop(line + 1);
474        }
475        return getLineBaseline(line);
476    }
477
478    /**
479     * Return the vertical position of the top of the specified line
480     * (0&hellip;getLineCount()).
481     * If the specified line is equal to the line count, returns the
482     * bottom of the last line.
483     */
484    public abstract int getLineTop(int line);
485
486    /**
487     * Return the descent of the specified line(0&hellip;getLineCount() - 1).
488     */
489    public abstract int getLineDescent(int line);
490
491    /**
492     * Return the text offset of the beginning of the specified line (
493     * 0&hellip;getLineCount()). If the specified line is equal to the line
494     * count, returns the length of the text.
495     */
496    public abstract int getLineStart(int line);
497
498    /**
499     * Returns the primary directionality of the paragraph containing the
500     * specified line, either 1 for left-to-right lines, or -1 for right-to-left
501     * lines (see {@link #DIR_LEFT_TO_RIGHT}, {@link #DIR_RIGHT_TO_LEFT}).
502     */
503    public abstract int getParagraphDirection(int line);
504
505    /**
506     * Returns whether the specified line contains one or more
507     * characters that need to be handled specially, like tabs
508     * or emoji.
509     */
510    public abstract boolean getLineContainsTab(int line);
511
512    /**
513     * Returns the directional run information for the specified line.
514     * The array alternates counts of characters in left-to-right
515     * and right-to-left segments of the line.
516     *
517     * <p>NOTE: this is inadequate to support bidirectional text, and will change.
518     */
519    public abstract Directions getLineDirections(int line);
520
521    /**
522     * Returns the (negative) number of extra pixels of ascent padding in the
523     * top line of the Layout.
524     */
525    public abstract int getTopPadding();
526
527    /**
528     * Returns the number of extra pixels of descent padding in the
529     * bottom line of the Layout.
530     */
531    public abstract int getBottomPadding();
532
533
534    /**
535     * Returns true if the character at offset and the preceding character
536     * are at different run levels (and thus there's a split caret).
537     * @param offset the offset
538     * @return true if at a level boundary
539     */
540    private boolean isLevelBoundary(int offset) {
541        int line = getLineForOffset(offset);
542        Directions dirs = getLineDirections(line);
543        if (dirs == DIRS_ALL_LEFT_TO_RIGHT || dirs == DIRS_ALL_RIGHT_TO_LEFT) {
544            return false;
545        }
546
547        int[] runs = dirs.mDirections;
548        int lineStart = getLineStart(line);
549        int lineEnd = getLineEnd(line);
550        if (offset == lineStart || offset == lineEnd) {
551            int paraLevel = getParagraphDirection(line) == 1 ? 0 : 1;
552            int runIndex = offset == lineStart ? 0 : runs.length - 2;
553            return ((runs[runIndex + 1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK) != paraLevel;
554        }
555
556        offset -= lineStart;
557        for (int i = 0; i < runs.length; i += 2) {
558            if (offset == runs[i]) {
559                return true;
560            }
561        }
562        return false;
563    }
564
565    private boolean primaryIsTrailingPrevious(int offset) {
566        int line = getLineForOffset(offset);
567        int lineStart = getLineStart(line);
568        int lineEnd = getLineEnd(line);
569        int[] runs = getLineDirections(line).mDirections;
570
571        int levelAt = -1;
572        for (int i = 0; i < runs.length; i += 2) {
573            int start = lineStart + runs[i];
574            int limit = start + (runs[i+1] & RUN_LENGTH_MASK);
575            if (limit > lineEnd) {
576                limit = lineEnd;
577            }
578            if (offset >= start && offset < limit) {
579                if (offset > start) {
580                    // Previous character is at same level, so don't use trailing.
581                    return false;
582                }
583                levelAt = (runs[i+1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK;
584                break;
585            }
586        }
587        if (levelAt == -1) {
588            // Offset was limit of line.
589            levelAt = getParagraphDirection(line) == 1 ? 0 : 1;
590        }
591
592        // At level boundary, check previous level.
593        int levelBefore = -1;
594        if (offset == lineStart) {
595            levelBefore = getParagraphDirection(line) == 1 ? 0 : 1;
596        } else {
597            offset -= 1;
598            for (int i = 0; i < runs.length; i += 2) {
599                int start = lineStart + runs[i];
600                int limit = start + (runs[i+1] & RUN_LENGTH_MASK);
601                if (limit > lineEnd) {
602                    limit = lineEnd;
603                }
604                if (offset >= start && offset < limit) {
605                    levelBefore = (runs[i+1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK;
606                    break;
607                }
608            }
609        }
610
611        return levelBefore < levelAt;
612    }
613
614    /**
615     * Get the primary horizontal position for the specified text offset.
616     * This is the location where a new character would be inserted in
617     * the paragraph's primary direction.
618     */
619    public float getPrimaryHorizontal(int offset) {
620        boolean trailing = primaryIsTrailingPrevious(offset);
621        return getHorizontal(offset, trailing);
622    }
623
624    /**
625     * Get the secondary horizontal position for the specified text offset.
626     * This is the location where a new character would be inserted in
627     * the direction other than the paragraph's primary direction.
628     */
629    public float getSecondaryHorizontal(int offset) {
630        boolean trailing = primaryIsTrailingPrevious(offset);
631        return getHorizontal(offset, !trailing);
632    }
633
634    private float getHorizontal(int offset, boolean trailing) {
635        int line = getLineForOffset(offset);
636
637        return getHorizontal(offset, trailing, line);
638    }
639
640    private float getHorizontal(int offset, boolean trailing, int line) {
641        int start = getLineStart(line);
642        int end = getLineVisibleEnd(line);
643        int dir = getParagraphDirection(line);
644        boolean tab = getLineContainsTab(line);
645        Directions directions = getLineDirections(line);
646
647        TabStopSpan[] tabs = null;
648        if (tab && mText instanceof Spanned) {
649            tabs = ((Spanned) mText).getSpans(start, end, TabStopSpan.class);
650        }
651
652        float wid = measureText(mPaint, mWorkPaint, mText, start, offset, end,
653                                dir, directions, trailing, tab, tabs);
654
655        if (offset > end) {
656            if (dir == DIR_RIGHT_TO_LEFT)
657                wid -= measureText(mPaint, mWorkPaint,
658                                   mText, end, offset, null, tab, tabs);
659            else
660                wid += measureText(mPaint, mWorkPaint,
661                                   mText, end, offset, null, tab, tabs);
662        }
663
664        Alignment align = getParagraphAlignment(line);
665        int left = getParagraphLeft(line);
666        int right = getParagraphRight(line);
667
668        if (align == Alignment.ALIGN_NORMAL) {
669            if (dir == DIR_RIGHT_TO_LEFT)
670                return right + wid;
671            else
672                return left + wid;
673        }
674
675        float max = getLineMax(line);
676
677        if (align == Alignment.ALIGN_OPPOSITE) {
678            if (dir == DIR_RIGHT_TO_LEFT)
679                return left + max + wid;
680            else
681                return right - max + wid;
682        } else { /* align == Alignment.ALIGN_CENTER */
683            int imax = ((int) max) & ~1;
684
685            if (dir == DIR_RIGHT_TO_LEFT)
686                return right - (((right - left) - imax) / 2) + wid;
687            else
688                return left + ((right - left) - imax) / 2 + wid;
689        }
690    }
691
692    /**
693     * Get the leftmost position that should be exposed for horizontal
694     * scrolling on the specified line.
695     */
696    public float getLineLeft(int line) {
697        int dir = getParagraphDirection(line);
698        Alignment align = getParagraphAlignment(line);
699
700        if (align == Alignment.ALIGN_NORMAL) {
701            if (dir == DIR_RIGHT_TO_LEFT)
702                return getParagraphRight(line) - getLineMax(line);
703            else
704                return 0;
705        } else if (align == Alignment.ALIGN_OPPOSITE) {
706            if (dir == DIR_RIGHT_TO_LEFT)
707                return 0;
708            else
709                return mWidth - getLineMax(line);
710        } else { /* align == Alignment.ALIGN_CENTER */
711            int left = getParagraphLeft(line);
712            int right = getParagraphRight(line);
713            int max = ((int) getLineMax(line)) & ~1;
714
715            return left + ((right - left) - max) / 2;
716        }
717    }
718
719    /**
720     * Get the rightmost position that should be exposed for horizontal
721     * scrolling on the specified line.
722     */
723    public float getLineRight(int line) {
724        int dir = getParagraphDirection(line);
725        Alignment align = getParagraphAlignment(line);
726
727        if (align == Alignment.ALIGN_NORMAL) {
728            if (dir == DIR_RIGHT_TO_LEFT)
729                return mWidth;
730            else
731                return getParagraphLeft(line) + getLineMax(line);
732        } else if (align == Alignment.ALIGN_OPPOSITE) {
733            if (dir == DIR_RIGHT_TO_LEFT)
734                return getLineMax(line);
735            else
736                return mWidth;
737        } else { /* align == Alignment.ALIGN_CENTER */
738            int left = getParagraphLeft(line);
739            int right = getParagraphRight(line);
740            int max = ((int) getLineMax(line)) & ~1;
741
742            return right - ((right - left) - max) / 2;
743        }
744    }
745
746    /**
747     * Gets the horizontal extent of the specified line, excluding
748     * trailing whitespace.
749     */
750    public float getLineMax(int line) {
751        return getLineMax(line, null, false);
752    }
753
754    /**
755     * Gets the horizontal extent of the specified line, including
756     * trailing whitespace.
757     */
758    public float getLineWidth(int line) {
759        return getLineMax(line, null, true);
760    }
761
762    private float getLineMax(int line, Object[] tabs, boolean full) {
763        int start = getLineStart(line);
764        int end;
765
766        if (full) {
767            end = getLineEnd(line);
768        } else {
769            end = getLineVisibleEnd(line);
770        }
771        boolean tab = getLineContainsTab(line);
772
773        if (tabs == null && tab && mText instanceof Spanned) {
774            tabs = ((Spanned) mText).getSpans(start, end, TabStopSpan.class);
775        }
776
777        return measureText(mPaint, mWorkPaint,
778                           mText, start, end, null, tab, tabs);
779    }
780
781    /**
782     * Get the line number corresponding to the specified vertical position.
783     * If you ask for a position above 0, you get 0; if you ask for a position
784     * below the bottom of the text, you get the last line.
785     */
786    // FIXME: It may be faster to do a linear search for layouts without many lines.
787    public int getLineForVertical(int vertical) {
788        int high = getLineCount(), low = -1, guess;
789
790        while (high - low > 1) {
791            guess = (high + low) / 2;
792
793            if (getLineTop(guess) > vertical)
794                high = guess;
795            else
796                low = guess;
797        }
798
799        if (low < 0)
800            return 0;
801        else
802            return low;
803    }
804
805    /**
806     * Get the line number on which the specified text offset appears.
807     * If you ask for a position before 0, you get 0; if you ask for a position
808     * beyond the end of the text, you get the last line.
809     */
810    public int getLineForOffset(int offset) {
811        int high = getLineCount(), low = -1, guess;
812
813        while (high - low > 1) {
814            guess = (high + low) / 2;
815
816            if (getLineStart(guess) > offset)
817                high = guess;
818            else
819                low = guess;
820        }
821
822        if (low < 0)
823            return 0;
824        else
825            return low;
826    }
827
828    /**
829     * Get the character offset on the specified line whose position is
830     * closest to the specified horizontal position.
831     */
832    public int getOffsetForHorizontal(int line, float horiz) {
833        int max = getLineEnd(line) - 1;
834        int min = getLineStart(line);
835        Directions dirs = getLineDirections(line);
836
837        if (line == getLineCount() - 1)
838            max++;
839
840        int best = min;
841        float bestdist = Math.abs(getPrimaryHorizontal(best) - horiz);
842
843        for (int i = 0; i < dirs.mDirections.length; i += 2) {
844            int here = min + dirs.mDirections[i];
845            int there = here + (dirs.mDirections[i+1] & RUN_LENGTH_MASK);
846            int swap = (dirs.mDirections[i+1] & RUN_RTL_FLAG) != 0 ? -1 : 1;
847
848            if (there > max)
849                there = max;
850            int high = there - 1 + 1, low = here + 1 - 1, guess;
851
852            while (high - low > 1) {
853                guess = (high + low) / 2;
854                int adguess = getOffsetAtStartOf(guess);
855
856                if (getPrimaryHorizontal(adguess) * swap >= horiz * swap)
857                    high = guess;
858                else
859                    low = guess;
860            }
861
862            if (low < here + 1)
863                low = here + 1;
864
865            if (low < there) {
866                low = getOffsetAtStartOf(low);
867
868                float dist = Math.abs(getPrimaryHorizontal(low) - horiz);
869
870                int aft = TextUtils.getOffsetAfter(mText, low);
871                if (aft < there) {
872                    float other = Math.abs(getPrimaryHorizontal(aft) - horiz);
873
874                    if (other < dist) {
875                        dist = other;
876                        low = aft;
877                    }
878                }
879
880                if (dist < bestdist) {
881                    bestdist = dist;
882                    best = low;
883                }
884            }
885
886            float dist = Math.abs(getPrimaryHorizontal(here) - horiz);
887
888            if (dist < bestdist) {
889                bestdist = dist;
890                best = here;
891            }
892        }
893
894        float dist = Math.abs(getPrimaryHorizontal(max) - horiz);
895
896        if (dist < bestdist) {
897            bestdist = dist;
898            best = max;
899        }
900
901        return best;
902    }
903
904    /**
905     * Return the text offset after the last character on the specified line.
906     */
907    public final int getLineEnd(int line) {
908        return getLineStart(line + 1);
909    }
910
911    /**
912     * Return the text offset after the last visible character (so whitespace
913     * is not counted) on the specified line.
914     */
915    public int getLineVisibleEnd(int line) {
916        return getLineVisibleEnd(line, getLineStart(line), getLineStart(line+1));
917    }
918
919    private int getLineVisibleEnd(int line, int start, int end) {
920        if (DEBUG) {
921            Assert.assertTrue(getLineStart(line) == start && getLineStart(line+1) == end);
922        }
923
924        CharSequence text = mText;
925        char ch;
926        if (line == getLineCount() - 1) {
927            return end;
928        }
929
930        for (; end > start; end--) {
931            ch = text.charAt(end - 1);
932
933            if (ch == '\n') {
934                return end - 1;
935            }
936
937            if (ch != ' ' && ch != '\t') {
938                break;
939            }
940
941        }
942
943        return end;
944    }
945
946    /**
947     * Return the vertical position of the bottom of the specified line.
948     */
949    public final int getLineBottom(int line) {
950        return getLineTop(line + 1);
951    }
952
953    /**
954     * Return the vertical position of the baseline of the specified line.
955     */
956    public final int getLineBaseline(int line) {
957        // getLineTop(line+1) == getLineTop(line)
958        return getLineTop(line+1) - getLineDescent(line);
959    }
960
961    /**
962     * Get the ascent of the text on the specified line.
963     * The return value is negative to match the Paint.ascent() convention.
964     */
965    public final int getLineAscent(int line) {
966        // getLineTop(line+1) - getLineDescent(line) == getLineBaseLine(line)
967        return getLineTop(line) - (getLineTop(line+1) - getLineDescent(line));
968    }
969
970    public int getOffsetToLeftOf(int offset) {
971        return getOffsetToLeftRightOf(offset, true);
972    }
973
974    public int getOffsetToRightOf(int offset) {
975        return getOffsetToLeftRightOf(offset, false);
976    }
977
978    // 1) The caret marks the leading edge of a character. The character
979    // logically before it might be on a different level, and the active caret
980    // position is on the character at the lower level. If that character
981    // was the previous character, the caret is on its trailing edge.
982    // 2) Take this character/edge and move it in the indicated direction.
983    // This gives you a new character and a new edge.
984    // 3) This position is between two visually adjacent characters.  One of
985    // these might be at a lower level.  The active position is on the
986    // character at the lower level.
987    // 4) If the active position is on the trailing edge of the character,
988    // the new caret position is the following logical character, else it
989    // is the character.
990    private int getOffsetToLeftRightOf(int caret, boolean toLeft) {
991        int line = getLineForOffset(caret);
992        int lineStart = getLineStart(line);
993        int lineEnd = getLineEnd(line);
994
995        boolean paraIsRtl = getParagraphDirection(line) == -1;
996        int[] runs = getLineDirections(line).mDirections;
997
998        int runIndex, runLevel = 0, runStart = lineStart, runLimit = lineEnd, newCaret = -1;
999        boolean trailing = false;
1000
1001        if (caret == lineStart) {
1002            runIndex = -2;
1003        } else if (caret == lineEnd) {
1004            runIndex = runs.length;
1005        } else {
1006          // First, get information about the run containing the character with
1007          // the active caret.
1008          for (runIndex = 0; runIndex < runs.length; runIndex += 2) {
1009            runStart = lineStart + runs[runIndex];
1010            if (caret >= runStart) {
1011              runLimit = runStart + (runs[runIndex+1] & RUN_LENGTH_MASK);
1012              if (runLimit > lineEnd) {
1013                  runLimit = lineEnd;
1014              }
1015              if (caret < runLimit) {
1016                runLevel = (runs[runIndex+1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK;
1017                if (caret == runStart) {
1018                  // The caret is on a run boundary, see if we should
1019                  // use the position on the trailing edge of the previous
1020                  // logical character instead.
1021                  int prevRunIndex, prevRunLevel, prevRunStart, prevRunLimit;
1022                  int pos = caret - 1;
1023                  for (prevRunIndex = 0; prevRunIndex < runs.length; prevRunIndex += 2) {
1024                    prevRunStart = lineStart + runs[prevRunIndex];
1025                    if (pos >= prevRunStart) {
1026                      prevRunLimit = prevRunStart + (runs[prevRunIndex+1] & RUN_LENGTH_MASK);
1027                      if (prevRunLimit > lineEnd) {
1028                          prevRunLimit = lineEnd;
1029                      }
1030                      if (pos < prevRunLimit) {
1031                        prevRunLevel = (runs[prevRunIndex+1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK;
1032                        if (prevRunLevel < runLevel) {
1033                          // Start from logically previous character.
1034                          runIndex = prevRunIndex;
1035                          runLevel = prevRunLevel;
1036                          runStart = prevRunStart;
1037                          runLimit = prevRunLimit;
1038                          trailing = true;
1039                          break;
1040                        }
1041                      }
1042                    }
1043                  }
1044                }
1045                break;
1046              }
1047            }
1048          }
1049
1050          // caret might be = lineEnd.  This is generally a space or paragraph
1051          // separator and has an associated run, but might be the end of
1052          // text, in which case it doesn't.  If that happens, we ran off the
1053          // end of the run list, and runIndex == runs.length.  In this case,
1054          // we are at a run boundary so we skip the below test.
1055          if (runIndex != runs.length) {
1056              boolean rtlRun = (runLevel & 0x1) != 0;
1057              boolean advance = toLeft == rtlRun;
1058              if (caret != (advance ? runLimit : runStart) || advance != trailing) {
1059                  // Moving within or into the run, so we can move logically.
1060                  newCaret = getOffsetBeforeAfter(caret, advance);
1061                  // If the new position is internal to the run, we're at the strong
1062                  // position already so we're finished.
1063                  if (newCaret != (advance ? runLimit : runStart)) {
1064                      return newCaret;
1065                  }
1066              }
1067          }
1068        }
1069
1070        // If newCaret is -1, we're starting at a run boundary and crossing
1071        // into another run. Otherwise we've arrived at a run boundary, and
1072        // need to figure out which character to attach to.  Note we might
1073        // need to run this twice, if we cross a run boundary and end up at
1074        // another run boundary.
1075        while (true) {
1076          boolean advance = toLeft == paraIsRtl;
1077          int otherRunIndex = runIndex + (advance ? 2 : -2);
1078          if (otherRunIndex >= 0 && otherRunIndex < runs.length) {
1079            int otherRunStart = lineStart + runs[otherRunIndex];
1080            int otherRunLimit = otherRunStart + (runs[otherRunIndex+1] & RUN_LENGTH_MASK);
1081            if (otherRunLimit > lineEnd) {
1082                otherRunLimit = lineEnd;
1083            }
1084            int otherRunLevel = runs[otherRunIndex+1] >>> RUN_LEVEL_SHIFT & RUN_LEVEL_MASK;
1085            boolean otherRunIsRtl = (otherRunLevel & 1) != 0;
1086
1087            advance = toLeft == otherRunIsRtl;
1088            if (newCaret == -1) {
1089              newCaret = getOffsetBeforeAfter(advance ? otherRunStart : otherRunLimit, advance);
1090              if (newCaret == (advance ? otherRunLimit : otherRunStart)) {
1091                // Crossed and ended up at a new boundary, repeat a second and final time.
1092                runIndex = otherRunIndex;
1093                runLevel = otherRunLevel;
1094                continue;
1095              }
1096              break;
1097            }
1098
1099            // The new caret is at a boundary.
1100            if (otherRunLevel < runLevel) {
1101              // The strong character is in the other run.
1102              newCaret = advance ? otherRunStart : otherRunLimit;
1103            }
1104            break;
1105          }
1106
1107          if (newCaret == -1) {
1108              // We're walking off the end of the line.  The paragraph
1109              // level is always equal to or lower than any internal level, so
1110              // the boundaries get the strong caret.
1111              newCaret = getOffsetBeforeAfter(caret, advance);
1112              break;
1113          }
1114          // Else we've arrived at the end of the line.  That's a strong position.
1115          // We might have arrived here by crossing over a run with no internal
1116          // breaks and dropping out of the above loop before advancing one final
1117          // time, so reset the caret.
1118          // Note, we use '<=' below to handle a situation where the only run
1119          // on the line is a counter-directional run.  If we're not advancing,
1120          // we can end up at the 'lineEnd' position but the caret we want is at
1121          // the lineStart.
1122          if (newCaret <= lineEnd) {
1123              newCaret = advance ? lineEnd : lineStart;
1124          }
1125          break;
1126        }
1127
1128        return newCaret;
1129    }
1130
1131    // utility, maybe just roll into the above.
1132    private int getOffsetBeforeAfter(int offset, boolean after) {
1133        if (after) {
1134            return TextUtils.getOffsetAfter(mText, offset);
1135        }
1136        return TextUtils.getOffsetBefore(mText, offset);
1137    }
1138
1139    private int getOffsetAtStartOf(int offset) {
1140        // XXX this probably should skip local reorderings and
1141        // zero-width characters, look at callers
1142        if (offset == 0)
1143            return 0;
1144
1145        CharSequence text = mText;
1146        char c = text.charAt(offset);
1147
1148        if (c >= '\uDC00' && c <= '\uDFFF') {
1149            char c1 = text.charAt(offset - 1);
1150
1151            if (c1 >= '\uD800' && c1 <= '\uDBFF')
1152                offset -= 1;
1153        }
1154
1155        if (mSpannedText) {
1156            ReplacementSpan[] spans = ((Spanned) text).getSpans(offset, offset,
1157                                                       ReplacementSpan.class);
1158
1159            for (int i = 0; i < spans.length; i++) {
1160                int start = ((Spanned) text).getSpanStart(spans[i]);
1161                int end = ((Spanned) text).getSpanEnd(spans[i]);
1162
1163                if (start < offset && end > offset)
1164                    offset = start;
1165            }
1166        }
1167
1168        return offset;
1169    }
1170
1171    /**
1172     * Fills in the specified Path with a representation of a cursor
1173     * at the specified offset.  This will often be a vertical line
1174     * but can be multiple discontinuous lines in text with multiple
1175     * directionalities.
1176     */
1177    public void getCursorPath(int point, Path dest,
1178                              CharSequence editingBuffer) {
1179        dest.reset();
1180
1181        int line = getLineForOffset(point);
1182        int top = getLineTop(line);
1183        int bottom = getLineTop(line+1);
1184
1185        float h1 = getPrimaryHorizontal(point) - 0.5f;
1186        float h2 = isLevelBoundary(point) ?
1187                    getSecondaryHorizontal(point) - 0.5f : h1;
1188
1189        int caps = TextKeyListener.getMetaState(editingBuffer,
1190                                                KeyEvent.META_SHIFT_ON) |
1191                   TextKeyListener.getMetaState(editingBuffer,
1192                                                TextKeyListener.META_SELECTING);
1193        int fn = TextKeyListener.getMetaState(editingBuffer,
1194                                              KeyEvent.META_ALT_ON);
1195        int dist = 0;
1196
1197        if (caps != 0 || fn != 0) {
1198            dist = (bottom - top) >> 2;
1199
1200            if (fn != 0)
1201                top += dist;
1202            if (caps != 0)
1203                bottom -= dist;
1204        }
1205
1206        if (h1 < 0.5f)
1207            h1 = 0.5f;
1208        if (h2 < 0.5f)
1209            h2 = 0.5f;
1210
1211        if (h1 == h2) {
1212            dest.moveTo(h1, top);
1213            dest.lineTo(h1, bottom);
1214        } else {
1215            dest.moveTo(h1, top);
1216            dest.lineTo(h1, (top + bottom) >> 1);
1217
1218            dest.moveTo(h2, (top + bottom) >> 1);
1219            dest.lineTo(h2, bottom);
1220        }
1221
1222        if (caps == 2) {
1223            dest.moveTo(h2, bottom);
1224            dest.lineTo(h2 - dist, bottom + dist);
1225            dest.lineTo(h2, bottom);
1226            dest.lineTo(h2 + dist, bottom + dist);
1227        } else if (caps == 1) {
1228            dest.moveTo(h2, bottom);
1229            dest.lineTo(h2 - dist, bottom + dist);
1230
1231            dest.moveTo(h2 - dist, bottom + dist - 0.5f);
1232            dest.lineTo(h2 + dist, bottom + dist - 0.5f);
1233
1234            dest.moveTo(h2 + dist, bottom + dist);
1235            dest.lineTo(h2, bottom);
1236        }
1237
1238        if (fn == 2) {
1239            dest.moveTo(h1, top);
1240            dest.lineTo(h1 - dist, top - dist);
1241            dest.lineTo(h1, top);
1242            dest.lineTo(h1 + dist, top - dist);
1243        } else if (fn == 1) {
1244            dest.moveTo(h1, top);
1245            dest.lineTo(h1 - dist, top - dist);
1246
1247            dest.moveTo(h1 - dist, top - dist + 0.5f);
1248            dest.lineTo(h1 + dist, top - dist + 0.5f);
1249
1250            dest.moveTo(h1 + dist, top - dist);
1251            dest.lineTo(h1, top);
1252        }
1253    }
1254
1255    private void addSelection(int line, int start, int end,
1256                              int top, int bottom, Path dest) {
1257        int linestart = getLineStart(line);
1258        int lineend = getLineEnd(line);
1259        Directions dirs = getLineDirections(line);
1260
1261        if (lineend > linestart && mText.charAt(lineend - 1) == '\n')
1262            lineend--;
1263
1264        for (int i = 0; i < dirs.mDirections.length; i += 2) {
1265            int here = linestart + dirs.mDirections[i];
1266            int there = here + (dirs.mDirections[i+1] & RUN_LENGTH_MASK);
1267
1268            if (there > lineend)
1269                there = lineend;
1270
1271            if (start <= there && end >= here) {
1272                int st = Math.max(start, here);
1273                int en = Math.min(end, there);
1274
1275                if (st != en) {
1276                    float h1 = getHorizontal(st, false, line);
1277                    float h2 = getHorizontal(en, true, line);
1278
1279                    dest.addRect(h1, top, h2, bottom, Path.Direction.CW);
1280                }
1281            }
1282        }
1283    }
1284
1285    /**
1286     * Fills in the specified Path with a representation of a highlight
1287     * between the specified offsets.  This will often be a rectangle
1288     * or a potentially discontinuous set of rectangles.  If the start
1289     * and end are the same, the returned path is empty.
1290     */
1291    public void getSelectionPath(int start, int end, Path dest) {
1292        dest.reset();
1293
1294        if (start == end)
1295            return;
1296
1297        if (end < start) {
1298            int temp = end;
1299            end = start;
1300            start = temp;
1301        }
1302
1303        int startline = getLineForOffset(start);
1304        int endline = getLineForOffset(end);
1305
1306        int top = getLineTop(startline);
1307        int bottom = getLineBottom(endline);
1308
1309        if (startline == endline) {
1310            addSelection(startline, start, end, top, bottom, dest);
1311        } else {
1312            final float width = mWidth;
1313
1314            addSelection(startline, start, getLineEnd(startline),
1315                         top, getLineBottom(startline), dest);
1316
1317            if (getParagraphDirection(startline) == DIR_RIGHT_TO_LEFT)
1318                dest.addRect(getLineLeft(startline), top,
1319                              0, getLineBottom(startline), Path.Direction.CW);
1320            else
1321                dest.addRect(getLineRight(startline), top,
1322                              width, getLineBottom(startline), Path.Direction.CW);
1323
1324            for (int i = startline + 1; i < endline; i++) {
1325                top = getLineTop(i);
1326                bottom = getLineBottom(i);
1327                dest.addRect(0, top, width, bottom, Path.Direction.CW);
1328            }
1329
1330            top = getLineTop(endline);
1331            bottom = getLineBottom(endline);
1332
1333            addSelection(endline, getLineStart(endline), end,
1334                         top, bottom, dest);
1335
1336            if (getParagraphDirection(endline) == DIR_RIGHT_TO_LEFT)
1337                dest.addRect(width, top, getLineRight(endline), bottom, Path.Direction.CW);
1338            else
1339                dest.addRect(0, top, getLineLeft(endline), bottom, Path.Direction.CW);
1340        }
1341    }
1342
1343    /**
1344     * Get the alignment of the specified paragraph, taking into account
1345     * markup attached to it.
1346     */
1347    public final Alignment getParagraphAlignment(int line) {
1348        Alignment align = mAlignment;
1349
1350        if (mSpannedText) {
1351            Spanned sp = (Spanned) mText;
1352            AlignmentSpan[] spans = sp.getSpans(getLineStart(line),
1353                                                getLineEnd(line),
1354                                                AlignmentSpan.class);
1355
1356            int spanLength = spans.length;
1357            if (spanLength > 0) {
1358                align = spans[spanLength-1].getAlignment();
1359            }
1360        }
1361
1362        return align;
1363    }
1364
1365    /**
1366     * Get the left edge of the specified paragraph, inset by left margins.
1367     */
1368    public final int getParagraphLeft(int line) {
1369        int dir = getParagraphDirection(line);
1370
1371        int left = 0;
1372
1373        boolean par = false;
1374        int off = getLineStart(line);
1375        if (off == 0 || mText.charAt(off - 1) == '\n')
1376            par = true;
1377
1378        if (dir == DIR_LEFT_TO_RIGHT) {
1379            if (mSpannedText) {
1380                Spanned sp = (Spanned) mText;
1381                LeadingMarginSpan[] spans = sp.getSpans(getLineStart(line),
1382                                                        getLineEnd(line),
1383                                                        LeadingMarginSpan.class);
1384
1385                for (int i = 0; i < spans.length; i++) {
1386                    boolean margin = par;
1387                    LeadingMarginSpan span = spans[i];
1388                    if (span instanceof LeadingMarginSpan.LeadingMarginSpan2) {
1389                        int count = ((LeadingMarginSpan.LeadingMarginSpan2)span).getLeadingMarginLineCount();
1390                        margin = count >= line;
1391                    }
1392                    left += span.getLeadingMargin(margin);
1393                }
1394            }
1395        }
1396
1397        return left;
1398    }
1399
1400    /**
1401     * Get the right edge of the specified paragraph, inset by right margins.
1402     */
1403    public final int getParagraphRight(int line) {
1404        int dir = getParagraphDirection(line);
1405
1406        int right = mWidth;
1407
1408        boolean par = false;
1409        int off = getLineStart(line);
1410        if (off == 0 || mText.charAt(off - 1) == '\n')
1411            par = true;
1412
1413
1414        if (dir == DIR_RIGHT_TO_LEFT) {
1415            if (mSpannedText) {
1416                Spanned sp = (Spanned) mText;
1417                LeadingMarginSpan[] spans = sp.getSpans(getLineStart(line),
1418                                                        getLineEnd(line),
1419                                                        LeadingMarginSpan.class);
1420
1421                for (int i = 0; i < spans.length; i++) {
1422                    right -= spans[i].getLeadingMargin(par);
1423                }
1424            }
1425        }
1426
1427        return right;
1428    }
1429
1430    private void drawText(Canvas canvas,
1431                                 CharSequence text, int start, int end,
1432                                 int dir, Directions directions,
1433                                 float x, int top, int y, int bottom,
1434                                 TextPaint paint,
1435                                 TextPaint workPaint,
1436                                 boolean hasTabs, Object[] parspans) {
1437        char[] buf;
1438        if (!hasTabs) {
1439            if (directions == DIRS_ALL_LEFT_TO_RIGHT) {
1440                if (DEBUG) {
1441                    Assert.assertTrue(DIR_LEFT_TO_RIGHT == dir);
1442                }
1443                Styled.drawText(canvas, text, start, end, dir, false, x, top, y, bottom, paint, workPaint, false);
1444                return;
1445            }
1446            buf = null;
1447        } else {
1448            buf = TextUtils.obtain(end - start);
1449            TextUtils.getChars(text, start, end, buf, 0);
1450        }
1451
1452        float h = 0;
1453
1454        int lastRunIndex = directions.mDirections.length - 2;
1455        for (int i = 0; i < directions.mDirections.length; i += 2) {
1456            int here = start + directions.mDirections[i];
1457            int there = here + (directions.mDirections[i+1] & RUN_LENGTH_MASK);
1458            boolean runIsRtl = (directions.mDirections[i+1] & RUN_RTL_FLAG) != 0;
1459
1460            if (there > end)
1461                there = end;
1462
1463            int segstart = here;
1464            for (int j = hasTabs ? here : there; j <= there; j++) {
1465                int pj = j - start;
1466                if (j == there || buf[pj] == '\t') {
1467                    h += Styled.drawText(canvas, text,
1468                                         segstart, j,
1469                                         dir, runIsRtl, x + h,
1470                                         top, y, bottom, paint, workPaint,
1471                                         i != lastRunIndex || j != end);
1472
1473                    if (j != there)
1474                        h = dir * nextTab(text, start, end, h * dir, parspans);
1475
1476                    segstart = j + 1;
1477                } else if (hasTabs && buf[pj] >= 0xD800 && buf[pj] <= 0xDFFF && j + 1 < there) {
1478                    int emoji = Character.codePointAt(buf, pj);
1479
1480                    if (emoji >= MIN_EMOJI && emoji <= MAX_EMOJI) {
1481                        Bitmap bm = EMOJI_FACTORY.
1482                            getBitmapFromAndroidPua(emoji);
1483
1484                        if (bm != null) {
1485                            h += Styled.drawText(canvas, text,
1486                                                 segstart, j,
1487                                                 dir, runIsRtl, x + h,
1488                                                 top, y, bottom, paint, workPaint,
1489                                                 i != lastRunIndex || j != end);
1490
1491                            if (mEmojiRect == null) {
1492                                mEmojiRect = new RectF();
1493                            }
1494
1495                            workPaint.set(paint);
1496                            Styled.measureText(paint, workPaint, text,
1497                                               j, j + 1,
1498                                               null);
1499
1500                            float bitmapHeight = bm.getHeight();
1501                            float textHeight = -workPaint.ascent();
1502                            float scale = textHeight / bitmapHeight;
1503                            float width = bm.getWidth() * scale;
1504
1505                            mEmojiRect.set(x + h, y - textHeight,
1506                                           x + h + width, y);
1507
1508                            canvas.drawBitmap(bm, null, mEmojiRect, paint);
1509                            h += width;
1510
1511                            j++;
1512                            segstart = j + 1;
1513                        }
1514                    }
1515                }
1516            }
1517        }
1518
1519        if (hasTabs)
1520            TextUtils.recycle(buf);
1521    }
1522
1523    /**
1524     * Get the distance from the margin to the requested edge of the character
1525     * at offset on the line from start to end.  Trailing indicates the edge
1526     * should be the trailing edge of the character at offset-1.
1527     */
1528    /* package */ static float measureText(TextPaint paint,
1529                                     TextPaint workPaint,
1530                                     CharSequence text,
1531                                     int start, int offset, int end,
1532                                     int dir, Directions directions,
1533                                     boolean trailing, boolean hasTabs,
1534                                     Object[] tabs) {
1535        char[] buf = null;
1536
1537        if (hasTabs) {
1538            buf = TextUtils.obtain(end - start);
1539            TextUtils.getChars(text, start, end, buf, 0);
1540        }
1541
1542        float h = 0;
1543
1544        int target = trailing ? offset - 1 : offset;
1545        if (target < start) {
1546            return 0;
1547        }
1548
1549        int[] runs = directions.mDirections;
1550        for (int i = 0; i < runs.length; i += 2) {
1551            int here = start + runs[i];
1552            int there = here + (runs[i+1] & RUN_LENGTH_MASK);
1553            if (there > end) {
1554                there = end;
1555            }
1556            boolean runIsRtl = (runs[i+1] & RUN_RTL_FLAG) != 0;
1557
1558            int segstart = here;
1559            for (int j = hasTabs ? here : there; j <= there; j++) {
1560                int codept = 0;
1561                Bitmap bm = null;
1562
1563                if (hasTabs && j < there) {
1564                    codept = buf[j - start];
1565                }
1566
1567                if (codept >= 0xD800 && codept <= 0xDFFF && j + 1 < there) {
1568                    codept = Character.codePointAt(buf, j - start);
1569
1570                    if (codept >= MIN_EMOJI && codept <= MAX_EMOJI) {
1571                        bm = EMOJI_FACTORY.getBitmapFromAndroidPua(codept);
1572                    }
1573                }
1574
1575                if (j == there || codept == '\t' || bm != null) {
1576                    float segw;
1577
1578                    boolean inSegment = target >= segstart && target < j;
1579
1580                    if (inSegment) {
1581                        if (dir == DIR_LEFT_TO_RIGHT && !runIsRtl) {
1582                            h += Styled.measureText(paint, workPaint, text,
1583                                                    segstart, offset,
1584                                                    null);
1585                            return h;
1586                        }
1587
1588                        if (dir == DIR_RIGHT_TO_LEFT && runIsRtl) {
1589                            h -= Styled.measureText(paint, workPaint, text,
1590                                                    segstart, offset,
1591                                                    null);
1592                            return h;
1593                        }
1594                    }
1595
1596                    // XXX Style.measureText assumes LTR?
1597                    segw = Styled.measureText(paint, workPaint, text,
1598                                              segstart, j,
1599                                              null);
1600
1601                    if (inSegment) {
1602                        if (dir == DIR_LEFT_TO_RIGHT) {
1603                            h += segw - Styled.measureText(paint, workPaint,
1604                                                           text,
1605                                                           segstart,
1606                                                           offset, null);
1607                            return h;
1608                        }
1609
1610                        if (dir == DIR_RIGHT_TO_LEFT) {
1611                            h -= segw - Styled.measureText(paint, workPaint,
1612                                                           text,
1613                                                           segstart,
1614                                                           offset, null);
1615                            return h;
1616                        }
1617                    }
1618
1619                    if (dir == DIR_RIGHT_TO_LEFT)
1620                        h -= segw;
1621                    else
1622                        h += segw;
1623
1624                    if (j != there && buf[j - start] == '\t') {
1625                        if (offset == j)
1626                            return h;
1627
1628                        h = dir * nextTab(text, start, end, h * dir, tabs);
1629
1630                        if (target == j) {
1631                            return h;
1632                        }
1633                    }
1634
1635                    if (bm != null) {
1636                        workPaint.set(paint);
1637                        Styled.measureText(paint, workPaint, text,
1638                                           j, j + 2, null);
1639
1640                        float wid = bm.getWidth() *
1641                                    -workPaint.ascent() / bm.getHeight();
1642
1643                        if (dir == DIR_RIGHT_TO_LEFT) {
1644                            h -= wid;
1645                        } else {
1646                            h += wid;
1647                        }
1648
1649                        j++;
1650                    }
1651
1652                    segstart = j + 1;
1653                }
1654            }
1655        }
1656
1657        if (hasTabs)
1658            TextUtils.recycle(buf);
1659
1660        return h;
1661    }
1662
1663    /**
1664     * Measure width of a run of text on a single line that is known to all be
1665     * in the same direction as the paragraph base direction. Returns the width,
1666     * and the line metrics in fm if fm is not null.
1667     *
1668     * @param paint the paint for the text; will not be modified
1669     * @param workPaint paint available for modification
1670     * @param text text
1671     * @param start start of the line
1672     * @param end limit of the line
1673     * @param fm object to return integer metrics in, can be null
1674     * @param hasTabs true if it is known that the line has tabs
1675     * @param tabs tab position information
1676     * @return the width of the text from start to end
1677     */
1678    /* package */ static float measureText(TextPaint paint,
1679                                           TextPaint workPaint,
1680                                           CharSequence text,
1681                                           int start, int end,
1682                                           Paint.FontMetricsInt fm,
1683                                           boolean hasTabs, Object[] tabs) {
1684        char[] buf = null;
1685
1686        if (hasTabs) {
1687            buf = TextUtils.obtain(end - start);
1688            TextUtils.getChars(text, start, end, buf, 0);
1689        }
1690
1691        int len = end - start;
1692
1693        int lastPos = 0;
1694        float width = 0;
1695        int ascent = 0, descent = 0, top = 0, bottom = 0;
1696
1697        if (fm != null) {
1698            fm.ascent = 0;
1699            fm.descent = 0;
1700        }
1701
1702        for (int pos = hasTabs ? 0 : len; pos <= len; pos++) {
1703            int codept = 0;
1704            Bitmap bm = null;
1705
1706            if (hasTabs && pos < len) {
1707                codept = buf[pos];
1708            }
1709
1710            if (codept >= 0xD800 && codept <= 0xDFFF && pos < len) {
1711                codept = Character.codePointAt(buf, pos);
1712
1713                if (codept >= MIN_EMOJI && codept <= MAX_EMOJI) {
1714                    bm = EMOJI_FACTORY.getBitmapFromAndroidPua(codept);
1715                }
1716            }
1717
1718            if (pos == len || codept == '\t' || bm != null) {
1719                workPaint.baselineShift = 0;
1720
1721                // XXX Styled.measureText assumes the run direction is LTR,
1722                // but it might not be.  Check this.
1723                width += Styled.measureText(paint, workPaint, text,
1724                                        start + lastPos, start + pos,
1725                                        fm);
1726
1727                if (fm != null) {
1728                    if (workPaint.baselineShift < 0) {
1729                        fm.ascent += workPaint.baselineShift;
1730                        fm.top += workPaint.baselineShift;
1731                    } else {
1732                        fm.descent += workPaint.baselineShift;
1733                        fm.bottom += workPaint.baselineShift;
1734                    }
1735                }
1736
1737                if (pos != len) {
1738                    if (bm == null) {
1739                        // no emoji, must have hit a tab
1740                        width = nextTab(text, start, end, width, tabs);
1741                    } else {
1742                        // This sets up workPaint with the font on the emoji
1743                        // text, so that we can extract the ascent and scale.
1744
1745                        // We can't use the result of the previous call to
1746                        // measureText because the emoji might have its own style.
1747                        // We have to initialize workPaint here because if the
1748                        // text is unstyled measureText might not use workPaint
1749                        // at all.
1750                        workPaint.set(paint);
1751                        Styled.measureText(paint, workPaint, text,
1752                                           start + pos, start + pos + 1, null);
1753
1754                        width += bm.getWidth() *
1755                                    -workPaint.ascent() / bm.getHeight();
1756
1757                        // Since we had an emoji, we bump past the second half
1758                        // of the surrogate pair.
1759                        pos++;
1760                    }
1761                }
1762
1763                if (fm != null) {
1764                    if (fm.ascent < ascent) {
1765                        ascent = fm.ascent;
1766                    }
1767                    if (fm.descent > descent) {
1768                        descent = fm.descent;
1769                    }
1770
1771                    if (fm.top < top) {
1772                        top = fm.top;
1773                    }
1774                    if (fm.bottom > bottom) {
1775                        bottom = fm.bottom;
1776                    }
1777
1778                    // No need to take bitmap height into account here,
1779                    // since it is scaled to match the text height.
1780                }
1781
1782                lastPos = pos + 1;
1783            }
1784        }
1785
1786        if (fm != null) {
1787            fm.ascent = ascent;
1788            fm.descent = descent;
1789            fm.top = top;
1790            fm.bottom = bottom;
1791        }
1792
1793        if (hasTabs)
1794            TextUtils.recycle(buf);
1795
1796        return width;
1797    }
1798
1799    /**
1800     * Returns the position of the next tab stop after h on the line.
1801     *
1802     * @param text the text
1803     * @param start start of the line
1804     * @param end limit of the line
1805     * @param h the current horizontal offset
1806     * @param tabs the tabs, can be null.  If it is null, any tabs in effect
1807     * on the line will be used.  If there are no tabs, a default offset
1808     * will be used to compute the tab stop.
1809     * @return the offset of the next tab stop.
1810     */
1811    /* package */ static float nextTab(CharSequence text, int start, int end,
1812                                       float h, Object[] tabs) {
1813        float nh = Float.MAX_VALUE;
1814        boolean alltabs = false;
1815
1816        if (text instanceof Spanned) {
1817            if (tabs == null) {
1818                tabs = ((Spanned) text).getSpans(start, end, TabStopSpan.class);
1819                alltabs = true;
1820            }
1821
1822            for (int i = 0; i < tabs.length; i++) {
1823                if (!alltabs) {
1824                    if (!(tabs[i] instanceof TabStopSpan))
1825                        continue;
1826                }
1827
1828                int where = ((TabStopSpan) tabs[i]).getTabStop();
1829
1830                if (where < nh && where > h)
1831                    nh = where;
1832            }
1833
1834            if (nh != Float.MAX_VALUE)
1835                return nh;
1836        }
1837
1838        return ((int) ((h + TAB_INCREMENT) / TAB_INCREMENT)) * TAB_INCREMENT;
1839    }
1840
1841    protected final boolean isSpanned() {
1842        return mSpannedText;
1843    }
1844
1845    private void ellipsize(int start, int end, int line,
1846                           char[] dest, int destoff) {
1847        int ellipsisCount = getEllipsisCount(line);
1848
1849        if (ellipsisCount == 0) {
1850            return;
1851        }
1852
1853        int ellipsisStart = getEllipsisStart(line);
1854        int linestart = getLineStart(line);
1855
1856        for (int i = ellipsisStart; i < ellipsisStart + ellipsisCount; i++) {
1857            char c;
1858
1859            if (i == ellipsisStart) {
1860                c = '\u2026'; // ellipsis
1861            } else {
1862                c = '\uFEFF'; // 0-width space
1863            }
1864
1865            int a = i + linestart;
1866
1867            if (a >= start && a < end) {
1868                dest[destoff + a - start] = c;
1869            }
1870        }
1871    }
1872
1873    /**
1874     * Stores information about bidirectional (left-to-right or right-to-left)
1875     * text within the layout of a line.
1876     */
1877    public static class Directions {
1878        // Directions represents directional runs within a line of text.
1879        // Runs are pairs of ints listed in visual order, starting from the
1880        // leading margin.  The first int of each pair is the offset from
1881        // the first character of the line to the start of the run.  The
1882        // second int represents both the length and level of the run.
1883        // The length is in the lower bits, accessed by masking with
1884        // DIR_LENGTH_MASK.  The level is in the higher bits, accessed
1885        // by shifting by DIR_LEVEL_SHIFT and masking by DIR_LEVEL_MASK.
1886        // To simply test for an RTL direction, test the bit using
1887        // DIR_RTL_FLAG, if set then the direction is rtl.
1888
1889        /* package */ int[] mDirections;
1890        /* package */ Directions(int[] dirs) {
1891            mDirections = dirs;
1892        }
1893    }
1894
1895    /**
1896     * Return the offset of the first character to be ellipsized away,
1897     * relative to the start of the line.  (So 0 if the beginning of the
1898     * line is ellipsized, not getLineStart().)
1899     */
1900    public abstract int getEllipsisStart(int line);
1901    /**
1902     * Returns the number of characters to be ellipsized away, or 0 if
1903     * no ellipsis is to take place.
1904     */
1905    public abstract int getEllipsisCount(int line);
1906
1907    /* package */ static class Ellipsizer implements CharSequence, GetChars {
1908        /* package */ CharSequence mText;
1909        /* package */ Layout mLayout;
1910        /* package */ int mWidth;
1911        /* package */ TextUtils.TruncateAt mMethod;
1912
1913        public Ellipsizer(CharSequence s) {
1914            mText = s;
1915        }
1916
1917        public char charAt(int off) {
1918            char[] buf = TextUtils.obtain(1);
1919            getChars(off, off + 1, buf, 0);
1920            char ret = buf[0];
1921
1922            TextUtils.recycle(buf);
1923            return ret;
1924        }
1925
1926        public void getChars(int start, int end, char[] dest, int destoff) {
1927            int line1 = mLayout.getLineForOffset(start);
1928            int line2 = mLayout.getLineForOffset(end);
1929
1930            TextUtils.getChars(mText, start, end, dest, destoff);
1931
1932            for (int i = line1; i <= line2; i++) {
1933                mLayout.ellipsize(start, end, i, dest, destoff);
1934            }
1935        }
1936
1937        public int length() {
1938            return mText.length();
1939        }
1940
1941        public CharSequence subSequence(int start, int end) {
1942            char[] s = new char[end - start];
1943            getChars(start, end, s, 0);
1944            return new String(s);
1945        }
1946
1947        public String toString() {
1948            char[] s = new char[length()];
1949            getChars(0, length(), s, 0);
1950            return new String(s);
1951        }
1952
1953    }
1954
1955    /* package */ static class SpannedEllipsizer
1956                    extends Ellipsizer implements Spanned {
1957        private Spanned mSpanned;
1958
1959        public SpannedEllipsizer(CharSequence display) {
1960            super(display);
1961            mSpanned = (Spanned) display;
1962        }
1963
1964        public <T> T[] getSpans(int start, int end, Class<T> type) {
1965            return mSpanned.getSpans(start, end, type);
1966        }
1967
1968        public int getSpanStart(Object tag) {
1969            return mSpanned.getSpanStart(tag);
1970        }
1971
1972        public int getSpanEnd(Object tag) {
1973            return mSpanned.getSpanEnd(tag);
1974        }
1975
1976        public int getSpanFlags(Object tag) {
1977            return mSpanned.getSpanFlags(tag);
1978        }
1979
1980        public int nextSpanTransition(int start, int limit, Class type) {
1981            return mSpanned.nextSpanTransition(start, limit, type);
1982        }
1983
1984        public CharSequence subSequence(int start, int end) {
1985            char[] s = new char[end - start];
1986            getChars(start, end, s, 0);
1987
1988            SpannableString ss = new SpannableString(new String(s));
1989            TextUtils.copySpansFrom(mSpanned, start, end, Object.class, ss, 0);
1990            return ss;
1991        }
1992    }
1993
1994    private CharSequence mText;
1995    private TextPaint mPaint;
1996    /* package */ TextPaint mWorkPaint;
1997    private int mWidth;
1998    private Alignment mAlignment = Alignment.ALIGN_NORMAL;
1999    private float mSpacingMult;
2000    private float mSpacingAdd;
2001    private static Rect sTempRect = new Rect();
2002    private boolean mSpannedText;
2003
2004    public static final int DIR_LEFT_TO_RIGHT = 1;
2005    public static final int DIR_RIGHT_TO_LEFT = -1;
2006
2007    /* package */ static final int DIR_REQUEST_LTR = 1;
2008    /* package */ static final int DIR_REQUEST_RTL = -1;
2009    /* package */ static final int DIR_REQUEST_DEFAULT_LTR = 2;
2010    /* package */ static final int DIR_REQUEST_DEFAULT_RTL = -2;
2011
2012    /* package */ static final int RUN_LENGTH_MASK = 0x03ffffff;
2013    /* package */ static final int RUN_LEVEL_SHIFT = 26;
2014    /* package */ static final int RUN_LEVEL_MASK = 0x3f;
2015    /* package */ static final int RUN_RTL_FLAG = 1 << RUN_LEVEL_SHIFT;
2016
2017    public enum Alignment {
2018        ALIGN_NORMAL,
2019        ALIGN_OPPOSITE,
2020        ALIGN_CENTER,
2021        // XXX ALIGN_LEFT,
2022        // XXX ALIGN_RIGHT,
2023    }
2024
2025    private static final int TAB_INCREMENT = 20;
2026
2027    /* package */ static final Directions DIRS_ALL_LEFT_TO_RIGHT =
2028        new Directions(new int[] { 0, RUN_LENGTH_MASK });
2029    /* package */ static final Directions DIRS_ALL_RIGHT_TO_LEFT =
2030        new Directions(new int[] { 0, RUN_LENGTH_MASK | RUN_RTL_FLAG });
2031}
2032
2033