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