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