FileDocCategorySizeDatePackage
Layout.javaAPI DocAndroid 1.5 API60128Wed May 06 22:41:56 BST 2009android.text

Layout

public abstract class Layout extends Object
A base class that manages text layout in visual elements on the screen.

For text that will be edited, use a {@link DynamicLayout}, which will be updated as the text changes. For text that will not change, use a {@link StaticLayout}.

Fields Summary
static final android.emoji.EmojiFactory
EMOJI_FACTORY
static final int
MIN_EMOJI
static final int
MAX_EMOJI
private android.graphics.RectF
mEmojiRect
private CharSequence
mText
private TextPaint
mPaint
TextPaint
mWorkPaint
private int
mWidth
private Alignment
mAlignment
private float
mSpacingMult
private float
mSpacingAdd
private static android.graphics.Rect
sTempRect
private boolean
mSpannedText
public static final int
DIR_LEFT_TO_RIGHT
public static final int
DIR_RIGHT_TO_LEFT
private static final int
TAB_INCREMENT
static final Directions
DIRS_ALL_LEFT_TO_RIGHT
static final Directions
DIRS_ALL_RIGHT_TO_LEFT
Constructors Summary
protected Layout(CharSequence text, TextPaint paint, int width, Alignment align, float spacingmult, float spacingadd)
Subclasses of Layout use this constructor to set the display text, width, and other standard properties.

        if (width < 0)
            throw new IllegalArgumentException("Layout: " + width + " < 0");

        mText = text;
        mPaint = paint;
        mWorkPaint = new TextPaint();
        mWidth = width;
        mAlignment = align;
        mSpacingMult = spacingmult;
        mSpacingAdd = spacingadd;
        mSpannedText = text instanceof Spanned;
    
Methods Summary
private voidaddSelection(int line, int start, int end, int top, int bottom, android.graphics.Path dest)

        int linestart = getLineStart(line);
        int lineend = getLineEnd(line);
        Directions dirs = getLineDirections(line);

        if (lineend > linestart && mText.charAt(lineend - 1) == '\n")
            lineend--;

        int here = linestart;
        for (int i = 0; i < dirs.mDirections.length; i++) {
            int there = here + dirs.mDirections[i];
            if (there > lineend)
                there = lineend;

            if (start <= there && end >= here) {
                int st = Math.max(start, here);
                int en = Math.min(end, there);

                if (st != en) {
                    float h1 = getHorizontal(st, false, false, line);
                    float h2 = getHorizontal(en, true, false, line);

                    dest.addRect(h1, top, h2, bottom, Path.Direction.CW);
                }
            }

            here = there;
        }
    
public voiddraw(android.graphics.Canvas c)
Draw this Layout on the specified Canvas.

        draw(c, null, null, 0);
    
public voiddraw(android.graphics.Canvas c, android.graphics.Path highlight, android.graphics.Paint highlightpaint, int cursorOffsetVertical)
Draw the specified rectangle from this Layout on the specified Canvas, with the specified path drawn between the background and the text.

        int dtop, dbottom;

        synchronized (sTempRect) {
            if (!c.getClipBounds(sTempRect)) {
                return;
            }

            dtop = sTempRect.top;
            dbottom = sTempRect.bottom;
        }

        TextPaint paint = mPaint;

        int top = 0;
        // getLineBottom(getLineCount() -1) just calls getLineTop(getLineCount) 
        int bottom = getLineTop(getLineCount());


        if (dtop > top) {
            top = dtop;
        }
        if (dbottom < bottom) {
            bottom = dbottom;
        }
        
        int first = getLineForVertical(top); 
        int last = getLineForVertical(bottom);
        
        int previousLineBottom = getLineTop(first);
        int previousLineEnd = getLineStart(first);
        
        CharSequence buf = mText;

        ParagraphStyle[] nospans = ArrayUtils.emptyArray(ParagraphStyle.class);
        ParagraphStyle[] spans = nospans;
        int spanend = 0;
        int textLength = 0;
        boolean spannedText = mSpannedText;

        if (spannedText) {
            spanend = 0;
            textLength = buf.length();
            for (int i = first; i <= last; i++) {
                int start = previousLineEnd;
                int end = getLineStart(i+1);
                previousLineEnd = end;

                int ltop = previousLineBottom;
                int lbottom = getLineTop(i+1);
                previousLineBottom = lbottom;
                int lbaseline = lbottom - getLineDescent(i);

                if (start >= spanend) {
                   Spanned sp = (Spanned) buf;
                   spanend = sp.nextSpanTransition(start, textLength,
                                                   LineBackgroundSpan.class);
                   spans = sp.getSpans(start, spanend,
                                       LineBackgroundSpan.class);
                }

                for (int n = 0; n < spans.length; n++) {
                    LineBackgroundSpan back = (LineBackgroundSpan) spans[n];

                    back.drawBackground(c, paint, 0, mWidth,
                                       ltop, lbaseline, lbottom,
                                       buf, start, end,
                                       i);
                }
            }
            // reset to their original values
            spanend = 0;
            previousLineBottom = getLineTop(first);
            previousLineEnd = getLineStart(first);
            spans = nospans;
        } 

        // There can be a highlight even without spans if we are drawing
        // a non-spanned transformation of a spanned editing buffer.
        if (highlight != null) {
            if (cursorOffsetVertical != 0) {
                c.translate(0, cursorOffsetVertical);
            }

            c.drawPath(highlight, highlightpaint);

            if (cursorOffsetVertical != 0) {
                c.translate(0, -cursorOffsetVertical);
            }
        }

        Alignment align = mAlignment;
        
        for (int i = first; i <= last; i++) {
            int start = previousLineEnd;

            previousLineEnd = getLineStart(i+1);
            int end = getLineVisibleEnd(i, start, previousLineEnd);

            int ltop = previousLineBottom;
            int lbottom = getLineTop(i+1);
            previousLineBottom = lbottom;
            int lbaseline = lbottom - getLineDescent(i);

            boolean par = false;
            if (spannedText) { 
                if (start == 0 || buf.charAt(start - 1) == '\n") {
                    par = true;
                }
                if (start >= spanend) {

                    Spanned sp = (Spanned) buf;

                    spanend = sp.nextSpanTransition(start, textLength,
                                                    ParagraphStyle.class);
                    spans = sp.getSpans(start, spanend, ParagraphStyle.class);
                    
                    align = mAlignment;
                    
                    for (int n = spans.length-1; n >= 0; n--) {
                        if (spans[n] instanceof AlignmentSpan) {
                            align = ((AlignmentSpan) spans[n]).getAlignment();
                            break;
                        }
                    }
                }
            }
            
            int dir = getParagraphDirection(i);
            int left = 0;
            int right = mWidth;

            if (spannedText) {
                final int length = spans.length;
                for (int n = 0; n < length; n++) {
                    if (spans[n] instanceof LeadingMarginSpan) {
                        LeadingMarginSpan margin = (LeadingMarginSpan) spans[n];

                        if (dir == DIR_RIGHT_TO_LEFT) {
                            margin.drawLeadingMargin(c, paint, right, dir, ltop,
                                                     lbaseline, lbottom, buf,
                                                     start, end, par, this);
                                
                            right -= margin.getLeadingMargin(par);
                        } else {
                            margin.drawLeadingMargin(c, paint, left, dir, ltop,
                                                     lbaseline, lbottom, buf,
                                                     start, end, par, this);

                            left += margin.getLeadingMargin(par);
                        }
                    }
                }
            }

            int x;
            if (align == Alignment.ALIGN_NORMAL) {
                if (dir == DIR_LEFT_TO_RIGHT) {
                    x = left;
                } else {
                    x = right;
                }
            } else {
                int max = (int)getLineMax(i, spans, false);
                if (align == Alignment.ALIGN_OPPOSITE) {
                    if (dir == DIR_RIGHT_TO_LEFT) {
                        x = left + max;
                    } else {
                        x = right - max;
                    }
                } else {
                    // Alignment.ALIGN_CENTER
                    max = max & ~1;
                    int half = (right - left - max) >> 1;
                    if (dir == DIR_RIGHT_TO_LEFT) {
                        x = right - half;
                    } else {
                        x = left + half;
                    }
                }
            }

            Directions directions = getLineDirections(i);
            boolean hasTab = getLineContainsTab(i);
            if (directions == DIRS_ALL_LEFT_TO_RIGHT &&
                    !spannedText && !hasTab) {
                if (Config.DEBUG) {
                    Assert.assertTrue(dir == DIR_LEFT_TO_RIGHT);
                    Assert.assertNotNull(c);
                }
                c.drawText(buf, start, end, x, lbaseline, paint);
            } else {
                drawText(c, buf, start, end, dir, directions,
                    x, ltop, lbaseline, lbottom, paint, mWorkPaint,
                    hasTab, spans);
            }
        }
    
private voiddrawText(android.graphics.Canvas canvas, java.lang.CharSequence text, int start, int end, int dir, android.text.Layout$Directions directions, float x, int top, int y, int bottom, TextPaint paint, TextPaint workPaint, boolean hasTabs, java.lang.Object[] parspans)

        char[] buf;
        if (!hasTabs) {
            if (directions == DIRS_ALL_LEFT_TO_RIGHT) {
                if (Config.DEBUG) {
                    Assert.assertTrue(DIR_LEFT_TO_RIGHT == dir);
                }
                Styled.drawText(canvas, text, start, end, dir, false, x, top, y, bottom, paint, workPaint, false);
                return;
            }
            buf = null;
        } else {
            buf = TextUtils.obtain(end - start);
            TextUtils.getChars(text, start, end, buf, 0);
        }

        float h = 0;

        int here = 0;
        for (int i = 0; i < directions.mDirections.length; i++) {
            int there = here + directions.mDirections[i];
            if (there > end - start)
                there = end - start;

            int segstart = here;
            for (int j = hasTabs ? here : there; j <= there; j++) {
                if (j == there || buf[j] == '\t") {
                    h += Styled.drawText(canvas, text,
                                         start + segstart, start + j,
                                         dir, (i & 1) != 0, x + h,
                                         top, y, bottom, paint, workPaint,
                                         start + j != end);

                    if (j != there && buf[j] == '\t")
                        h = dir * nextTab(text, start, end, h * dir, parspans);

                    segstart = j + 1;
                } else if (hasTabs && buf[j] >= 0xD800 && buf[j] <= 0xDFFF && j + 1 < there) {
                    int emoji = Character.codePointAt(buf, j);

                    if (emoji >= MIN_EMOJI && emoji <= MAX_EMOJI) {
                        Bitmap bm = EMOJI_FACTORY.
                            getBitmapFromAndroidPua(emoji);

                        if (bm != null) {
                            h += Styled.drawText(canvas, text,
                                                 start + segstart, start + j,
                                                 dir, (i & 1) != 0, x + h,
                                                 top, y, bottom, paint, workPaint,
                                                 start + j != end);

                            if (mEmojiRect == null) {
                                mEmojiRect = new RectF();
                            }

                            workPaint.set(paint);
                            Styled.measureText(paint, workPaint, text,
                                               start + j, start + j + 1,
                                               null);
                                        
                            float bitmapHeight = bm.getHeight();
                            float textHeight = -workPaint.ascent();
                            float scale = textHeight / bitmapHeight;
                            float width = bm.getWidth() * scale;

                            mEmojiRect.set(x + h, y - textHeight,
                                           x + h + width, y);

                            canvas.drawBitmap(bm, null, mEmojiRect, paint);
                            h += width;

                            j++;
                            segstart = j + 1;
                        }
                    }
                }
            }

            here = there;
        }

        if (hasTabs)
            TextUtils.recycle(buf);
    
private voidellipsize(int start, int end, int line, char[] dest, int destoff)

        int ellipsisCount = getEllipsisCount(line);

        if (ellipsisCount == 0) {
            return;
        }

        int ellipsisStart = getEllipsisStart(line);
        int linestart = getLineStart(line);

        for (int i = ellipsisStart; i < ellipsisStart + ellipsisCount; i++) {
            char c;

            if (i == ellipsisStart) {
                c = '\u2026"; // ellipsis
            } else {
                c = '\uFEFF"; // 0-width space
            }

            int a = i + linestart;

            if (a >= start && a < end) {
                dest[destoff + a - start] = c;
            }
        }
    
public final android.text.Layout$AlignmentgetAlignment()
Return the base alignment of this layout.

        return mAlignment;
    
public abstract intgetBottomPadding()
Returns the number of extra pixels of descent padding in the bottom line of the Layout.

public voidgetCursorPath(int point, android.graphics.Path dest, java.lang.CharSequence editingBuffer)
Fills in the specified Path with a representation of a cursor at the specified offset. This will often be a vertical line but can be multiple discontinous lines in text with multiple directionalities.

        dest.reset();

        int line = getLineForOffset(point);
        int top = getLineTop(line);
        int bottom = getLineTop(line+1);

        float h1 = getPrimaryHorizontal(point) - 0.5f;
        float h2 = getSecondaryHorizontal(point) - 0.5f;

        int caps = TextKeyListener.getMetaState(editingBuffer,
                                                KeyEvent.META_SHIFT_ON) |
                   TextKeyListener.getMetaState(editingBuffer,
                                                TextKeyListener.META_SELECTING);
        int fn = TextKeyListener.getMetaState(editingBuffer,
                                              KeyEvent.META_ALT_ON);
        int dist = 0;

        if (caps != 0 || fn != 0) {
            dist = (bottom - top) >> 2;

            if (fn != 0)
                top += dist;
            if (caps != 0)
                bottom -= dist;
        }

        if (h1 < 0.5f)
            h1 = 0.5f;
        if (h2 < 0.5f)
            h2 = 0.5f;

        if (h1 == h2) {
            dest.moveTo(h1, top);
            dest.lineTo(h1, bottom);
        } else {
            dest.moveTo(h1, top);
            dest.lineTo(h1, (top + bottom) >> 1);

            dest.moveTo(h2, (top + bottom) >> 1);
            dest.lineTo(h2, bottom);
        }

        if (caps == 2) {
            dest.moveTo(h2, bottom);
            dest.lineTo(h2 - dist, bottom + dist);
            dest.lineTo(h2, bottom);
            dest.lineTo(h2 + dist, bottom + dist);
        } else if (caps == 1) {
            dest.moveTo(h2, bottom);
            dest.lineTo(h2 - dist, bottom + dist);

            dest.moveTo(h2 - dist, bottom + dist - 0.5f);
            dest.lineTo(h2 + dist, bottom + dist - 0.5f);

            dest.moveTo(h2 + dist, bottom + dist);
            dest.lineTo(h2, bottom);
        }

        if (fn == 2) {
            dest.moveTo(h1, top);
            dest.lineTo(h1 - dist, top - dist);
            dest.lineTo(h1, top);
            dest.lineTo(h1 + dist, top - dist);
        } else if (fn == 1) {
            dest.moveTo(h1, top);
            dest.lineTo(h1 - dist, top - dist);

            dest.moveTo(h1 - dist, top - dist + 0.5f);
            dest.lineTo(h1 + dist, top - dist + 0.5f);

            dest.moveTo(h1 + dist, top - dist);
            dest.lineTo(h1, top);
        }
    
public static floatgetDesiredWidth(java.lang.CharSequence source, TextPaint paint)
Return how wide a layout would be necessary to display the specified text with one line per paragraph.

        return getDesiredWidth(source, 0, source.length(), paint);
    
public static floatgetDesiredWidth(java.lang.CharSequence source, int start, int end, TextPaint paint)
Return how wide a layout would be necessary to display the specified text slice with one line per paragraph.

        float need = 0;
        TextPaint workPaint = new TextPaint();

        int next;
        for (int i = start; i <= end; i = next) {
            next = TextUtils.indexOf(source, '\n", i, end);

            if (next < 0)
                next = end;

            float w = measureText(paint, workPaint,
                                  source, i, next, null, true, null);

            if (w > need)
                need = w;

            next++;
        }

        return need;
    
public abstract intgetEllipsisCount(int line)
Returns the number of characters to be ellipsized away, or 0 if no ellipsis is to take place.

public abstract intgetEllipsisStart(int line)
Return the offset of the first character to be ellipsized away, relative to the start of the line. (So 0 if the beginning of the line is ellipsized, not getLineStart().)

public intgetEllipsizedWidth()
Return the width to which this Layout is ellipsizing, or {@link #getWidth} if it is not doing anything special.

        return mWidth;
    
public intgetHeight()
Return the total height of this layout.

        return getLineTop(getLineCount());  // same as getLineBottom(getLineCount() - 1);
    
private floatgetHorizontal(int offset, boolean trailing, boolean alt)

        int line = getLineForOffset(offset);

        return getHorizontal(offset, trailing, alt, line);
    
private floatgetHorizontal(int offset, boolean trailing, boolean alt, int line)

        int start = getLineStart(line);
        int end = getLineVisibleEnd(line);
        int dir = getParagraphDirection(line);
        boolean tab = getLineContainsTab(line);
        Directions directions = getLineDirections(line);

        TabStopSpan[] tabs = null;
        if (tab && mText instanceof Spanned) {
            tabs = ((Spanned) mText).getSpans(start, end, TabStopSpan.class);
        }

        float wid = measureText(mPaint, mWorkPaint, mText, start, offset, end,
                                dir, directions, trailing, alt, tab, tabs);

        if (offset > end) {
            if (dir == DIR_RIGHT_TO_LEFT)
                wid -= measureText(mPaint, mWorkPaint,
                                   mText, end, offset, null, tab, tabs);
            else
                wid += measureText(mPaint, mWorkPaint,
                                   mText, end, offset, null, tab, tabs);
        }

        Alignment align = getParagraphAlignment(line);
        int left = getParagraphLeft(line);
        int right = getParagraphRight(line);

        if (align == Alignment.ALIGN_NORMAL) {
            if (dir == DIR_RIGHT_TO_LEFT)
                return right + wid;
            else
                return left + wid;
        }

        float max = getLineMax(line);

        if (align == Alignment.ALIGN_OPPOSITE) {
            if (dir == DIR_RIGHT_TO_LEFT)
                return left + max + wid;
            else
                return right - max + wid;
        } else { /* align == Alignment.ALIGN_CENTER */
            int imax = ((int) max) & ~1;

            if (dir == DIR_RIGHT_TO_LEFT)
                return right - (((right - left) - imax) / 2) + wid;
            else
                return left + ((right - left) - imax) / 2 + wid;
        }
    
public final intgetLineAscent(int line)
Get the ascent of the text on the specified line. The return value is negative to match the Paint.ascent() convention.

        // getLineTop(line+1) - getLineDescent(line) == getLineBaseLine(line)
        return getLineTop(line) - (getLineTop(line+1) - getLineDescent(line));
    
public final intgetLineBaseline(int line)
Return the vertical position of the baseline of the specified line.

        // getLineTop(line+1) == getLineTop(line)
        return getLineTop(line+1) - getLineDescent(line);
    
public final intgetLineBottom(int line)
Return the vertical position of the bottom of the specified line.

        return getLineTop(line + 1);
    
public intgetLineBounds(int line, android.graphics.Rect bounds)
Return the baseline for the specified line (0…getLineCount() - 1) If bounds is not null, return the top, left, right, bottom extents of the specified line in it.

param
line which line to examine (0..getLineCount() - 1)
param
bounds Optional. If not null, it returns the extent of the line
return
the Y-coordinate of the baseline

        if (bounds != null) {
            bounds.left = 0;     // ???
            bounds.top = getLineTop(line);
            bounds.right = mWidth;   // ???
            bounds.bottom = getLineBottom(line);
        }
        return getLineBaseline(line);
    
public abstract booleangetLineContainsTab(int line)
Returns whether the specified line contains one or more characters that need to be handled specially, like tabs or emoji.

public abstract intgetLineCount()
Return the number of lines of text in this layout.

public abstract intgetLineDescent(int line)
Return the descent of the specified line.

public abstract android.text.Layout$DirectionsgetLineDirections(int line)
Returns an array of directionalities for the specified line. The array alternates counts of characters in left-to-right and right-to-left segments of the line.

public final intgetLineEnd(int line)
Return the text offset after the last character on the specified line.

        return getLineStart(line + 1);
    
public intgetLineForOffset(int offset)
Get the line number on which the specified text offset appears. If you ask for a position before 0, you get 0; if you ask for a position beyond the end of the text, you get the last line.

        int high = getLineCount(), low = -1, guess;

        while (high - low > 1) {
            guess = (high + low) / 2;

            if (getLineStart(guess) > offset)
                high = guess;
            else
                low = guess;
        }

        if (low < 0)
            return 0;
        else
            return low;
    
public intgetLineForVertical(int vertical)
Get the line number corresponding to the specified vertical position. If you ask for a position above 0, you get 0; if you ask for a position below the bottom of the text, you get the last line.

        int high = getLineCount(), low = -1, guess;

        while (high - low > 1) {
            guess = (high + low) / 2;

            if (getLineTop(guess) > vertical)
                high = guess;
            else
                low = guess;
        }

        if (low < 0)
            return 0;
        else
            return low;
    
public floatgetLineLeft(int line)
Get the leftmost position that should be exposed for horizontal scrolling on the specified line.

        int dir = getParagraphDirection(line);
        Alignment align = getParagraphAlignment(line);

        if (align == Alignment.ALIGN_NORMAL) {
            if (dir == DIR_RIGHT_TO_LEFT)
                return getParagraphRight(line) - getLineMax(line);
            else
                return 0;
        } else if (align == Alignment.ALIGN_OPPOSITE) {
            if (dir == DIR_RIGHT_TO_LEFT)
                return 0;
            else
                return mWidth - getLineMax(line);
        } else { /* align == Alignment.ALIGN_CENTER */
            int left = getParagraphLeft(line);
            int right = getParagraphRight(line);
            int max = ((int) getLineMax(line)) & ~1;

            return left + ((right - left) - max) / 2;
        }
    
public floatgetLineMax(int line)
Gets the horizontal extent of the specified line, excluding trailing whitespace.

        return getLineMax(line, null, false);
    
private floatgetLineMax(int line, java.lang.Object[] tabs, boolean full)

        int start = getLineStart(line);
        int end;

        if (full) {
            end = getLineEnd(line);
        } else {
            end = getLineVisibleEnd(line);
        } 
        boolean tab = getLineContainsTab(line);

        if (tabs == null && tab && mText instanceof Spanned) {
            tabs = ((Spanned) mText).getSpans(start, end, TabStopSpan.class);
        }

        return measureText(mPaint, mWorkPaint,
                           mText, start, end, null, tab, tabs);
    
public floatgetLineRight(int line)
Get the rightmost position that should be exposed for horizontal scrolling on the specified line.

        int dir = getParagraphDirection(line);
        Alignment align = getParagraphAlignment(line);

        if (align == Alignment.ALIGN_NORMAL) {
            if (dir == DIR_RIGHT_TO_LEFT)
                return mWidth;
            else
                return getParagraphLeft(line) + getLineMax(line);
        } else if (align == Alignment.ALIGN_OPPOSITE) {
            if (dir == DIR_RIGHT_TO_LEFT)
                return getLineMax(line);
            else
                return mWidth;
        } else { /* align == Alignment.ALIGN_CENTER */
            int left = getParagraphLeft(line);
            int right = getParagraphRight(line);
            int max = ((int) getLineMax(line)) & ~1;

            return right - ((right - left) - max) / 2;
        }
    
public abstract intgetLineStart(int line)
Return the text offset of the beginning of the specified line. If the specified line is one beyond the last line, returns the end of the last line.

public abstract intgetLineTop(int line)
Return the vertical position of the top of the specified line. If the specified line is one beyond the last line, returns the bottom of the last line.

public intgetLineVisibleEnd(int line)
Return the text offset after the last visible character (so whitespace is not counted) on the specified line.

        return getLineVisibleEnd(line, getLineStart(line), getLineStart(line+1));
    
private intgetLineVisibleEnd(int line, int start, int end)

        if (Config.DEBUG) {
            Assert.assertTrue(getLineStart(line) == start && getLineStart(line+1) == end);
        }

        CharSequence text = mText;
        char ch;
        if (line == getLineCount() - 1) {
            return end;
        }

        for (; end > start; end--) {
            ch = text.charAt(end - 1);

            if (ch == '\n") {
                return end - 1;
            }

            if (ch != ' " && ch != '\t") {
                break;
            }

        }

        return end;
    
public floatgetLineWidth(int line)
Gets the horizontal extent of the specified line, including trailing whitespace.

        return getLineMax(line, null, true);
    
private intgetOffsetAtStartOf(int offset)

        if (offset == 0)
            return 0;

        CharSequence text = mText;
        char c = text.charAt(offset);

        if (c >= '\uDC00" && c <= '\uDFFF") {
            char c1 = text.charAt(offset - 1);

            if (c1 >= '\uD800" && c1 <= '\uDBFF")
                offset -= 1;
        }

        if (mSpannedText) {
            ReplacementSpan[] spans = ((Spanned) text).getSpans(offset, offset,
                                                       ReplacementSpan.class);

            for (int i = 0; i < spans.length; i++) {
                int start = ((Spanned) text).getSpanStart(spans[i]);
                int end = ((Spanned) text).getSpanEnd(spans[i]);

                if (start < offset && end > offset)
                    offset = start;
            }
        }

        return offset;
    
public intgetOffsetForHorizontal(int line, float horiz)
Get the character offset on the specfied line whose position is closest to the specified horizontal position.

        int max = getLineEnd(line) - 1;
        int min = getLineStart(line);
        Directions dirs = getLineDirections(line);

        if (line == getLineCount() - 1)
            max++;

        int best = min;
        float bestdist = Math.abs(getPrimaryHorizontal(best) - horiz);

        int here = min;
        for (int i = 0; i < dirs.mDirections.length; i++) {
            int there = here + dirs.mDirections[i];
            int swap = ((i & 1) == 0) ? 1 : -1;

            if (there > max)
                there = max;

            int high = there - 1 + 1, low = here + 1 - 1, guess;

            while (high - low > 1) {
                guess = (high + low) / 2;
                int adguess = getOffsetAtStartOf(guess);

                if (getPrimaryHorizontal(adguess) * swap >= horiz * swap)
                    high = guess;
                else
                    low = guess;
            }

            if (low < here + 1)
                low = here + 1;

            if (low < there) {
                low = getOffsetAtStartOf(low);

                float dist = Math.abs(getPrimaryHorizontal(low) - horiz);

                int aft = TextUtils.getOffsetAfter(mText, low);
                if (aft < there) {
                    float other = Math.abs(getPrimaryHorizontal(aft) - horiz);

                    if (other < dist) {
                        dist = other;
                        low = aft;
                    }
                }

                if (dist < bestdist) {
                    bestdist = dist;
                    best = low;   
                }
            }

            float dist = Math.abs(getPrimaryHorizontal(here) - horiz);

            if (dist < bestdist) {
                bestdist = dist;
                best = here;
            }

            here = there;
        }

        float dist = Math.abs(getPrimaryHorizontal(max) - horiz);

        if (dist < bestdist) {
            bestdist = dist;
            best = max;
        }

        return best;
    
public intgetOffsetToLeftOf(int offset)
Return the text offset that would be reached by moving left (possibly onto another line) from the specified offset.

        int line = getLineForOffset(offset);
        int start = getLineStart(line);
        int end = getLineEnd(line);
        Directions dirs = getLineDirections(line);

        if (line != getLineCount() - 1)
            end--;

        float horiz = getPrimaryHorizontal(offset);

        int best = offset;
        float besth = Integer.MIN_VALUE;
        int candidate;

        candidate = TextUtils.getOffsetBefore(mText, offset);
        if (candidate >= start && candidate <= end) {
            float h = getPrimaryHorizontal(candidate);

            if (h < horiz && h > besth) {
                best = candidate;
                besth = h;
            }
        }

        candidate = TextUtils.getOffsetAfter(mText, offset);
        if (candidate >= start && candidate <= end) {
            float h = getPrimaryHorizontal(candidate);

            if (h < horiz && h > besth) {
                best = candidate;
                besth = h;
            }
        }

        int here = start;
        for (int i = 0; i < dirs.mDirections.length; i++) {
            int there = here + dirs.mDirections[i];
            if (there > end)
                there = end;

            float h = getPrimaryHorizontal(here);

            if (h < horiz && h > besth) {
                best = here;
                besth = h;
            }

            candidate = TextUtils.getOffsetAfter(mText, here);
            if (candidate >= start && candidate <= end) {
                h = getPrimaryHorizontal(candidate);

                if (h < horiz && h > besth) {
                    best = candidate;
                    besth = h;
                }
            }

            candidate = TextUtils.getOffsetBefore(mText, there);
            if (candidate >= start && candidate <= end) {
                h = getPrimaryHorizontal(candidate);

                if (h < horiz && h > besth) {
                    best = candidate;
                    besth = h;
                }
            }

            here = there;
        }

        float h = getPrimaryHorizontal(end);

        if (h < horiz && h > besth) {
            best = end;
            besth = h;
        }

        if (best != offset)
            return best;

        int dir = getParagraphDirection(line);

        if (dir > 0) {
            if (line == 0)
                return best;
            else
                return getOffsetForHorizontal(line - 1, 10000);
        } else {
            if (line == getLineCount() - 1)
                return best;
            else
                return getOffsetForHorizontal(line + 1, 10000);
        }
    
public intgetOffsetToRightOf(int offset)
Return the text offset that would be reached by moving right (possibly onto another line) from the specified offset.

        int line = getLineForOffset(offset);
        int start = getLineStart(line);
        int end = getLineEnd(line);
        Directions dirs = getLineDirections(line);

        if (line != getLineCount() - 1)
            end--;

        float horiz = getPrimaryHorizontal(offset);

        int best = offset;
        float besth = Integer.MAX_VALUE;
        int candidate;

        candidate = TextUtils.getOffsetBefore(mText, offset);
        if (candidate >= start && candidate <= end) {
            float h = getPrimaryHorizontal(candidate);

            if (h > horiz && h < besth) {
                best = candidate;
                besth = h;
            }
        }

        candidate = TextUtils.getOffsetAfter(mText, offset);
        if (candidate >= start && candidate <= end) {
            float h = getPrimaryHorizontal(candidate);

            if (h > horiz && h < besth) {
                best = candidate;
                besth = h;
            }
        }

        int here = start;
        for (int i = 0; i < dirs.mDirections.length; i++) {
            int there = here + dirs.mDirections[i];
            if (there > end)
                there = end;

            float h = getPrimaryHorizontal(here);

            if (h > horiz && h < besth) {
                best = here;
                besth = h;
            }

            candidate = TextUtils.getOffsetAfter(mText, here);
            if (candidate >= start && candidate <= end) {
                h = getPrimaryHorizontal(candidate);

                if (h > horiz && h < besth) {
                    best = candidate;
                    besth = h;
                }
            }

            candidate = TextUtils.getOffsetBefore(mText, there);
            if (candidate >= start && candidate <= end) {
                h = getPrimaryHorizontal(candidate);

                if (h > horiz && h < besth) {
                    best = candidate;
                    besth = h;
                }
            }

            here = there;
        }

        float h = getPrimaryHorizontal(end);

        if (h > horiz && h < besth) {
            best = end;
            besth = h;
        }

        if (best != offset)
            return best;

        int dir = getParagraphDirection(line);

        if (dir > 0) {
            if (line == getLineCount() - 1)
                return best;
            else
                return getOffsetForHorizontal(line + 1, -10000);
        } else {
            if (line == 0)
                return best;
            else
                return getOffsetForHorizontal(line - 1, -10000);
        }
    
public final TextPaintgetPaint()
Return the base Paint properties for this layout. Do NOT change the paint, which may result in funny drawing for this layout.

        return mPaint;
    
public final android.text.Layout$AlignmentgetParagraphAlignment(int line)
Get the alignment of the specified paragraph, taking into account markup attached to it.

        Alignment align = mAlignment;

        if (mSpannedText) {
            Spanned sp = (Spanned) mText;
            AlignmentSpan[] spans = sp.getSpans(getLineStart(line),
                                                getLineEnd(line),
                                                AlignmentSpan.class);

            int spanLength = spans.length;
            if (spanLength > 0) {
                align = spans[spanLength-1].getAlignment();
            }
        }

        return align;
    
public abstract intgetParagraphDirection(int line)
Returns the primary directionality of the paragraph containing the specified line.

public final intgetParagraphLeft(int line)
Get the left edge of the specified paragraph, inset by left margins.

        int dir = getParagraphDirection(line);

        int left = 0;

        boolean par = false;
        int off = getLineStart(line);
        if (off == 0 || mText.charAt(off - 1) == '\n")
            par = true;

        if (dir == DIR_LEFT_TO_RIGHT) {
            if (mSpannedText) {
                Spanned sp = (Spanned) mText;
                LeadingMarginSpan[] spans = sp.getSpans(getLineStart(line),
                                                        getLineEnd(line),
                                                        LeadingMarginSpan.class);

                for (int i = 0; i < spans.length; i++) {
                    left += spans[i].getLeadingMargin(par);
                }
            }
        }

        return left;
    
public final intgetParagraphRight(int line)
Get the right edge of the specified paragraph, inset by right margins.

        int dir = getParagraphDirection(line);

        int right = mWidth;

        boolean par = false;
        int off = getLineStart(line);
        if (off == 0 || mText.charAt(off - 1) == '\n")
            par = true;


        if (dir == DIR_RIGHT_TO_LEFT) {
            if (mSpannedText) {
                Spanned sp = (Spanned) mText;
                LeadingMarginSpan[] spans = sp.getSpans(getLineStart(line),
                                                        getLineEnd(line),
                                                        LeadingMarginSpan.class);

                for (int i = 0; i < spans.length; i++) {
                    right -= spans[i].getLeadingMargin(par);
                }
            }
        }

        return right;
    
public floatgetPrimaryHorizontal(int offset)
Get the primary horizontal position for the specified text offset. This is the location where a new character would be inserted in the paragraph's primary direction.

        return getHorizontal(offset, false, true);
    
public floatgetSecondaryHorizontal(int offset)
Get the secondary horizontal position for the specified text offset. This is the location where a new character would be inserted in the direction other than the paragraph's primary direction.

        return getHorizontal(offset, true, true);
    
public voidgetSelectionPath(int start, int end, android.graphics.Path dest)
Fills in the specified Path with a representation of a highlight between the specified offsets. This will often be a rectangle or a potentially discontinuous set of rectangles. If the start and end are the same, the returned path is empty.

        dest.reset();

        if (start == end)
            return;

        if (end < start) {
            int temp = end;
            end = start;
            start = temp;
        }

        int startline = getLineForOffset(start);
        int endline = getLineForOffset(end);

        int top = getLineTop(startline);
        int bottom = getLineBottom(endline);

        if (startline == endline) {
            addSelection(startline, start, end, top, bottom, dest);
        } else {
            final float width = mWidth;

            addSelection(startline, start, getLineEnd(startline),
                         top, getLineBottom(startline), dest);
            
            if (getParagraphDirection(startline) == DIR_RIGHT_TO_LEFT)
                dest.addRect(getLineLeft(startline), top,
                              0, getLineBottom(startline), Path.Direction.CW);
            else
                dest.addRect(getLineRight(startline), top,
                              width, getLineBottom(startline), Path.Direction.CW);

            for (int i = startline + 1; i < endline; i++) {
                top = getLineTop(i);
                bottom = getLineBottom(i);
                dest.addRect(0, top, width, bottom, Path.Direction.CW);
            }

            top = getLineTop(endline);
            bottom = getLineBottom(endline);

            addSelection(endline, getLineStart(endline), end,
                         top, bottom, dest);

            if (getParagraphDirection(endline) == DIR_RIGHT_TO_LEFT)
                dest.addRect(width, top, getLineRight(endline), bottom, Path.Direction.CW);
            else
                dest.addRect(0, top, getLineLeft(endline), bottom, Path.Direction.CW);
        }
    
public final floatgetSpacingAdd()
Return the number of units of leading that are added to each line.

        return mSpacingAdd;
    
public final floatgetSpacingMultiplier()
Return what the text height is multiplied by to get the line height.

        return mSpacingMult;
    
public final java.lang.CharSequencegetText()
Return the text that is displayed by this Layout.

        return mText;
    
public abstract intgetTopPadding()
Returns the (negative) number of extra pixels of ascent padding in the top line of the Layout.

public final intgetWidth()
Return the width of this layout.

        return mWidth;
    
public final voidincreaseWidthTo(int wid)
Increase the width of this layout to the specified width. Be careful to use this only when you know it is appropriate -- it does not cause the text to reflow to use the full new width.

        if (wid < mWidth) {
            throw new RuntimeException("attempted to reduce Layout width");
        }

        mWidth = wid;
    
protected final booleanisSpanned()

        return mSpannedText;
    
private static floatmeasureText(TextPaint paint, TextPaint workPaint, java.lang.CharSequence text, int start, int offset, int end, int dir, android.text.Layout$Directions directions, boolean trailing, boolean alt, boolean hasTabs, java.lang.Object[] tabs)

        char[] buf = null;

        if (hasTabs) {
            buf = TextUtils.obtain(end - start);
            TextUtils.getChars(text, start, end, buf, 0);
        }

        float h = 0;

        if (alt) {
            if (dir == DIR_RIGHT_TO_LEFT)
                trailing = !trailing;
        }

        int here = 0;
        for (int i = 0; i < directions.mDirections.length; i++) {
            if (alt)
                trailing = !trailing;

            int there = here + directions.mDirections[i];
            if (there > end - start)
                there = end - start;

            int segstart = here;
            for (int j = hasTabs ? here : there; j <= there; j++) {
                int codept = 0;
                Bitmap bm = null;

                if (hasTabs && j < there) {
                    codept = buf[j];
                }

                if (codept >= 0xD800 && codept <= 0xDFFF && j + 1 < there) {
                    codept = Character.codePointAt(buf, j);

                    if (codept >= MIN_EMOJI && codept <= MAX_EMOJI) {
                        bm = EMOJI_FACTORY.getBitmapFromAndroidPua(codept);
                    }
                }

                if (j == there || codept == '\t" || bm != null) {
                    float segw;

                    if (offset < start + j ||
                       (trailing && offset <= start + j)) {
                        if (dir == DIR_LEFT_TO_RIGHT && (i & 1) == 0) {
                            h += Styled.measureText(paint, workPaint, text,
                                                    start + segstart, offset,
                                                    null);
                            return h;
                        }

                        if (dir == DIR_RIGHT_TO_LEFT && (i & 1) != 0) {
                            h -= Styled.measureText(paint, workPaint, text,
                                                    start + segstart, offset,
                                                    null);
                            return h;
                        }
                    }

                    segw = Styled.measureText(paint, workPaint, text,
                                              start + segstart, start + j,
                                              null);

                    if (offset < start + j ||
                        (trailing && offset <= start + j)) {
                        if (dir == DIR_LEFT_TO_RIGHT) {
                            h += segw - Styled.measureText(paint, workPaint,
                                                           text,
                                                           start + segstart,
                                                           offset, null);
                            return h;
                        }

                        if (dir == DIR_RIGHT_TO_LEFT) {
                            h -= segw - Styled.measureText(paint, workPaint,
                                                           text,
                                                           start + segstart,
                                                           offset, null);
                            return h;
                        }
                    }

                    if (dir == DIR_RIGHT_TO_LEFT)
                        h -= segw;
                    else
                        h += segw;

                    if (j != there && buf[j] == '\t") {
                        if (offset == start + j)
                            return h;

                        h = dir * nextTab(text, start, end, h * dir, tabs);
                    }

                    if (bm != null) {
                        workPaint.set(paint);
                        Styled.measureText(paint, workPaint, text,
                                           offset, offset + 1, null);

                        float wid = (float) bm.getWidth() *
                                    -workPaint.ascent() / bm.getHeight();

                        if (dir == DIR_RIGHT_TO_LEFT) {
                            h -= wid;
                        } else {
                            h += wid;
                        }

                        j++;
                    }

                    segstart = j + 1;
                }
            }

            here = there;
        }

        if (hasTabs)
            TextUtils.recycle(buf);

        return h;
    
static floatmeasureText(TextPaint paint, TextPaint workPaint, java.lang.CharSequence text, int start, int end, Paint.FontMetricsInt fm, boolean hasTabs, java.lang.Object[] tabs)

        char[] buf = null;
  
        if (hasTabs) {
            buf = TextUtils.obtain(end - start);
            TextUtils.getChars(text, start, end, buf, 0);
        }

        int len = end - start;

        int here = 0;
        float h = 0;
        int ab = 0, be = 0;
        int top = 0, bot = 0;

        if (fm != null) {
            fm.ascent = 0;
            fm.descent = 0;
        }

        for (int i = hasTabs ? 0 : len; i <= len; i++) {
            int codept = 0;
            Bitmap bm = null;

            if (hasTabs && i < len) {
                codept = buf[i];
            }

            if (codept >= 0xD800 && codept <= 0xDFFF && i < len) {
                codept = Character.codePointAt(buf, i);

                if (codept >= MIN_EMOJI && codept <= MAX_EMOJI) {
                    bm = EMOJI_FACTORY.getBitmapFromAndroidPua(codept);
                }
            }

            if (i == len || codept == '\t" || bm != null) {
                workPaint.baselineShift = 0;

                h += Styled.measureText(paint, workPaint, text,
                                        start + here, start + i,
                                        fm);

                if (fm != null) {
                    if (workPaint.baselineShift < 0) {
                        fm.ascent += workPaint.baselineShift;
                        fm.top += workPaint.baselineShift;
                    } else {
                        fm.descent += workPaint.baselineShift;
                        fm.bottom += workPaint.baselineShift;
                    }
                }

                if (i != len) {
                    if (bm == null) {
                        h = nextTab(text, start, end, h, tabs);
                    } else {
                        workPaint.set(paint);
                        Styled.measureText(paint, workPaint, text,
                                           start + i, start + i + 1, null);

                        float wid = (float) bm.getWidth() *
                                    -workPaint.ascent() / bm.getHeight();

                        h += wid;
                        i++;
                    }
                }

                if (fm != null) {
                    if (fm.ascent < ab) {
                        ab = fm.ascent;
                    }
                    if (fm.descent > be) {
                        be = fm.descent;
                    }

                    if (fm.top < top) {
                        top = fm.top;
                    }
                    if (fm.bottom > bot) {
                        bot = fm.bottom;
                    }

                    /*
                     * No need to take bitmap height into account here,
                     * since it is scaled to match the text height.
                     */
                }

                here = i + 1;
            }
        }

        if (fm != null) {
            fm.ascent = ab;
            fm.descent = be;
            fm.top = top;
            fm.bottom = bot;
        }

        if (hasTabs)
            TextUtils.recycle(buf);

        return h;
    
static floatnextTab(java.lang.CharSequence text, int start, int end, float h, java.lang.Object[] tabs)

        float nh = Float.MAX_VALUE;
        boolean alltabs = false;

        if (text instanceof Spanned) {
            if (tabs == null) {
                tabs = ((Spanned) text).getSpans(start, end, TabStopSpan.class);
                alltabs = true;
            }

            for (int i = 0; i < tabs.length; i++) {
                if (!alltabs) {
                    if (!(tabs[i] instanceof TabStopSpan))
                        continue;
                }

                int where = ((TabStopSpan) tabs[i]).getTabStop();

                if (where < nh && where > h)
                    nh = where;
            }

            if (nh != Float.MAX_VALUE)
                return nh;
        }

        return ((int) ((h + TAB_INCREMENT) / TAB_INCREMENT)) * TAB_INCREMENT;
    
voidreplaceWith(java.lang.CharSequence text, TextPaint paint, int width, android.text.Layout$Alignment align, float spacingmult, float spacingadd)
Replace constructor properties of this Layout with new ones. Be careful.

        if (width < 0) {
            throw new IllegalArgumentException("Layout: " + width + " < 0");
        }

        mText = text;
        mPaint = paint;
        mWidth = width;
        mAlignment = align;
        mSpacingMult = spacingmult;
        mSpacingAdd = spacingadd;
        mSpannedText = text instanceof Spanned;