auto import from //depot/cupcake/@135843
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
new file mode 100644
index 0000000..95acf9d
--- /dev/null
+++ b/core/java/android/text/Layout.java
@@ -0,0 +1,1747 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.Path;
+import com.android.internal.util.ArrayUtils;
+import android.util.Config;
+
+import junit.framework.Assert;
+import android.text.style.*;
+import android.text.method.TextKeyListener;
+import android.view.KeyEvent;
+
+/**
+ * A base class that manages text layout in visual elements on
+ * the screen.
+ * <p>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}.
+ */
+public abstract class Layout {
+ /**
+ * Return how wide a layout would be necessary to display the
+ * specified text with one line per paragraph.
+ */
+ public static float getDesiredWidth(CharSequence source,
+ TextPaint paint) {
+ return getDesiredWidth(source, 0, source.length(), paint);
+ }
+
+ /**
+ * Return how wide a layout would be necessary to display the
+ * specified text slice with one line per paragraph.
+ */
+ public static float getDesiredWidth(CharSequence source,
+ int start, int end,
+ TextPaint paint) {
+ 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;
+ }
+
+ /**
+ * Subclasses of Layout use this constructor to set the display text,
+ * width, and other standard properties.
+ */
+ protected Layout(CharSequence text, TextPaint paint,
+ int width, Alignment align,
+ float spacingmult, float spacingadd) {
+ 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;
+ }
+
+ /**
+ * Replace constructor properties of this Layout with new ones. Be careful.
+ */
+ /* package */ void replaceWith(CharSequence text, TextPaint paint,
+ int width, Alignment align,
+ float spacingmult, float spacingadd) {
+ 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;
+ }
+
+ /**
+ * Draw this Layout on the specified Canvas.
+ */
+ public void draw(Canvas c) {
+ draw(c, null, null, 0);
+ }
+
+ /**
+ * Draw the specified rectangle from this Layout on the specified Canvas,
+ * with the specified path drawn between the background and the text.
+ */
+ public void draw(Canvas c, Path highlight, Paint highlightpaint,
+ int cursorOffsetVertical) {
+ 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);
+ }
+ }
+ }
+
+ /**
+ * Return the text that is displayed by this Layout.
+ */
+ public final CharSequence getText() {
+ return mText;
+ }
+
+ /**
+ * Return the base Paint properties for this layout.
+ * Do NOT change the paint, which may result in funny
+ * drawing for this layout.
+ */
+ public final TextPaint getPaint() {
+ return mPaint;
+ }
+
+ /**
+ * Return the width of this layout.
+ */
+ public final int getWidth() {
+ return mWidth;
+ }
+
+ /**
+ * Return the width to which this Layout is ellipsizing, or
+ * {@link #getWidth} if it is not doing anything special.
+ */
+ public int getEllipsizedWidth() {
+ return mWidth;
+ }
+
+ /**
+ * 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.
+ */
+ public final void increaseWidthTo(int wid) {
+ if (wid < mWidth) {
+ throw new RuntimeException("attempted to reduce Layout width");
+ }
+
+ mWidth = wid;
+ }
+
+ /**
+ * Return the total height of this layout.
+ */
+ public int getHeight() {
+ return getLineTop(getLineCount()); // same as getLineBottom(getLineCount() - 1);
+ }
+
+ /**
+ * Return the base alignment of this layout.
+ */
+ public final Alignment getAlignment() {
+ return mAlignment;
+ }
+
+ /**
+ * Return what the text height is multiplied by to get the line height.
+ */
+ public final float getSpacingMultiplier() {
+ return mSpacingMult;
+ }
+
+ /**
+ * Return the number of units of leading that are added to each line.
+ */
+ public final float getSpacingAdd() {
+ return mSpacingAdd;
+ }
+
+ /**
+ * Return the number of lines of text in this layout.
+ */
+ public abstract int getLineCount();
+
+ /**
+ * 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
+ */
+ public int getLineBounds(int line, Rect bounds) {
+ if (bounds != null) {
+ bounds.left = 0; // ???
+ bounds.top = getLineTop(line);
+ bounds.right = mWidth; // ???
+ bounds.bottom = getLineBottom(line);
+ }
+ return getLineBaseline(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 abstract int getLineTop(int line);
+
+ /**
+ * Return the descent of the specified line.
+ */
+ public abstract int getLineDescent(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 int getLineStart(int line);
+
+ /**
+ * Returns the primary directionality of the paragraph containing
+ * the specified line.
+ */
+ public abstract int getParagraphDirection(int line);
+
+ /**
+ * Returns whether the specified line contains one or more tabs.
+ */
+ public abstract boolean getLineContainsTab(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 abstract Directions getLineDirections(int line);
+
+ /**
+ * Returns the (negative) number of extra pixels of ascent padding in the
+ * top line of the Layout.
+ */
+ public abstract int getTopPadding();
+
+ /**
+ * Returns the number of extra pixels of descent padding in the
+ * bottom line of the Layout.
+ */
+ public abstract int getBottomPadding();
+
+ /**
+ * 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.
+ */
+ public float getPrimaryHorizontal(int offset) {
+ return getHorizontal(offset, false, true);
+ }
+
+ /**
+ * 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.
+ */
+ public float getSecondaryHorizontal(int offset) {
+ return getHorizontal(offset, true, true);
+ }
+
+ private float getHorizontal(int offset, boolean trailing, boolean alt) {
+ int line = getLineForOffset(offset);
+
+ return getHorizontal(offset, trailing, alt, line);
+ }
+
+ private float getHorizontal(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;
+ }
+ }
+
+ /**
+ * Get the leftmost position that should be exposed for horizontal
+ * scrolling on the specified line.
+ */
+ public float getLineLeft(int 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;
+ }
+ }
+
+ /**
+ * Get the rightmost position that should be exposed for horizontal
+ * scrolling on the specified line.
+ */
+ public float getLineRight(int 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;
+ }
+ }
+
+ /**
+ * Gets the horizontal extent of the specified line, excluding
+ * trailing whitespace.
+ */
+ public float getLineMax(int line) {
+ return getLineMax(line, null, false);
+ }
+
+ /**
+ * Gets the horizontal extent of the specified line, including
+ * trailing whitespace.
+ */
+ public float getLineWidth(int line) {
+ return getLineMax(line, null, true);
+ }
+
+ private float getLineMax(int line, 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);
+ }
+
+ /**
+ * 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.
+ */
+ // FIXME: It may be faster to do a linear search for layouts without many lines.
+ public int getLineForVertical(int vertical) {
+ 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;
+ }
+
+ /**
+ * 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.
+ */
+ public int getLineForOffset(int offset) {
+ 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;
+ }
+
+ /**
+ * Get the character offset on the specfied line whose position is
+ * closest to the specified horizontal position.
+ */
+ public int getOffsetForHorizontal(int line, float horiz) {
+ 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;
+ }
+
+ /**
+ * Return the text offset after the last character on the specified line.
+ */
+ public final int getLineEnd(int line) {
+ return getLineStart(line + 1);
+ }
+
+ /**
+ * Return the text offset after the last visible character (so whitespace
+ * is not counted) on the specified line.
+ */
+ public int getLineVisibleEnd(int line) {
+ return getLineVisibleEnd(line, getLineStart(line), getLineStart(line+1));
+ }
+
+ private int getLineVisibleEnd(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;
+ }
+
+ /**
+ * Return the vertical position of the bottom of the specified line.
+ */
+ public final int getLineBottom(int line) {
+ return getLineTop(line + 1);
+ }
+
+ /**
+ * Return the vertical position of the baseline of the specified line.
+ */
+ public final int getLineBaseline(int line) {
+ // getLineTop(line+1) == getLineTop(line)
+ return getLineTop(line+1) - getLineDescent(line);
+ }
+
+ /**
+ * Get the ascent of the text on the specified line.
+ * The return value is negative to match the Paint.ascent() convention.
+ */
+ public final int getLineAscent(int line) {
+ // getLineTop(line+1) - getLineDescent(line) == getLineBaseLine(line)
+ return getLineTop(line) - (getLineTop(line+1) - getLineDescent(line));
+ }
+
+ /**
+ * Return the text offset that would be reached by moving left
+ * (possibly onto another line) from the specified offset.
+ */
+ public int getOffsetToLeftOf(int 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);
+ }
+ }
+
+ /**
+ * Return the text offset that would be reached by moving right
+ * (possibly onto another line) from the specified offset.
+ */
+ public int getOffsetToRightOf(int 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);
+ }
+ }
+
+ private int getOffsetAtStartOf(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;
+ }
+
+ /**
+ * 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.
+ */
+ public void getCursorPath(int point, Path dest,
+ CharSequence editingBuffer) {
+ 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);
+ }
+ }
+
+ private void addSelection(int line, int start, int end,
+ int top, int bottom, 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;
+ }
+ }
+
+ /**
+ * 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.
+ */
+ public void getSelectionPath(int start, int end, Path dest) {
+ 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);
+ }
+ }
+
+ /**
+ * Get the alignment of the specified paragraph, taking into account
+ * markup attached to it.
+ */
+ public final Alignment getParagraphAlignment(int line) {
+ 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;
+ }
+
+ /**
+ * Get the left edge of the specified paragraph, inset by left margins.
+ */
+ public final int getParagraphLeft(int line) {
+ 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;
+ }
+
+ /**
+ * Get the right edge of the specified paragraph, inset by right margins.
+ */
+ public final int getParagraphRight(int line) {
+ 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;
+ }
+
+ private static void drawText(Canvas canvas,
+ CharSequence text, int start, int end,
+ int dir, Directions directions,
+ float x, int top, int y, int bottom,
+ TextPaint paint,
+ TextPaint workPaint,
+ boolean hasTabs, 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;
+ }
+ }
+
+ here = there;
+ }
+
+ if (hasTabs)
+ TextUtils.recycle(buf);
+ }
+
+ private static float measureText(TextPaint paint,
+ TextPaint workPaint,
+ CharSequence text,
+ int start, int offset, int end,
+ int dir, Directions directions,
+ boolean trailing, boolean alt,
+ boolean hasTabs, 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++) {
+ if (j == there || buf[j] == '\t') {
+ 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);
+ }
+
+ segstart = j + 1;
+ }
+ }
+
+ here = there;
+ }
+
+ if (hasTabs)
+ TextUtils.recycle(buf);
+
+ return h;
+ }
+
+ /* package */ static float measureText(TextPaint paint,
+ TextPaint workPaint,
+ CharSequence text,
+ int start, int end,
+ Paint.FontMetricsInt fm,
+ boolean hasTabs, 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++) {
+ if (i == len || buf[i] == '\t') {
+ 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)
+ h = nextTab(text, start, end, h, tabs);
+
+ 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;
+ }
+ }
+
+ 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;
+ }
+
+ /* package */ static float nextTab(CharSequence text, int start, int end,
+ float h, 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;
+ }
+
+ protected final boolean isSpanned() {
+ return mSpannedText;
+ }
+
+ private void ellipsize(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;
+ }
+ }
+ }
+
+ /**
+ * Stores information about bidirectional (left-to-right or right-to-left)
+ * text within the layout of a line. TODO: This work is not complete
+ * or correct and will be fleshed out in a later revision.
+ */
+ public static class Directions {
+ private short[] mDirections;
+
+ /* package */ Directions(short[] dirs) {
+ mDirections = dirs;
+ }
+ }
+
+ /**
+ * 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 abstract int getEllipsisStart(int line);
+ /**
+ * Returns the number of characters to be ellipsized away, or 0 if
+ * no ellipsis is to take place.
+ */
+ public abstract int getEllipsisCount(int line);
+
+ /* package */ static class Ellipsizer implements CharSequence, GetChars {
+ /* package */ CharSequence mText;
+ /* package */ Layout mLayout;
+ /* package */ int mWidth;
+ /* package */ TextUtils.TruncateAt mMethod;
+
+ public Ellipsizer(CharSequence s) {
+ mText = s;
+ }
+
+ public char charAt(int off) {
+ char[] buf = TextUtils.obtain(1);
+ getChars(off, off + 1, buf, 0);
+ char ret = buf[0];
+
+ TextUtils.recycle(buf);
+ return ret;
+ }
+
+ public void getChars(int start, int end, char[] dest, int destoff) {
+ int line1 = mLayout.getLineForOffset(start);
+ int line2 = mLayout.getLineForOffset(end);
+
+ TextUtils.getChars(mText, start, end, dest, destoff);
+
+ for (int i = line1; i <= line2; i++) {
+ mLayout.ellipsize(start, end, i, dest, destoff);
+ }
+ }
+
+ public int length() {
+ return mText.length();
+ }
+
+ public CharSequence subSequence(int start, int end) {
+ char[] s = new char[end - start];
+ getChars(start, end, s, 0);
+ return new String(s);
+ }
+
+ public String toString() {
+ char[] s = new char[length()];
+ getChars(0, length(), s, 0);
+ return new String(s);
+ }
+
+ }
+
+ /* package */ static class SpannedEllipsizer
+ extends Ellipsizer implements Spanned {
+ private Spanned mSpanned;
+
+ public SpannedEllipsizer(CharSequence display) {
+ super(display);
+ mSpanned = (Spanned) display;
+ }
+
+ public <T> T[] getSpans(int start, int end, Class<T> type) {
+ return mSpanned.getSpans(start, end, type);
+ }
+
+ public int getSpanStart(Object tag) {
+ return mSpanned.getSpanStart(tag);
+ }
+
+ public int getSpanEnd(Object tag) {
+ return mSpanned.getSpanEnd(tag);
+ }
+
+ public int getSpanFlags(Object tag) {
+ return mSpanned.getSpanFlags(tag);
+ }
+
+ public int nextSpanTransition(int start, int limit, Class type) {
+ return mSpanned.nextSpanTransition(start, limit, type);
+ }
+
+ public CharSequence subSequence(int start, int end) {
+ char[] s = new char[end - start];
+ getChars(start, end, s, 0);
+
+ SpannableString ss = new SpannableString(new String(s));
+ TextUtils.copySpansFrom(mSpanned, start, end, Object.class, ss, 0);
+ return ss;
+ }
+ }
+
+ private CharSequence mText;
+ private TextPaint mPaint;
+ /* package */ TextPaint mWorkPaint;
+ private int mWidth;
+ private Alignment mAlignment = Alignment.ALIGN_NORMAL;
+ private float mSpacingMult;
+ private float mSpacingAdd;
+ private static Rect sTempRect = new Rect();
+ private boolean mSpannedText;
+
+ public static final int DIR_LEFT_TO_RIGHT = 1;
+ public static final int DIR_RIGHT_TO_LEFT = -1;
+
+ public enum Alignment {
+ ALIGN_NORMAL,
+ ALIGN_OPPOSITE,
+ ALIGN_CENTER,
+ // XXX ALIGN_LEFT,
+ // XXX ALIGN_RIGHT,
+ }
+
+ private static final int TAB_INCREMENT = 20;
+
+ /* package */ static final Directions DIRS_ALL_LEFT_TO_RIGHT =
+ new Directions(new short[] { 32767 });
+ /* package */ static final Directions DIRS_ALL_RIGHT_TO_LEFT =
+ new Directions(new short[] { 0, 32767 });
+
+}
+