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