Layout.java revision 9f7a4442b89cc06cb8cae6992484e7ae795323ab
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    // return the level of the character at offset.
534    // XXX remove if not needed
535    private int getRunLevelAtOffset(int offset) {
536        int line = getLineForOffset(offset);
537        int lineStart = getLineStart(line);
538        int lineEnd = getLineVisibleEnd(line);
539        int[] runs = getLineDirections(line).mDirections;
540        for (int i = 0; i < runs.length; i += 2) {
541            int start = runs[i];
542            if (offset >= start) {
543               int limit = start + (runs[i+1] & RUN_LENGTH_MASK);
544               if (limit > lineEnd) {
545                   limit = lineEnd;
546               }
547               if (offset < limit) {
548                   return (runs[i+1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK;
549               }
550            }
551        }
552        return getParagraphDirection(line) == 1 ? 0 : 1;
553    }
554
555    private boolean primaryIsTrailingPrevious(int offset) {
556        int line = getLineForOffset(offset);
557        int lineStart = getLineStart(line);
558        int lineEnd = getLineVisibleEnd(line);
559        int[] runs = getLineDirections(line).mDirections;
560
561        int levelAt = -1;
562        for (int i = 0; i < runs.length; i += 2) {
563            int start = lineStart + runs[i];
564            int limit = start + (runs[i+1] & RUN_LENGTH_MASK);
565            if (limit > lineEnd) {
566                limit = lineEnd;
567            }
568            if (offset >= start && offset < limit) {
569                if (offset > start) {
570                    // Previous character is at same level, so don't use trailing.
571                    return false;
572                }
573                levelAt = (runs[i+1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK;
574                break;
575            }
576        }
577        if (levelAt == -1) {
578            // Offset was limit of line.
579            levelAt = getParagraphDirection(line) == 1 ? 0 : 1;
580        }
581
582        // At level boundary, check previous level.
583        int levelBefore = -1;
584        if (offset == lineStart) {
585            levelBefore = getParagraphDirection(line) == 1 ? 0 : 1;
586        } else {
587            offset -= 1;
588            for (int i = 0; i < runs.length; i += 2) {
589                int start = lineStart + runs[i];
590                int limit = start + (runs[i+1] & RUN_LENGTH_MASK);
591                if (limit > lineEnd) {
592                    limit = lineEnd;
593                }
594                if (offset >= start && offset < limit) {
595                    levelBefore = (runs[i+1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK;
596                    break;
597                }
598            }
599        }
600
601        return levelBefore < levelAt;
602    }
603
604    /**
605     * Get the primary horizontal position for the specified text offset.
606     * This is the location where a new character would be inserted in
607     * the paragraph's primary direction.
608     */
609    public float getPrimaryHorizontal(int offset) {
610        boolean trailing = primaryIsTrailingPrevious(offset);
611        return getHorizontal(offset, trailing);
612    }
613
614    /**
615     * Get the secondary horizontal position for the specified text offset.
616     * This is the location where a new character would be inserted in
617     * the direction other than the paragraph's primary direction.
618     */
619    public float getSecondaryHorizontal(int offset) {
620        boolean trailing = primaryIsTrailingPrevious(offset);
621        return getHorizontal(offset, !trailing);
622    }
623
624    private float getHorizontal(int offset, boolean trailing) {
625        int line = getLineForOffset(offset);
626
627        return getHorizontal(offset, trailing, line);
628    }
629
630    private float getHorizontal(int offset, boolean trailing, int line) {
631        int start = getLineStart(line);
632        int end = getLineVisibleEnd(line);
633        int dir = getParagraphDirection(line);
634        boolean tab = getLineContainsTab(line);
635        Directions directions = getLineDirections(line);
636
637        TabStopSpan[] tabs = null;
638        if (tab && mText instanceof Spanned) {
639            tabs = ((Spanned) mText).getSpans(start, end, TabStopSpan.class);
640        }
641
642        float wid = measureText(mPaint, mWorkPaint, mText, start, offset, end,
643                                dir, directions, trailing, tab, tabs);
644
645        if (offset > end) {
646            if (dir == DIR_RIGHT_TO_LEFT)
647                wid -= measureText(mPaint, mWorkPaint,
648                                   mText, end, offset, null, tab, tabs);
649            else
650                wid += measureText(mPaint, mWorkPaint,
651                                   mText, end, offset, null, tab, tabs);
652        }
653
654        Alignment align = getParagraphAlignment(line);
655        int left = getParagraphLeft(line);
656        int right = getParagraphRight(line);
657
658        if (align == Alignment.ALIGN_NORMAL) {
659            if (dir == DIR_RIGHT_TO_LEFT)
660                return right + wid;
661            else
662                return left + wid;
663        }
664
665        float max = getLineMax(line);
666
667        if (align == Alignment.ALIGN_OPPOSITE) {
668            if (dir == DIR_RIGHT_TO_LEFT)
669                return left + max + wid;
670            else
671                return right - max + wid;
672        } else { /* align == Alignment.ALIGN_CENTER */
673            int imax = ((int) max) & ~1;
674
675            if (dir == DIR_RIGHT_TO_LEFT)
676                return right - (((right - left) - imax) / 2) + wid;
677            else
678                return left + ((right - left) - imax) / 2 + wid;
679        }
680    }
681
682    /**
683     * Get the leftmost position that should be exposed for horizontal
684     * scrolling on the specified line.
685     */
686    public float getLineLeft(int line) {
687        int dir = getParagraphDirection(line);
688        Alignment align = getParagraphAlignment(line);
689
690        if (align == Alignment.ALIGN_NORMAL) {
691            if (dir == DIR_RIGHT_TO_LEFT)
692                return getParagraphRight(line) - getLineMax(line);
693            else
694                return 0;
695        } else if (align == Alignment.ALIGN_OPPOSITE) {
696            if (dir == DIR_RIGHT_TO_LEFT)
697                return 0;
698            else
699                return mWidth - getLineMax(line);
700        } else { /* align == Alignment.ALIGN_CENTER */
701            int left = getParagraphLeft(line);
702            int right = getParagraphRight(line);
703            int max = ((int) getLineMax(line)) & ~1;
704
705            return left + ((right - left) - max) / 2;
706        }
707    }
708
709    /**
710     * Get the rightmost position that should be exposed for horizontal
711     * scrolling on the specified line.
712     */
713    public float getLineRight(int line) {
714        int dir = getParagraphDirection(line);
715        Alignment align = getParagraphAlignment(line);
716
717        if (align == Alignment.ALIGN_NORMAL) {
718            if (dir == DIR_RIGHT_TO_LEFT)
719                return mWidth;
720            else
721                return getParagraphLeft(line) + getLineMax(line);
722        } else if (align == Alignment.ALIGN_OPPOSITE) {
723            if (dir == DIR_RIGHT_TO_LEFT)
724                return getLineMax(line);
725            else
726                return mWidth;
727        } else { /* align == Alignment.ALIGN_CENTER */
728            int left = getParagraphLeft(line);
729            int right = getParagraphRight(line);
730            int max = ((int) getLineMax(line)) & ~1;
731
732            return right - ((right - left) - max) / 2;
733        }
734    }
735
736    /**
737     * Gets the horizontal extent of the specified line, excluding
738     * trailing whitespace.
739     */
740    public float getLineMax(int line) {
741        return getLineMax(line, null, false);
742    }
743
744    /**
745     * Gets the horizontal extent of the specified line, including
746     * trailing whitespace.
747     */
748    public float getLineWidth(int line) {
749        return getLineMax(line, null, true);
750    }
751
752    private float getLineMax(int line, Object[] tabs, boolean full) {
753        int start = getLineStart(line);
754        int end;
755
756        if (full) {
757            end = getLineEnd(line);
758        } else {
759            end = getLineVisibleEnd(line);
760        }
761        boolean tab = getLineContainsTab(line);
762
763        if (tabs == null && tab && mText instanceof Spanned) {
764            tabs = ((Spanned) mText).getSpans(start, end, TabStopSpan.class);
765        }
766
767        return measureText(mPaint, mWorkPaint,
768                           mText, start, end, null, tab, tabs);
769    }
770
771    /**
772     * Get the line number corresponding to the specified vertical position.
773     * If you ask for a position above 0, you get 0; if you ask for a position
774     * below the bottom of the text, you get the last line.
775     */
776    // FIXME: It may be faster to do a linear search for layouts without many lines.
777    public int getLineForVertical(int vertical) {
778        int high = getLineCount(), low = -1, guess;
779
780        while (high - low > 1) {
781            guess = (high + low) / 2;
782
783            if (getLineTop(guess) > vertical)
784                high = guess;
785            else
786                low = guess;
787        }
788
789        if (low < 0)
790            return 0;
791        else
792            return low;
793    }
794
795    /**
796     * Get the line number on which the specified text offset appears.
797     * If you ask for a position before 0, you get 0; if you ask for a position
798     * beyond the end of the text, you get the last line.
799     */
800    public int getLineForOffset(int offset) {
801        int high = getLineCount(), low = -1, guess;
802
803        while (high - low > 1) {
804            guess = (high + low) / 2;
805
806            if (getLineStart(guess) > offset)
807                high = guess;
808            else
809                low = guess;
810        }
811
812        if (low < 0)
813            return 0;
814        else
815            return low;
816    }
817
818    /**
819     * Get the character offset on the specified line whose position is
820     * closest to the specified horizontal position.
821     */
822    public int getOffsetForHorizontal(int line, float horiz) {
823        int max = getLineEnd(line) - 1;
824        int min = getLineStart(line);
825        Directions dirs = getLineDirections(line);
826
827        if (line == getLineCount() - 1)
828            max++;
829
830        int best = min;
831        float bestdist = Math.abs(getPrimaryHorizontal(best) - horiz);
832
833        for (int i = 0; i < dirs.mDirections.length; i += 2) {
834            int here = min + dirs.mDirections[i];
835            int there = here + (dirs.mDirections[i+1] & RUN_LENGTH_MASK);
836            int swap = (dirs.mDirections[i+1] & RUN_RTL_FLAG) != 0 ? -1 : 1;
837
838            if (there > max)
839                there = max;
840            int high = there - 1 + 1, low = here + 1 - 1, guess;
841
842            while (high - low > 1) {
843                guess = (high + low) / 2;
844                int adguess = getOffsetAtStartOf(guess);
845
846                if (getPrimaryHorizontal(adguess) * swap >= horiz * swap)
847                    high = guess;
848                else
849                    low = guess;
850            }
851
852            if (low < here + 1)
853                low = here + 1;
854
855            if (low < there) {
856                low = getOffsetAtStartOf(low);
857
858                float dist = Math.abs(getPrimaryHorizontal(low) - horiz);
859
860                int aft = TextUtils.getOffsetAfter(mText, low);
861                if (aft < there) {
862                    float other = Math.abs(getPrimaryHorizontal(aft) - horiz);
863
864                    if (other < dist) {
865                        dist = other;
866                        low = aft;
867                    }
868                }
869
870                if (dist < bestdist) {
871                    bestdist = dist;
872                    best = low;
873                }
874            }
875
876            float dist = Math.abs(getPrimaryHorizontal(here) - horiz);
877
878            if (dist < bestdist) {
879                bestdist = dist;
880                best = here;
881            }
882        }
883
884        float dist = Math.abs(getPrimaryHorizontal(max) - horiz);
885
886        if (dist < bestdist) {
887            bestdist = dist;
888            best = max;
889        }
890
891        return best;
892    }
893
894    /**
895     * Return the text offset after the last character on the specified line.
896     */
897    public final int getLineEnd(int line) {
898        return getLineStart(line + 1);
899    }
900
901    /**
902     * Return the text offset after the last visible character (so whitespace
903     * is not counted) on the specified line.
904     */
905    public int getLineVisibleEnd(int line) {
906        return getLineVisibleEnd(line, getLineStart(line), getLineStart(line+1));
907    }
908
909    private int getLineVisibleEnd(int line, int start, int end) {
910        if (DEBUG) {
911            Assert.assertTrue(getLineStart(line) == start && getLineStart(line+1) == end);
912        }
913
914        CharSequence text = mText;
915        char ch;
916        if (line == getLineCount() - 1) {
917            return end;
918        }
919
920        for (; end > start; end--) {
921            ch = text.charAt(end - 1);
922
923            if (ch == '\n') {
924                return end - 1;
925            }
926
927            if (ch != ' ' && ch != '\t') {
928                break;
929            }
930
931        }
932
933        return end;
934    }
935
936    /**
937     * Return the vertical position of the bottom of the specified line.
938     */
939    public final int getLineBottom(int line) {
940        return getLineTop(line + 1);
941    }
942
943    /**
944     * Return the vertical position of the baseline of the specified line.
945     */
946    public final int getLineBaseline(int line) {
947        // getLineTop(line+1) == getLineTop(line)
948        return getLineTop(line+1) - getLineDescent(line);
949    }
950
951    /**
952     * Get the ascent of the text on the specified line.
953     * The return value is negative to match the Paint.ascent() convention.
954     */
955    public final int getLineAscent(int line) {
956        // getLineTop(line+1) - getLineDescent(line) == getLineBaseLine(line)
957        return getLineTop(line) - (getLineTop(line+1) - getLineDescent(line));
958    }
959
960    public int getOffsetToLeftOf(int offset) {
961        return getOffsetToLeftRightOf(offset, true);
962    }
963
964    public int getOffsetToRightOf(int offset) {
965        return getOffsetToLeftRightOf(offset, false);
966    }
967
968    // 1) The caret marks the leading edge of a character. The character
969    // logically before it might be on a different level, and the active caret
970    // position is on the character at the lower level. If that character
971    // was the previous character, the caret is on its trailing edge.
972    // 2) Take this character/edge and move it in the indicated direction.
973    // This gives you a new character and a new edge.
974    // 3) This position is between two visually adjacent characters.  One of
975    // these might be at a lower level.  The active position is on the
976    // character at the lower level.
977    // 4) If the active position is on the trailing edge of the character,
978    // the new caret position is the following logical character, else it
979    // is the character.
980    private int getOffsetToLeftRightOf(int caret, boolean toLeft) {
981        int line = getLineForOffset(caret);
982        int lineStart = getLineStart(line);
983        int lineEnd = getLineEnd(line);
984
985        boolean paraIsRtl = getParagraphDirection(line) == -1;
986        int[] runs = getLineDirections(line).mDirections;
987
988        int runIndex, runLevel = 0, runStart = lineStart, runLimit = lineEnd, newCaret = -1;
989        boolean trailing = false;
990
991        if (caret == lineStart) {
992            runIndex = -2;
993        } else if (caret == lineEnd) {
994            runIndex = runs.length;
995        } else {
996          // First, get information about the run containing the character with
997          // the active caret.
998          for (runIndex = 0; runIndex < runs.length; runIndex += 2) {
999            runStart = lineStart + runs[runIndex];
1000            if (caret >= runStart) {
1001              runLimit = runStart + (runs[runIndex+1] & RUN_LENGTH_MASK);
1002              if (runLimit > lineEnd) {
1003                  runLimit = lineEnd;
1004              }
1005              if (caret < runLimit) {
1006                runLevel = (runs[runIndex+1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK;
1007                if (caret == runStart) {
1008                  // The caret is on a run boundary, see if we should
1009                  // use the position on the trailing edge of the previous
1010                  // logical character instead.
1011                  int prevRunIndex, prevRunLevel, prevRunStart, prevRunLimit;
1012                  int pos = caret - 1;
1013                  for (prevRunIndex = 0; prevRunIndex < runs.length; prevRunIndex += 2) {
1014                    prevRunStart = lineStart + runs[prevRunIndex];
1015                    if (pos >= prevRunStart) {
1016                      prevRunLimit = prevRunStart + (runs[prevRunIndex+1] & RUN_LENGTH_MASK);
1017                      if (prevRunLimit > lineEnd) {
1018                          prevRunLimit = lineEnd;
1019                      }
1020                      if (pos < prevRunLimit) {
1021                        prevRunLevel = (runs[prevRunIndex+1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK;
1022                        if (prevRunLevel < runLevel) {
1023                          // Start from logically previous character.
1024                          runIndex = prevRunIndex;
1025                          runLevel = prevRunLevel;
1026                          runStart = prevRunStart;
1027                          runLimit = prevRunLimit;
1028                          trailing = true;
1029                          break;
1030                        }
1031                      }
1032                    }
1033                  }
1034                }
1035                break;
1036              }
1037            }
1038          }
1039
1040          // caret might be = lineEnd.  This is generally a space or paragraph
1041          // separator and has an associated run, but might be the end of
1042          // text, in which case it doesn't.  If that happens, we ran off the
1043          // end of the run list, and runIndex == runs.length.  In this case,
1044          // we are at a run boundary so we skip the below test.
1045          if (runIndex != runs.length) {
1046              boolean rtlRun = (runLevel & 0x1) != 0;
1047              boolean advance = toLeft == rtlRun;
1048              if (caret != (advance ? runLimit : runStart) || advance != trailing) {
1049                  // Moving within or into the run, so we can move logically.
1050                  newCaret = getOffsetBeforeAfter(caret, advance);
1051                  // If the new position is internal to the run, we're at the strong
1052                  // position already so we're finished.
1053                  if (newCaret != (advance ? runLimit : runStart)) {
1054                      return newCaret;
1055                  }
1056              }
1057          }
1058        }
1059
1060        // If newCaret is -1, we're starting at a run boundary and crossing
1061        // into another run. Otherwise we've arrived at a run boundary, and
1062        // need to figure out which character to attach to.  Note we might
1063        // need to run this twice, if we cross a run boundary and end up at
1064        // another run boundary.
1065        while (true) {
1066          boolean advance = toLeft == paraIsRtl;
1067          int otherRunIndex = runIndex + (advance ? 2 : -2);
1068          if (otherRunIndex >= 0 && otherRunIndex < runs.length) {
1069            int otherRunStart = lineStart + runs[otherRunIndex];
1070            int otherRunLimit = otherRunStart + (runs[otherRunIndex+1] & RUN_LENGTH_MASK);
1071            if (otherRunLimit > lineEnd) {
1072                otherRunLimit = lineEnd;
1073            }
1074            int otherRunLevel = runs[otherRunIndex+1] >>> RUN_LEVEL_SHIFT & RUN_LEVEL_MASK;
1075            boolean otherRunIsRtl = (otherRunLevel & 1) != 0;
1076
1077            advance = toLeft == otherRunIsRtl;
1078            if (newCaret == -1) {
1079              newCaret = getOffsetBeforeAfter(advance ? otherRunStart : otherRunLimit, advance);
1080              if (newCaret == (advance ? otherRunLimit : otherRunStart)) {
1081                // Crossed and ended up at a new boundary, repeat a second and final time.
1082                runIndex = otherRunIndex;
1083                runLevel = otherRunLevel;
1084                continue;
1085              }
1086              break;
1087            }
1088
1089            // The new caret is at a boundary.
1090            if (otherRunLevel < runLevel) {
1091              // The strong character is in the other run.
1092              newCaret = advance ? otherRunStart : otherRunLimit;
1093            }
1094            break;
1095          }
1096
1097          if (newCaret == -1) {
1098              // We're walking off the end of the line.  The paragraph
1099              // level is always equal to or lower than any internal level, so
1100              // the boundaries get the strong caret.
1101              newCaret = getOffsetBeforeAfter(caret, advance);
1102              break;
1103          }
1104          // Else we've arrived at the end of the line.  That's a strong position.
1105          // We might have arrived here by crossing over a run with no internal
1106          // breaks and dropping out of the above loop before advancing one final
1107          // time, so reset the caret.
1108          // Note, we use '<=' below to handle a situation where the only run
1109          // on the line is a counter-directional run.  If we're not advancing,
1110          // we can end up at the 'lineEnd' position but the caret we want is at
1111          // the lineStart.
1112          if (newCaret <= lineEnd) {
1113              newCaret = advance ? lineEnd : lineStart;
1114          }
1115          break;
1116        }
1117
1118        return newCaret;
1119    }
1120
1121    // utility, maybe just roll into the above.
1122    private int getOffsetBeforeAfter(int offset, boolean after) {
1123        if (after) {
1124            return TextUtils.getOffsetAfter(mText, offset);
1125        }
1126        return TextUtils.getOffsetBefore(mText, offset);
1127    }
1128
1129    private int getOffsetAtStartOf(int offset) {
1130        // XXX this probably should skip local reorderings and
1131        // zero-width characters, look at callers
1132        if (offset == 0)
1133            return 0;
1134
1135        CharSequence text = mText;
1136        char c = text.charAt(offset);
1137
1138        if (c >= '\uDC00' && c <= '\uDFFF') {
1139            char c1 = text.charAt(offset - 1);
1140
1141            if (c1 >= '\uD800' && c1 <= '\uDBFF')
1142                offset -= 1;
1143        }
1144
1145        if (mSpannedText) {
1146            ReplacementSpan[] spans = ((Spanned) text).getSpans(offset, offset,
1147                                                       ReplacementSpan.class);
1148
1149            for (int i = 0; i < spans.length; i++) {
1150                int start = ((Spanned) text).getSpanStart(spans[i]);
1151                int end = ((Spanned) text).getSpanEnd(spans[i]);
1152
1153                if (start < offset && end > offset)
1154                    offset = start;
1155            }
1156        }
1157
1158        return offset;
1159    }
1160
1161    /**
1162     * Fills in the specified Path with a representation of a cursor
1163     * at the specified offset.  This will often be a vertical line
1164     * but can be multiple discontinous lines in text with multiple
1165     * directionalities.
1166     */
1167    public void getCursorPath(int point, Path dest,
1168                              CharSequence editingBuffer) {
1169        dest.reset();
1170
1171        int line = getLineForOffset(point);
1172        int top = getLineTop(line);
1173        int bottom = getLineTop(line+1);
1174
1175        float h1 = getPrimaryHorizontal(point) - 0.5f;
1176        float h2 = getSecondaryHorizontal(point) - 0.5f;
1177
1178        int caps = TextKeyListener.getMetaState(editingBuffer,
1179                                                KeyEvent.META_SHIFT_ON) |
1180                   TextKeyListener.getMetaState(editingBuffer,
1181                                                TextKeyListener.META_SELECTING);
1182        int fn = TextKeyListener.getMetaState(editingBuffer,
1183                                              KeyEvent.META_ALT_ON);
1184        int dist = 0;
1185
1186        if (caps != 0 || fn != 0) {
1187            dist = (bottom - top) >> 2;
1188
1189            if (fn != 0)
1190                top += dist;
1191            if (caps != 0)
1192                bottom -= dist;
1193        }
1194
1195        if (h1 < 0.5f)
1196            h1 = 0.5f;
1197        if (h2 < 0.5f)
1198            h2 = 0.5f;
1199
1200        if (h1 == h2) {
1201            dest.moveTo(h1, top);
1202            dest.lineTo(h1, bottom);
1203        } else {
1204            dest.moveTo(h1, top);
1205            dest.lineTo(h1, (top + bottom) >> 1);
1206
1207            dest.moveTo(h2, (top + bottom) >> 1);
1208            dest.lineTo(h2, bottom);
1209        }
1210
1211        if (caps == 2) {
1212            dest.moveTo(h2, bottom);
1213            dest.lineTo(h2 - dist, bottom + dist);
1214            dest.lineTo(h2, bottom);
1215            dest.lineTo(h2 + dist, bottom + dist);
1216        } else if (caps == 1) {
1217            dest.moveTo(h2, bottom);
1218            dest.lineTo(h2 - dist, bottom + dist);
1219
1220            dest.moveTo(h2 - dist, bottom + dist - 0.5f);
1221            dest.lineTo(h2 + dist, bottom + dist - 0.5f);
1222
1223            dest.moveTo(h2 + dist, bottom + dist);
1224            dest.lineTo(h2, bottom);
1225        }
1226
1227        if (fn == 2) {
1228            dest.moveTo(h1, top);
1229            dest.lineTo(h1 - dist, top - dist);
1230            dest.lineTo(h1, top);
1231            dest.lineTo(h1 + dist, top - dist);
1232        } else if (fn == 1) {
1233            dest.moveTo(h1, top);
1234            dest.lineTo(h1 - dist, top - dist);
1235
1236            dest.moveTo(h1 - dist, top - dist + 0.5f);
1237            dest.lineTo(h1 + dist, top - dist + 0.5f);
1238
1239            dest.moveTo(h1 + dist, top - dist);
1240            dest.lineTo(h1, top);
1241        }
1242    }
1243
1244    private void addSelection(int line, int start, int end,
1245                              int top, int bottom, Path dest) {
1246        int linestart = getLineStart(line);
1247        int lineend = getLineEnd(line);
1248        Directions dirs = getLineDirections(line);
1249
1250        if (lineend > linestart && mText.charAt(lineend - 1) == '\n')
1251            lineend--;
1252
1253        for (int i = 0; i < dirs.mDirections.length; i += 2) {
1254            int here = linestart + dirs.mDirections[i];
1255            int there = here + (dirs.mDirections[i+1] & RUN_LENGTH_MASK);
1256
1257            if (there > lineend)
1258                there = lineend;
1259
1260            if (start <= there && end >= here) {
1261                int st = Math.max(start, here);
1262                int en = Math.min(end, there);
1263
1264                if (st != en) {
1265                    float h1 = getHorizontal(st, false, line);
1266                    float h2 = getHorizontal(en, true, line);
1267
1268                    dest.addRect(h1, top, h2, bottom, Path.Direction.CW);
1269                }
1270            }
1271        }
1272    }
1273
1274    /**
1275     * Fills in the specified Path with a representation of a highlight
1276     * between the specified offsets.  This will often be a rectangle
1277     * or a potentially discontinuous set of rectangles.  If the start
1278     * and end are the same, the returned path is empty.
1279     */
1280    public void getSelectionPath(int start, int end, Path dest) {
1281        dest.reset();
1282
1283        if (start == end)
1284            return;
1285
1286        if (end < start) {
1287            int temp = end;
1288            end = start;
1289            start = temp;
1290        }
1291
1292        int startline = getLineForOffset(start);
1293        int endline = getLineForOffset(end);
1294
1295        int top = getLineTop(startline);
1296        int bottom = getLineBottom(endline);
1297
1298        if (startline == endline) {
1299            addSelection(startline, start, end, top, bottom, dest);
1300        } else {
1301            final float width = mWidth;
1302
1303            addSelection(startline, start, getLineEnd(startline),
1304                         top, getLineBottom(startline), dest);
1305
1306            if (getParagraphDirection(startline) == DIR_RIGHT_TO_LEFT)
1307                dest.addRect(getLineLeft(startline), top,
1308                              0, getLineBottom(startline), Path.Direction.CW);
1309            else
1310                dest.addRect(getLineRight(startline), top,
1311                              width, getLineBottom(startline), Path.Direction.CW);
1312
1313            for (int i = startline + 1; i < endline; i++) {
1314                top = getLineTop(i);
1315                bottom = getLineBottom(i);
1316                dest.addRect(0, top, width, bottom, Path.Direction.CW);
1317            }
1318
1319            top = getLineTop(endline);
1320            bottom = getLineBottom(endline);
1321
1322            addSelection(endline, getLineStart(endline), end,
1323                         top, bottom, dest);
1324
1325            if (getParagraphDirection(endline) == DIR_RIGHT_TO_LEFT)
1326                dest.addRect(width, top, getLineRight(endline), bottom, Path.Direction.CW);
1327            else
1328                dest.addRect(0, top, getLineLeft(endline), bottom, Path.Direction.CW);
1329        }
1330    }
1331
1332    /**
1333     * Get the alignment of the specified paragraph, taking into account
1334     * markup attached to it.
1335     */
1336    public final Alignment getParagraphAlignment(int line) {
1337        Alignment align = mAlignment;
1338
1339        if (mSpannedText) {
1340            Spanned sp = (Spanned) mText;
1341            AlignmentSpan[] spans = sp.getSpans(getLineStart(line),
1342                                                getLineEnd(line),
1343                                                AlignmentSpan.class);
1344
1345            int spanLength = spans.length;
1346            if (spanLength > 0) {
1347                align = spans[spanLength-1].getAlignment();
1348            }
1349        }
1350
1351        return align;
1352    }
1353
1354    /**
1355     * Get the left edge of the specified paragraph, inset by left margins.
1356     */
1357    public final int getParagraphLeft(int line) {
1358        int dir = getParagraphDirection(line);
1359
1360        int left = 0;
1361
1362        boolean par = false;
1363        int off = getLineStart(line);
1364        if (off == 0 || mText.charAt(off - 1) == '\n')
1365            par = true;
1366
1367        if (dir == DIR_LEFT_TO_RIGHT) {
1368            if (mSpannedText) {
1369                Spanned sp = (Spanned) mText;
1370                LeadingMarginSpan[] spans = sp.getSpans(getLineStart(line),
1371                                                        getLineEnd(line),
1372                                                        LeadingMarginSpan.class);
1373
1374                for (int i = 0; i < spans.length; i++) {
1375                    boolean margin = par;
1376                    LeadingMarginSpan span = spans[i];
1377                    if (span instanceof LeadingMarginSpan.LeadingMarginSpan2) {
1378                        int count = ((LeadingMarginSpan.LeadingMarginSpan2)span).getLeadingMarginLineCount();
1379                        margin = count >= line;
1380                    }
1381                    left += span.getLeadingMargin(margin);
1382                }
1383            }
1384        }
1385
1386        return left;
1387    }
1388
1389    /**
1390     * Get the right edge of the specified paragraph, inset by right margins.
1391     */
1392    public final int getParagraphRight(int line) {
1393        int dir = getParagraphDirection(line);
1394
1395        int right = mWidth;
1396
1397        boolean par = false;
1398        int off = getLineStart(line);
1399        if (off == 0 || mText.charAt(off - 1) == '\n')
1400            par = true;
1401
1402
1403        if (dir == DIR_RIGHT_TO_LEFT) {
1404            if (mSpannedText) {
1405                Spanned sp = (Spanned) mText;
1406                LeadingMarginSpan[] spans = sp.getSpans(getLineStart(line),
1407                                                        getLineEnd(line),
1408                                                        LeadingMarginSpan.class);
1409
1410                for (int i = 0; i < spans.length; i++) {
1411                    right -= spans[i].getLeadingMargin(par);
1412                }
1413            }
1414        }
1415
1416        return right;
1417    }
1418
1419    private void drawText(Canvas canvas,
1420                                 CharSequence text, int start, int end,
1421                                 int dir, Directions directions,
1422                                 float x, int top, int y, int bottom,
1423                                 TextPaint paint,
1424                                 TextPaint workPaint,
1425                                 boolean hasTabs, Object[] parspans) {
1426        char[] buf;
1427        if (!hasTabs) {
1428            if (directions == DIRS_ALL_LEFT_TO_RIGHT) {
1429                if (DEBUG) {
1430                    Assert.assertTrue(DIR_LEFT_TO_RIGHT == dir);
1431                }
1432                Styled.drawText(canvas, text, start, end, dir, false, x, top, y, bottom, paint, workPaint, false);
1433                return;
1434            }
1435            buf = null;
1436        } else {
1437            buf = TextUtils.obtain(end - start);
1438            TextUtils.getChars(text, start, end, buf, 0);
1439        }
1440
1441        float h = 0;
1442
1443        int lastRunIndex = directions.mDirections.length - 2;
1444        for (int i = 0; i < directions.mDirections.length; i += 2) {
1445            int here = start + directions.mDirections[i];
1446            int there = here + (directions.mDirections[i+1] & RUN_LENGTH_MASK);
1447            boolean runIsRtl = (directions.mDirections[i+1] & RUN_RTL_FLAG) != 0;
1448
1449            if (there > end)
1450                there = end;
1451
1452            int segstart = here;
1453            for (int j = hasTabs ? here : there; j <= there; j++) {
1454                int pj = j - start;
1455                if (j == there || buf[pj] == '\t') {
1456                    h += Styled.drawText(canvas, text,
1457                                         segstart, j,
1458                                         dir, runIsRtl, x + h,
1459                                         top, y, bottom, paint, workPaint,
1460                                         i != lastRunIndex || j != end);
1461
1462                    if (j != there)
1463                        h = dir * nextTab(text, start, end, h * dir, parspans);
1464
1465                    segstart = j + 1;
1466                } else if (hasTabs && buf[pj] >= 0xD800 && buf[pj] <= 0xDFFF && j + 1 < there) {
1467                    int emoji = Character.codePointAt(buf, pj);
1468
1469                    if (emoji >= MIN_EMOJI && emoji <= MAX_EMOJI) {
1470                        Bitmap bm = EMOJI_FACTORY.
1471                            getBitmapFromAndroidPua(emoji);
1472
1473                        if (bm != null) {
1474                            h += Styled.drawText(canvas, text,
1475                                                 segstart, j,
1476                                                 dir, runIsRtl, x + h,
1477                                                 top, y, bottom, paint, workPaint,
1478                                                 i != lastRunIndex || j != end);
1479
1480                            if (mEmojiRect == null) {
1481                                mEmojiRect = new RectF();
1482                            }
1483
1484                            workPaint.set(paint);
1485                            Styled.measureText(paint, workPaint, text,
1486                                               j, j + 1,
1487                                               null);
1488
1489                            float bitmapHeight = bm.getHeight();
1490                            float textHeight = -workPaint.ascent();
1491                            float scale = textHeight / bitmapHeight;
1492                            float width = bm.getWidth() * scale;
1493
1494                            mEmojiRect.set(x + h, y - textHeight,
1495                                           x + h + width, y);
1496
1497                            canvas.drawBitmap(bm, null, mEmojiRect, paint);
1498                            h += width;
1499
1500                            j++;
1501                            segstart = j + 1;
1502                        }
1503                    }
1504                }
1505            }
1506        }
1507
1508        if (hasTabs)
1509            TextUtils.recycle(buf);
1510    }
1511
1512    /**
1513     * Get the distance from the margin to the requested edge of the character
1514     * at offset on the line from start to end.  Trailing indicates the edge
1515     * should be the trailing edge of the character at offset-1.
1516     */
1517    /* package */ static float measureText(TextPaint paint,
1518                                     TextPaint workPaint,
1519                                     CharSequence text,
1520                                     int start, int offset, int end,
1521                                     int dir, Directions directions,
1522                                     boolean trailing, boolean hasTabs,
1523                                     Object[] tabs) {
1524        char[] buf = null;
1525
1526        if (hasTabs) {
1527            buf = TextUtils.obtain(end - start);
1528            TextUtils.getChars(text, start, end, buf, 0);
1529        }
1530
1531        float h = 0;
1532
1533        int target = trailing ? offset - 1 : offset;
1534        if (target < start) {
1535            return 0;
1536        }
1537
1538        int[] runs = directions.mDirections;
1539        for (int i = 0; i < runs.length; i += 2) {
1540            int here = start + runs[i];
1541            int there = here + (runs[i+1] & RUN_LENGTH_MASK);
1542            if (there > end) {
1543                there = end;
1544            }
1545            boolean runIsRtl = (runs[i+1] & RUN_RTL_FLAG) != 0;
1546
1547            int segstart = here;
1548            for (int j = hasTabs ? here : there; j <= there; j++) {
1549                int codept = 0;
1550                Bitmap bm = null;
1551
1552                if (hasTabs && j < there) {
1553                    codept = buf[j - start];
1554                }
1555
1556                if (codept >= 0xD800 && codept <= 0xDFFF && j + 1 < there) {
1557                    codept = Character.codePointAt(buf, j - start);
1558
1559                    if (codept >= MIN_EMOJI && codept <= MAX_EMOJI) {
1560                        bm = EMOJI_FACTORY.getBitmapFromAndroidPua(codept);
1561                    }
1562                }
1563
1564                if (j == there || codept == '\t' || bm != null) {
1565                    float segw;
1566
1567                    boolean inSegment = target >= segstart && target < j;
1568
1569                    if (inSegment) {
1570                        if (dir == DIR_LEFT_TO_RIGHT && !runIsRtl) {
1571                            h += Styled.measureText(paint, workPaint, text,
1572                                                    segstart, offset,
1573                                                    null);
1574                            return h;
1575                        }
1576
1577                        if (dir == DIR_RIGHT_TO_LEFT && runIsRtl) {
1578                            h -= Styled.measureText(paint, workPaint, text,
1579                                                    segstart, offset,
1580                                                    null);
1581                            return h;
1582                        }
1583                    }
1584
1585                    // XXX Style.measureText assumes LTR?
1586                    segw = Styled.measureText(paint, workPaint, text,
1587                                              segstart, j,
1588                                              null);
1589
1590                    if (inSegment) {
1591                        if (dir == DIR_LEFT_TO_RIGHT) {
1592                            h += segw - Styled.measureText(paint, workPaint,
1593                                                           text,
1594                                                           segstart,
1595                                                           offset, null);
1596                            return h;
1597                        }
1598
1599                        if (dir == DIR_RIGHT_TO_LEFT) {
1600                            h -= segw - Styled.measureText(paint, workPaint,
1601                                                           text,
1602                                                           segstart,
1603                                                           offset, null);
1604                            return h;
1605                        }
1606                    }
1607
1608                    if (dir == DIR_RIGHT_TO_LEFT)
1609                        h -= segw;
1610                    else
1611                        h += segw;
1612
1613                    if (j != there && buf[j - start] == '\t') {
1614                        if (offset == j)
1615                            return h;
1616
1617                        h = dir * nextTab(text, start, end, h * dir, tabs);
1618
1619                        if (target == j) {
1620                            return h;
1621                        }
1622                    }
1623
1624                    if (bm != null) {
1625                        workPaint.set(paint);
1626                        Styled.measureText(paint, workPaint, text,
1627                                           j, j + 2, null);
1628
1629                        float wid = bm.getWidth() *
1630                                    -workPaint.ascent() / bm.getHeight();
1631
1632                        if (dir == DIR_RIGHT_TO_LEFT) {
1633                            h -= wid;
1634                        } else {
1635                            h += wid;
1636                        }
1637
1638                        j++;
1639                    }
1640
1641                    segstart = j + 1;
1642                }
1643            }
1644        }
1645
1646        if (hasTabs)
1647            TextUtils.recycle(buf);
1648
1649        return h;
1650    }
1651
1652    /**
1653     * Measure width of a run of text on a single line that is known to all be
1654     * in the same direction as the paragraph base direction. Returns the width,
1655     * and the line metrics in fm if fm is not null.
1656     *
1657     * @param paint the paint for the text; will not be modified
1658     * @param workPaint paint available for modification
1659     * @param text text
1660     * @param start start of the line
1661     * @param end limit of the line
1662     * @param fm object to return integer metrics in, can be null
1663     * @param hasTabs true if it is known that the line has tabs
1664     * @param tabs tab position information
1665     * @return the width of the text from start to end
1666     */
1667    /* package */ static float measureText(TextPaint paint,
1668                                           TextPaint workPaint,
1669                                           CharSequence text,
1670                                           int start, int end,
1671                                           Paint.FontMetricsInt fm,
1672                                           boolean hasTabs, Object[] tabs) {
1673        char[] buf = null;
1674
1675        if (hasTabs) {
1676            buf = TextUtils.obtain(end - start);
1677            TextUtils.getChars(text, start, end, buf, 0);
1678        }
1679
1680        int len = end - start;
1681
1682        int lastPos = 0;
1683        float width = 0;
1684        int ascent = 0, descent = 0, top = 0, bottom = 0;
1685
1686        if (fm != null) {
1687            fm.ascent = 0;
1688            fm.descent = 0;
1689        }
1690
1691        for (int pos = hasTabs ? 0 : len; pos <= len; pos++) {
1692            int codept = 0;
1693            Bitmap bm = null;
1694
1695            if (hasTabs && pos < len) {
1696                codept = buf[pos];
1697            }
1698
1699            if (codept >= 0xD800 && codept <= 0xDFFF && pos < len) {
1700                codept = Character.codePointAt(buf, pos);
1701
1702                if (codept >= MIN_EMOJI && codept <= MAX_EMOJI) {
1703                    bm = EMOJI_FACTORY.getBitmapFromAndroidPua(codept);
1704                }
1705            }
1706
1707            if (pos == len || codept == '\t' || bm != null) {
1708                workPaint.baselineShift = 0;
1709
1710                // XXX Styled.measureText assumes the run direction is LTR,
1711                // but it might not be.  Check this.
1712                width += Styled.measureText(paint, workPaint, text,
1713                                        start + lastPos, start + pos,
1714                                        fm);
1715
1716                if (fm != null) {
1717                    if (workPaint.baselineShift < 0) {
1718                        fm.ascent += workPaint.baselineShift;
1719                        fm.top += workPaint.baselineShift;
1720                    } else {
1721                        fm.descent += workPaint.baselineShift;
1722                        fm.bottom += workPaint.baselineShift;
1723                    }
1724                }
1725
1726                if (pos != len) {
1727                    if (bm == null) {
1728                        // no emoji, must have hit a tab
1729                        width = nextTab(text, start, end, width, tabs);
1730                    } else {
1731                        // This sets up workPaint with the font on the emoji
1732                        // text, so that we can extract the ascent and scale.
1733
1734                        // We can't use the result of the previous call to
1735                        // measureText because the emoji might have its own style.
1736                        // We have to initialize workPaint here because if the
1737                        // text is unstyled measureText might not use workPaint
1738                        // at all.
1739                        workPaint.set(paint);
1740                        Styled.measureText(paint, workPaint, text,
1741                                           start + pos, start + pos + 1, null);
1742
1743                        width += bm.getWidth() *
1744                                    -workPaint.ascent() / bm.getHeight();
1745
1746                        // Since we had an emoji, we bump past the second half
1747                        // of the surrogate pair.
1748                        pos++;
1749                    }
1750                }
1751
1752                if (fm != null) {
1753                    if (fm.ascent < ascent) {
1754                        ascent = fm.ascent;
1755                    }
1756                    if (fm.descent > descent) {
1757                        descent = fm.descent;
1758                    }
1759
1760                    if (fm.top < top) {
1761                        top = fm.top;
1762                    }
1763                    if (fm.bottom > bottom) {
1764                        bottom = fm.bottom;
1765                    }
1766
1767                    // No need to take bitmap height into account here,
1768                    // since it is scaled to match the text height.
1769                }
1770
1771                lastPos = pos + 1;
1772            }
1773        }
1774
1775        if (fm != null) {
1776            fm.ascent = ascent;
1777            fm.descent = descent;
1778            fm.top = top;
1779            fm.bottom = bottom;
1780        }
1781
1782        if (hasTabs)
1783            TextUtils.recycle(buf);
1784
1785        return width;
1786    }
1787
1788    /**
1789     * Returns the position of the next tab stop after h on the line.
1790     *
1791     * @param text the text
1792     * @param start start of the line
1793     * @param end limit of the line
1794     * @param h the current horizontal offset
1795     * @param tabs the tabs, can be null.  If it is null, any tabs in effect
1796     * on the line will be used.  If there are no tabs, a default offset
1797     * will be used to compute the tab stop.
1798     * @return the offset of the next tab stop.
1799     */
1800    /* package */ static float nextTab(CharSequence text, int start, int end,
1801                                       float h, Object[] tabs) {
1802        float nh = Float.MAX_VALUE;
1803        boolean alltabs = false;
1804
1805        if (text instanceof Spanned) {
1806            if (tabs == null) {
1807                tabs = ((Spanned) text).getSpans(start, end, TabStopSpan.class);
1808                alltabs = true;
1809            }
1810
1811            for (int i = 0; i < tabs.length; i++) {
1812                if (!alltabs) {
1813                    if (!(tabs[i] instanceof TabStopSpan))
1814                        continue;
1815                }
1816
1817                int where = ((TabStopSpan) tabs[i]).getTabStop();
1818
1819                if (where < nh && where > h)
1820                    nh = where;
1821            }
1822
1823            if (nh != Float.MAX_VALUE)
1824                return nh;
1825        }
1826
1827        return ((int) ((h + TAB_INCREMENT) / TAB_INCREMENT)) * TAB_INCREMENT;
1828    }
1829
1830    protected final boolean isSpanned() {
1831        return mSpannedText;
1832    }
1833
1834    private void ellipsize(int start, int end, int line,
1835                           char[] dest, int destoff) {
1836        int ellipsisCount = getEllipsisCount(line);
1837
1838        if (ellipsisCount == 0) {
1839            return;
1840        }
1841
1842        int ellipsisStart = getEllipsisStart(line);
1843        int linestart = getLineStart(line);
1844
1845        for (int i = ellipsisStart; i < ellipsisStart + ellipsisCount; i++) {
1846            char c;
1847
1848            if (i == ellipsisStart) {
1849                c = '\u2026'; // ellipsis
1850            } else {
1851                c = '\uFEFF'; // 0-width space
1852            }
1853
1854            int a = i + linestart;
1855
1856            if (a >= start && a < end) {
1857                dest[destoff + a - start] = c;
1858            }
1859        }
1860    }
1861
1862    /**
1863     * Stores information about bidirectional (left-to-right or right-to-left)
1864     * text within the layout of a line.
1865     */
1866    public static class Directions {
1867        // Directions represents directional runs within a line of text.
1868        // Runs are pairs of ints listed in visual order, starting from the
1869        // leading margin.  The first int of each pair is the offset from
1870        // the first character of the line to the start of the run.  The
1871        // second int represents both the length and level of the run.
1872        // The length is in the lower bits, accessed by masking with
1873        // DIR_LENGTH_MASK.  The level is in the higher bits, accessed
1874        // by shifting by DIR_LEVEL_SHIFT and masking by DIR_LEVEL_MASK.
1875        // To simply test for an RTL direction, test the bit using
1876        // DIR_RTL_FLAG, if set then the direction is rtl.
1877
1878        /* package */ int[] mDirections;
1879        /* package */ Directions(int[] dirs) {
1880            mDirections = dirs;
1881        }
1882    }
1883
1884    /**
1885     * Return the offset of the first character to be ellipsized away,
1886     * relative to the start of the line.  (So 0 if the beginning of the
1887     * line is ellipsized, not getLineStart().)
1888     */
1889    public abstract int getEllipsisStart(int line);
1890    /**
1891     * Returns the number of characters to be ellipsized away, or 0 if
1892     * no ellipsis is to take place.
1893     */
1894    public abstract int getEllipsisCount(int line);
1895
1896    /* package */ static class Ellipsizer implements CharSequence, GetChars {
1897        /* package */ CharSequence mText;
1898        /* package */ Layout mLayout;
1899        /* package */ int mWidth;
1900        /* package */ TextUtils.TruncateAt mMethod;
1901
1902        public Ellipsizer(CharSequence s) {
1903            mText = s;
1904        }
1905
1906        public char charAt(int off) {
1907            char[] buf = TextUtils.obtain(1);
1908            getChars(off, off + 1, buf, 0);
1909            char ret = buf[0];
1910
1911            TextUtils.recycle(buf);
1912            return ret;
1913        }
1914
1915        public void getChars(int start, int end, char[] dest, int destoff) {
1916            int line1 = mLayout.getLineForOffset(start);
1917            int line2 = mLayout.getLineForOffset(end);
1918
1919            TextUtils.getChars(mText, start, end, dest, destoff);
1920
1921            for (int i = line1; i <= line2; i++) {
1922                mLayout.ellipsize(start, end, i, dest, destoff);
1923            }
1924        }
1925
1926        public int length() {
1927            return mText.length();
1928        }
1929
1930        public CharSequence subSequence(int start, int end) {
1931            char[] s = new char[end - start];
1932            getChars(start, end, s, 0);
1933            return new String(s);
1934        }
1935
1936        public String toString() {
1937            char[] s = new char[length()];
1938            getChars(0, length(), s, 0);
1939            return new String(s);
1940        }
1941
1942    }
1943
1944    /* package */ static class SpannedEllipsizer
1945                    extends Ellipsizer implements Spanned {
1946        private Spanned mSpanned;
1947
1948        public SpannedEllipsizer(CharSequence display) {
1949            super(display);
1950            mSpanned = (Spanned) display;
1951        }
1952
1953        public <T> T[] getSpans(int start, int end, Class<T> type) {
1954            return mSpanned.getSpans(start, end, type);
1955        }
1956
1957        public int getSpanStart(Object tag) {
1958            return mSpanned.getSpanStart(tag);
1959        }
1960
1961        public int getSpanEnd(Object tag) {
1962            return mSpanned.getSpanEnd(tag);
1963        }
1964
1965        public int getSpanFlags(Object tag) {
1966            return mSpanned.getSpanFlags(tag);
1967        }
1968
1969        public int nextSpanTransition(int start, int limit, Class type) {
1970            return mSpanned.nextSpanTransition(start, limit, type);
1971        }
1972
1973        public CharSequence subSequence(int start, int end) {
1974            char[] s = new char[end - start];
1975            getChars(start, end, s, 0);
1976
1977            SpannableString ss = new SpannableString(new String(s));
1978            TextUtils.copySpansFrom(mSpanned, start, end, Object.class, ss, 0);
1979            return ss;
1980        }
1981    }
1982
1983    private CharSequence mText;
1984    private TextPaint mPaint;
1985    /* package */ TextPaint mWorkPaint;
1986    private int mWidth;
1987    private Alignment mAlignment = Alignment.ALIGN_NORMAL;
1988    private float mSpacingMult;
1989    private float mSpacingAdd;
1990    private static Rect sTempRect = new Rect();
1991    private boolean mSpannedText;
1992
1993    public static final int DIR_LEFT_TO_RIGHT = 1;
1994    public static final int DIR_RIGHT_TO_LEFT = -1;
1995
1996    /* package */ static final int DIR_REQUEST_LTR = 1;
1997    /* package */ static final int DIR_REQUEST_RTL = -1;
1998    /* package */ static final int DIR_REQUEST_DEFAULT_LTR = 2;
1999    /* package */ static final int DIR_REQUEST_DEFAULT_RTL = -2;
2000
2001    /* package */ static final int RUN_LENGTH_MASK = 0x03ffffff;
2002    /* package */ static final int RUN_LEVEL_SHIFT = 26;
2003    /* package */ static final int RUN_LEVEL_MASK = 0x3f;
2004    /* package */ static final int RUN_RTL_FLAG = 1 << RUN_LEVEL_SHIFT;
2005
2006    public enum Alignment {
2007        ALIGN_NORMAL,
2008        ALIGN_OPPOSITE,
2009        ALIGN_CENTER,
2010        // XXX ALIGN_LEFT,
2011        // XXX ALIGN_RIGHT,
2012    }
2013
2014    private static final int TAB_INCREMENT = 20;
2015
2016    /* package */ static final Directions DIRS_ALL_LEFT_TO_RIGHT =
2017        new Directions(new int[] { 0, RUN_LENGTH_MASK });
2018    /* package */ static final Directions DIRS_ALL_RIGHT_TO_LEFT =
2019        new Directions(new int[] { 0, RUN_LENGTH_MASK | RUN_RTL_FLAG });
2020}
2021
2022