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