diff options
| author | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 18:28:45 -0800 |
|---|---|---|
| committer | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 18:28:45 -0800 |
| commit | d83a98f4ce9cfa908f5c54bbd70f03eec07e7553 (patch) | |
| tree | 4b825dc642cb6eb9a060e54bf8d69288fbee4904 /core/java/android/text/Layout.java | |
| parent | 076357b8567458d4b6dfdcf839ef751634cd2bfb (diff) | |
auto import from //depot/cupcake/@135843
Diffstat (limited to 'core/java/android/text/Layout.java')
| -rw-r--r-- | core/java/android/text/Layout.java | 1747 |
1 files changed, 0 insertions, 1747 deletions
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java deleted file mode 100644 index 95acf9d23617..000000000000 --- a/core/java/android/text/Layout.java +++ /dev/null @@ -1,1747 +0,0 @@ -/* - * 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 }); - -} - |
