Layout.java revision b798689749c64baba81f02e10cf2157c747d6b46
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                   TextKeyListener.getMetaState(editingBuffer,
1079                                                TextKeyListener.META_SELECTING);
1080        int fn = TextKeyListener.getMetaState(editingBuffer,
1081                                              KeyEvent.META_ALT_ON);
1082        int dist = 0;
1083
1084        if (caps != 0 || fn != 0) {
1085            dist = (bottom - top) >> 2;
1086
1087            if (fn != 0)
1088                top += dist;
1089            if (caps != 0)
1090                bottom -= dist;
1091        }
1092
1093        if (h1 < 0.5f)
1094            h1 = 0.5f;
1095        if (h2 < 0.5f)
1096            h2 = 0.5f;
1097
1098        if (h1 == h2) {
1099            dest.moveTo(h1, top);
1100            dest.lineTo(h1, bottom);
1101        } else {
1102            dest.moveTo(h1, top);
1103            dest.lineTo(h1, (top + bottom) >> 1);
1104
1105            dest.moveTo(h2, (top + bottom) >> 1);
1106            dest.lineTo(h2, bottom);
1107        }
1108
1109        if (caps == 2) {
1110            dest.moveTo(h2, bottom);
1111            dest.lineTo(h2 - dist, bottom + dist);
1112            dest.lineTo(h2, bottom);
1113            dest.lineTo(h2 + dist, bottom + dist);
1114        } else if (caps == 1) {
1115            dest.moveTo(h2, bottom);
1116            dest.lineTo(h2 - dist, bottom + dist);
1117
1118            dest.moveTo(h2 - dist, bottom + dist - 0.5f);
1119            dest.lineTo(h2 + dist, bottom + dist - 0.5f);
1120
1121            dest.moveTo(h2 + dist, bottom + dist);
1122            dest.lineTo(h2, bottom);
1123        }
1124
1125        if (fn == 2) {
1126            dest.moveTo(h1, top);
1127            dest.lineTo(h1 - dist, top - dist);
1128            dest.lineTo(h1, top);
1129            dest.lineTo(h1 + dist, top - dist);
1130        } else if (fn == 1) {
1131            dest.moveTo(h1, top);
1132            dest.lineTo(h1 - dist, top - dist);
1133
1134            dest.moveTo(h1 - dist, top - dist + 0.5f);
1135            dest.lineTo(h1 + dist, top - dist + 0.5f);
1136
1137            dest.moveTo(h1 + dist, top - dist);
1138            dest.lineTo(h1, top);
1139        }
1140    }
1141
1142    private void addSelection(int line, int start, int end,
1143                              int top, int bottom, Path dest) {
1144        int linestart = getLineStart(line);
1145        int lineend = getLineEnd(line);
1146        Directions dirs = getLineDirections(line);
1147
1148        if (lineend > linestart && mText.charAt(lineend - 1) == '\n')
1149            lineend--;
1150
1151        int here = linestart;
1152        for (int i = 0; i < dirs.mDirections.length; i++) {
1153            int there = here + dirs.mDirections[i];
1154            if (there > lineend)
1155                there = lineend;
1156
1157            if (start <= there && end >= here) {
1158                int st = Math.max(start, here);
1159                int en = Math.min(end, there);
1160
1161                if (st != en) {
1162                    float h1 = getHorizontal(st, false, false, line);
1163                    float h2 = getHorizontal(en, true, false, line);
1164
1165                    dest.addRect(h1, top, h2, bottom, Path.Direction.CW);
1166                }
1167            }
1168
1169            here = there;
1170        }
1171    }
1172
1173    /**
1174     * Fills in the specified Path with a representation of a highlight
1175     * between the specified offsets.  This will often be a rectangle
1176     * or a potentially discontinuous set of rectangles.  If the start
1177     * and end are the same, the returned path is empty.
1178     */
1179    public void getSelectionPath(int start, int end, Path dest) {
1180        dest.reset();
1181
1182        if (start == end)
1183            return;
1184
1185        if (end < start) {
1186            int temp = end;
1187            end = start;
1188            start = temp;
1189        }
1190
1191        int startline = getLineForOffset(start);
1192        int endline = getLineForOffset(end);
1193
1194        int top = getLineTop(startline);
1195        int bottom = getLineBottom(endline);
1196
1197        if (startline == endline) {
1198            addSelection(startline, start, end, top, bottom, dest);
1199        } else {
1200            final float width = mWidth;
1201
1202            addSelection(startline, start, getLineEnd(startline),
1203                         top, getLineBottom(startline), dest);
1204
1205            if (getParagraphDirection(startline) == DIR_RIGHT_TO_LEFT)
1206                dest.addRect(getLineLeft(startline), top,
1207                              0, getLineBottom(startline), Path.Direction.CW);
1208            else
1209                dest.addRect(getLineRight(startline), top,
1210                              width, getLineBottom(startline), Path.Direction.CW);
1211
1212            for (int i = startline + 1; i < endline; i++) {
1213                top = getLineTop(i);
1214                bottom = getLineBottom(i);
1215                dest.addRect(0, top, width, bottom, Path.Direction.CW);
1216            }
1217
1218            top = getLineTop(endline);
1219            bottom = getLineBottom(endline);
1220
1221            addSelection(endline, getLineStart(endline), end,
1222                         top, bottom, dest);
1223
1224            if (getParagraphDirection(endline) == DIR_RIGHT_TO_LEFT)
1225                dest.addRect(width, top, getLineRight(endline), bottom, Path.Direction.CW);
1226            else
1227                dest.addRect(0, top, getLineLeft(endline), bottom, Path.Direction.CW);
1228        }
1229    }
1230
1231    /**
1232     * Get the alignment of the specified paragraph, taking into account
1233     * markup attached to it.
1234     */
1235    public final Alignment getParagraphAlignment(int line) {
1236        Alignment align = mAlignment;
1237
1238        if (mSpannedText) {
1239            Spanned sp = (Spanned) mText;
1240            AlignmentSpan[] spans = sp.getSpans(getLineStart(line),
1241                                                getLineEnd(line),
1242                                                AlignmentSpan.class);
1243
1244            int spanLength = spans.length;
1245            if (spanLength > 0) {
1246                align = spans[spanLength-1].getAlignment();
1247            }
1248        }
1249
1250        return align;
1251    }
1252
1253    /**
1254     * Get the left edge of the specified paragraph, inset by left margins.
1255     */
1256    public final int getParagraphLeft(int line) {
1257        int dir = getParagraphDirection(line);
1258
1259        int left = 0;
1260
1261        boolean par = false;
1262        int off = getLineStart(line);
1263        if (off == 0 || mText.charAt(off - 1) == '\n')
1264            par = true;
1265
1266        if (dir == DIR_LEFT_TO_RIGHT) {
1267            if (mSpannedText) {
1268                Spanned sp = (Spanned) mText;
1269                LeadingMarginSpan[] spans = sp.getSpans(getLineStart(line),
1270                                                        getLineEnd(line),
1271                                                        LeadingMarginSpan.class);
1272
1273                for (int i = 0; i < spans.length; i++) {
1274                    left += spans[i].getLeadingMargin(par);
1275                }
1276            }
1277        }
1278
1279        return left;
1280    }
1281
1282    /**
1283     * Get the right edge of the specified paragraph, inset by right margins.
1284     */
1285    public final int getParagraphRight(int line) {
1286        int dir = getParagraphDirection(line);
1287
1288        int right = mWidth;
1289
1290        boolean par = false;
1291        int off = getLineStart(line);
1292        if (off == 0 || mText.charAt(off - 1) == '\n')
1293            par = true;
1294
1295
1296        if (dir == DIR_RIGHT_TO_LEFT) {
1297            if (mSpannedText) {
1298                Spanned sp = (Spanned) mText;
1299                LeadingMarginSpan[] spans = sp.getSpans(getLineStart(line),
1300                                                        getLineEnd(line),
1301                                                        LeadingMarginSpan.class);
1302
1303                for (int i = 0; i < spans.length; i++) {
1304                    right -= spans[i].getLeadingMargin(par);
1305                }
1306            }
1307        }
1308
1309        return right;
1310    }
1311
1312    private static void drawText(Canvas canvas,
1313                                 CharSequence text, int start, int end,
1314                                 int dir, Directions directions,
1315                                 float x, int top, int y, int bottom,
1316                                 TextPaint paint,
1317                                 TextPaint workPaint,
1318                                 boolean hasTabs, Object[] parspans) {
1319        char[] buf;
1320        if (!hasTabs) {
1321            if (directions == DIRS_ALL_LEFT_TO_RIGHT) {
1322                if (Config.DEBUG) {
1323                    Assert.assertTrue(DIR_LEFT_TO_RIGHT == dir);
1324                }
1325                Styled.drawText(canvas, text, start, end, dir, false, x, top, y, bottom, paint, workPaint, false);
1326                return;
1327            }
1328            buf = null;
1329        } else {
1330            buf = TextUtils.obtain(end - start);
1331            TextUtils.getChars(text, start, end, buf, 0);
1332        }
1333
1334        float h = 0;
1335
1336        int here = 0;
1337        for (int i = 0; i < directions.mDirections.length; i++) {
1338            int there = here + directions.mDirections[i];
1339            if (there > end - start)
1340                there = end - start;
1341
1342            int segstart = here;
1343            for (int j = hasTabs ? here : there; j <= there; j++) {
1344                if (j == there || buf[j] == '\t') {
1345                    h += Styled.drawText(canvas, text,
1346                                         start + segstart, start + j,
1347                                         dir, (i & 1) != 0, x + h,
1348                                         top, y, bottom, paint, workPaint,
1349                                         start + j != end);
1350
1351                    if (j != there && buf[j] == '\t')
1352                        h = dir * nextTab(text, start, end, h * dir, parspans);
1353
1354                    segstart = j + 1;
1355                }
1356            }
1357
1358            here = there;
1359        }
1360
1361        if (hasTabs)
1362            TextUtils.recycle(buf);
1363    }
1364
1365    private static float measureText(TextPaint paint,
1366                                     TextPaint workPaint,
1367                                     CharSequence text,
1368                                     int start, int offset, int end,
1369                                     int dir, Directions directions,
1370                                     boolean trailing, boolean alt,
1371                                     boolean hasTabs, Object[] tabs) {
1372        char[] buf = null;
1373
1374        if (hasTabs) {
1375            buf = TextUtils.obtain(end - start);
1376            TextUtils.getChars(text, start, end, buf, 0);
1377        }
1378
1379        float h = 0;
1380
1381        if (alt) {
1382            if (dir == DIR_RIGHT_TO_LEFT)
1383                trailing = !trailing;
1384        }
1385
1386        int here = 0;
1387        for (int i = 0; i < directions.mDirections.length; i++) {
1388            if (alt)
1389                trailing = !trailing;
1390
1391            int there = here + directions.mDirections[i];
1392            if (there > end - start)
1393                there = end - start;
1394
1395            int segstart = here;
1396            for (int j = hasTabs ? here : there; j <= there; j++) {
1397                if (j == there || buf[j] == '\t') {
1398                    float segw;
1399
1400                    if (offset < start + j ||
1401                       (trailing && offset <= start + j)) {
1402                        if (dir == DIR_LEFT_TO_RIGHT && (i & 1) == 0) {
1403                            h += Styled.measureText(paint, workPaint, text,
1404                                                    start + segstart, offset,
1405                                                    null);
1406                            return h;
1407                        }
1408
1409                        if (dir == DIR_RIGHT_TO_LEFT && (i & 1) != 0) {
1410                            h -= Styled.measureText(paint, workPaint, text,
1411                                                    start + segstart, offset,
1412                                                    null);
1413                            return h;
1414                        }
1415                    }
1416
1417                    segw = Styled.measureText(paint, workPaint, text,
1418                                              start + segstart, start + j,
1419                                              null);
1420
1421                    if (offset < start + j ||
1422                        (trailing && offset <= start + j)) {
1423                        if (dir == DIR_LEFT_TO_RIGHT) {
1424                            h += segw - Styled.measureText(paint, workPaint,
1425                                                           text,
1426                                                           start + segstart,
1427                                                           offset, null);
1428                            return h;
1429                        }
1430
1431                        if (dir == DIR_RIGHT_TO_LEFT) {
1432                            h -= segw - Styled.measureText(paint, workPaint,
1433                                                           text,
1434                                                           start + segstart,
1435                                                           offset, null);
1436                            return h;
1437                        }
1438                    }
1439
1440                    if (dir == DIR_RIGHT_TO_LEFT)
1441                        h -= segw;
1442                    else
1443                        h += segw;
1444
1445                    if (j != there && buf[j] == '\t') {
1446                        if (offset == start + j)
1447                            return h;
1448
1449                        h = dir * nextTab(text, start, end, h * dir, tabs);
1450                    }
1451
1452                    segstart = j + 1;
1453                }
1454            }
1455
1456            here = there;
1457        }
1458
1459        if (hasTabs)
1460            TextUtils.recycle(buf);
1461
1462        return h;
1463    }
1464
1465    /* package */ static float measureText(TextPaint paint,
1466                                           TextPaint workPaint,
1467                                           CharSequence text,
1468                                           int start, int end,
1469                                           Paint.FontMetricsInt fm,
1470                                           boolean hasTabs, Object[] tabs) {
1471        char[] buf = null;
1472
1473        if (hasTabs) {
1474            buf = TextUtils.obtain(end - start);
1475            TextUtils.getChars(text, start, end, buf, 0);
1476        }
1477
1478        int len = end - start;
1479
1480        int here = 0;
1481        float h = 0;
1482        int ab = 0, be = 0;
1483        int top = 0, bot = 0;
1484
1485        if (fm != null) {
1486            fm.ascent = 0;
1487            fm.descent = 0;
1488        }
1489
1490        for (int i = hasTabs ? 0 : len; i <= len; i++) {
1491            if (i == len || buf[i] == '\t') {
1492                workPaint.baselineShift = 0;
1493
1494                h += Styled.measureText(paint, workPaint, text,
1495                                        start + here, start + i,
1496                                        fm);
1497
1498                if (fm != null) {
1499                    if (workPaint.baselineShift < 0) {
1500                        fm.ascent += workPaint.baselineShift;
1501                        fm.top += workPaint.baselineShift;
1502                    } else {
1503                        fm.descent += workPaint.baselineShift;
1504                        fm.bottom += workPaint.baselineShift;
1505                    }
1506                }
1507
1508                if (i != len)
1509                    h = nextTab(text, start, end, h, tabs);
1510
1511                if (fm != null) {
1512                    if (fm.ascent < ab) {
1513                        ab = fm.ascent;
1514                    }
1515                    if (fm.descent > be) {
1516                        be = fm.descent;
1517                    }
1518
1519                    if (fm.top < top) {
1520                        top = fm.top;
1521                    }
1522                    if (fm.bottom > bot) {
1523                        bot = fm.bottom;
1524                    }
1525                }
1526
1527                here = i + 1;
1528            }
1529        }
1530
1531        if (fm != null) {
1532            fm.ascent = ab;
1533            fm.descent = be;
1534            fm.top = top;
1535            fm.bottom = bot;
1536        }
1537
1538        if (hasTabs)
1539            TextUtils.recycle(buf);
1540
1541        return h;
1542    }
1543
1544    /* package */ static float nextTab(CharSequence text, int start, int end,
1545                                       float h, Object[] tabs) {
1546        float nh = Float.MAX_VALUE;
1547        boolean alltabs = false;
1548
1549        if (text instanceof Spanned) {
1550            if (tabs == null) {
1551                tabs = ((Spanned) text).getSpans(start, end, TabStopSpan.class);
1552                alltabs = true;
1553            }
1554
1555            for (int i = 0; i < tabs.length; i++) {
1556                if (!alltabs) {
1557                    if (!(tabs[i] instanceof TabStopSpan))
1558                        continue;
1559                }
1560
1561                int where = ((TabStopSpan) tabs[i]).getTabStop();
1562
1563                if (where < nh && where > h)
1564                    nh = where;
1565            }
1566
1567            if (nh != Float.MAX_VALUE)
1568                return nh;
1569        }
1570
1571        return ((int) ((h + TAB_INCREMENT) / TAB_INCREMENT)) * TAB_INCREMENT;
1572    }
1573
1574    protected final boolean isSpanned() {
1575        return mSpannedText;
1576    }
1577
1578    private void ellipsize(int start, int end, int line,
1579                           char[] dest, int destoff) {
1580        int ellipsisCount = getEllipsisCount(line);
1581
1582        if (ellipsisCount == 0) {
1583            return;
1584        }
1585
1586        int ellipsisStart = getEllipsisStart(line);
1587        int linestart = getLineStart(line);
1588
1589        for (int i = ellipsisStart; i < ellipsisStart + ellipsisCount; i++) {
1590            char c;
1591
1592            if (i == ellipsisStart) {
1593                c = '\u2026'; // ellipsis
1594            } else {
1595                c = '\uFEFF'; // 0-width space
1596            }
1597
1598            int a = i + linestart;
1599
1600            if (a >= start && a < end) {
1601                dest[destoff + a - start] = c;
1602            }
1603        }
1604    }
1605
1606    /**
1607     * Stores information about bidirectional (left-to-right or right-to-left)
1608     * text within the layout of a line.  TODO: This work is not complete
1609     * or correct and will be fleshed out in a later revision.
1610     */
1611    public static class Directions {
1612        private short[] mDirections;
1613
1614        /* package */ Directions(short[] dirs) {
1615            mDirections = dirs;
1616        }
1617    }
1618
1619    /**
1620     * Return the offset of the first character to be ellipsized away,
1621     * relative to the start of the line.  (So 0 if the beginning of the
1622     * line is ellipsized, not getLineStart().)
1623     */
1624    public abstract int getEllipsisStart(int line);
1625    /**
1626     * Returns the number of characters to be ellipsized away, or 0 if
1627     * no ellipsis is to take place.
1628     */
1629    public abstract int getEllipsisCount(int line);
1630
1631    /* package */ static class Ellipsizer implements CharSequence, GetChars {
1632        /* package */ CharSequence mText;
1633        /* package */ Layout mLayout;
1634        /* package */ int mWidth;
1635        /* package */ TextUtils.TruncateAt mMethod;
1636
1637        public Ellipsizer(CharSequence s) {
1638            mText = s;
1639        }
1640
1641        public char charAt(int off) {
1642            char[] buf = TextUtils.obtain(1);
1643            getChars(off, off + 1, buf, 0);
1644            char ret = buf[0];
1645
1646            TextUtils.recycle(buf);
1647            return ret;
1648        }
1649
1650        public void getChars(int start, int end, char[] dest, int destoff) {
1651            int line1 = mLayout.getLineForOffset(start);
1652            int line2 = mLayout.getLineForOffset(end);
1653
1654            TextUtils.getChars(mText, start, end, dest, destoff);
1655
1656            for (int i = line1; i <= line2; i++) {
1657                mLayout.ellipsize(start, end, i, dest, destoff);
1658            }
1659        }
1660
1661        public int length() {
1662            return mText.length();
1663        }
1664
1665        public CharSequence subSequence(int start, int end) {
1666            char[] s = new char[end - start];
1667            getChars(start, end, s, 0);
1668            return new String(s);
1669        }
1670
1671        public String toString() {
1672            char[] s = new char[length()];
1673            getChars(0, length(), s, 0);
1674            return new String(s);
1675        }
1676
1677    }
1678
1679    /* package */ static class SpannedEllipsizer
1680                    extends Ellipsizer implements Spanned {
1681        private Spanned mSpanned;
1682
1683        public SpannedEllipsizer(CharSequence display) {
1684            super(display);
1685            mSpanned = (Spanned) display;
1686        }
1687
1688        public <T> T[] getSpans(int start, int end, Class<T> type) {
1689            return mSpanned.getSpans(start, end, type);
1690        }
1691
1692        public int getSpanStart(Object tag) {
1693            return mSpanned.getSpanStart(tag);
1694        }
1695
1696        public int getSpanEnd(Object tag) {
1697            return mSpanned.getSpanEnd(tag);
1698        }
1699
1700        public int getSpanFlags(Object tag) {
1701            return mSpanned.getSpanFlags(tag);
1702        }
1703
1704        public int nextSpanTransition(int start, int limit, Class type) {
1705            return mSpanned.nextSpanTransition(start, limit, type);
1706        }
1707
1708        public CharSequence subSequence(int start, int end) {
1709            char[] s = new char[end - start];
1710            getChars(start, end, s, 0);
1711
1712            SpannableString ss = new SpannableString(new String(s));
1713            TextUtils.copySpansFrom(mSpanned, start, end, Object.class, ss, 0);
1714            return ss;
1715        }
1716    }
1717
1718    private CharSequence mText;
1719    private TextPaint mPaint;
1720    /* package */ TextPaint mWorkPaint;
1721    private int mWidth;
1722    private Alignment mAlignment = Alignment.ALIGN_NORMAL;
1723    private float mSpacingMult;
1724    private float mSpacingAdd;
1725    private static Rect sTempRect = new Rect();
1726    private boolean mSpannedText;
1727
1728    public static final int DIR_LEFT_TO_RIGHT = 1;
1729    public static final int DIR_RIGHT_TO_LEFT = -1;
1730
1731    public enum Alignment {
1732        ALIGN_NORMAL,
1733        ALIGN_OPPOSITE,
1734        ALIGN_CENTER,
1735        // XXX ALIGN_LEFT,
1736        // XXX ALIGN_RIGHT,
1737    }
1738
1739    private static final int TAB_INCREMENT = 20;
1740
1741    /* package */ static final Directions DIRS_ALL_LEFT_TO_RIGHT =
1742                                       new Directions(new short[] { 32767 });
1743    /* package */ static final Directions DIRS_ALL_RIGHT_TO_LEFT =
1744                                       new Directions(new short[] { 0, 32767 });
1745
1746}
1747
1748