Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Refactor Styled utility functions into reusable objects.

This takes utility functions from Styled and a few other classes and
incorporates them into two new utility classes, TextLine and
MeasuredText.  The main point of this is to support shaping by skia,
to experiment with how this will look, this also introduces
character-based Arabic shaping.

MeasuredText is used by code that determines line breaks by generating
and examining character widths in logical order.  Factoring the code
in this way makes it usable by the ellipsize functions in TextUtils as
well as by StaticLayout.  This class takes over the caching of widths
and chars arrays that was previously performed by StyledText.  A small
number of MeasuredText objects are themselves cached by the class and
accesed using static obtain and recycle methods.  Generally only these
few cached instances are ever created.

TextLine is used by code that draws or measures text on a line.  This
unifies the line measuring and rendering code, and pushes assumptions
about how rtl text is treated closer to the points where skia code is
invoked.  TextLine implements the functions that were previously
provided by Styled, working on member arrays rather than
explicitly-passed arguments.  It implements the same kind of static
cache as MeasuredText.

TextLine and MeasureText simulate arabic glyph generation and shaping
by using ArabicShaping, ported with very minor changes from ICU4J's
ArabicShaping.  This class generates shaped Arabic glyphs and Lam-Alef
ligatures using Unicode presentation forms.  ArabicShaping is not
intended to be permanent, but to be replaced by real shaping from the
skia layer. It is introduced in order to emulate the behavior of real
shaping so that higher level code dealing with rendering shaped text
and cursor movement over ligatures can be developed and tested; it
also provides basic-level support for Arabic.

Since cursor movement depends on conjuncts whose formation is
font-dependent, cursor movement code that was formerly in Layout and
StaticLayout was moved into TextLine so that it can work on the shaped
text.

Other than these changes, the other major change is a rework of the
ellipsize utility functions to combine multiple branches into fewer
branches with additional state.

Updated copyright notices on new files.

Change-Id: I492cb58b51f5aaf6f14cb1419bdbed49eac5ba29
  • Loading branch information...
commit e8e45f2c05cb3b6d23f30c8f96d8e0b3699cea7a 1 parent 5d470d0
@dougfelt dougfelt authored
View
129 core/java/android/text/AndroidBidi.java
@@ -16,6 +16,8 @@
package android.text;
+import android.text.Layout.Directions;
+
/**
* Access the ICU bidi implementation.
* @hide
@@ -44,5 +46,132 @@ public static int bidi(int dir, char[] chs, byte[] chInfo, int n, boolean haveIn
return result;
}
+ /**
+ * Returns run direction information for a line within a paragraph.
+ *
+ * @param dir base line direction, either Layout.DIR_LEFT_TO_RIGHT or
+ * Layout.DIR_RIGHT_TO_LEFT
+ * @param levels levels as returned from {@link #bidi}
+ * @param lstart start of the line in the levels array
+ * @param chars the character array (used to determine whitespace)
+ * @param cstart the start of the line in the chars array
+ * @param len the length of the line
+ * @return the directions
+ */
+ public static Directions directions(int dir, byte[] levels, int lstart,
+ char[] chars, int cstart, int len) {
+
+ int baseLevel = dir == Layout.DIR_LEFT_TO_RIGHT ? 0 : 1;
+ int curLevel = levels[lstart];
+ int minLevel = curLevel;
+ int runCount = 1;
+ for (int i = lstart + 1, e = lstart + len; i < e; ++i) {
+ int level = levels[i];
+ if (level != curLevel) {
+ curLevel = level;
+ ++runCount;
+ }
+ }
+
+ // add final run for trailing counter-directional whitespace
+ int visLen = len;
+ if ((curLevel & 1) != (baseLevel & 1)) {
+ // look for visible end
+ while (--visLen >= 0) {
+ char ch = chars[cstart + visLen];
+
+ if (ch == '\n') {
+ --visLen;
+ break;
+ }
+
+ if (ch != ' ' && ch != '\t') {
+ break;
+ }
+ }
+ ++visLen;
+ if (visLen != len) {
+ ++runCount;
+ }
+ }
+
+ if (runCount == 1 && minLevel == baseLevel) {
+ // we're done, only one run on this line
+ if ((minLevel & 1) != 0) {
+ return Layout.DIRS_ALL_RIGHT_TO_LEFT;
+ }
+ return Layout.DIRS_ALL_LEFT_TO_RIGHT;
+ }
+
+ int[] ld = new int[runCount * 2];
+ int maxLevel = minLevel;
+ int levelBits = minLevel << Layout.RUN_LEVEL_SHIFT;
+ {
+ // Start of first pair is always 0, we write
+ // length then start at each new run, and the
+ // last run length after we're done.
+ int n = 1;
+ int prev = lstart;
+ curLevel = minLevel;
+ for (int i = lstart, e = lstart + visLen; i < e; ++i) {
+ int level = levels[i];
+ if (level != curLevel) {
+ curLevel = level;
+ if (level > maxLevel) {
+ maxLevel = level;
+ } else if (level < minLevel) {
+ minLevel = level;
+ }
+ // XXX ignore run length limit of 2^RUN_LEVEL_SHIFT
+ ld[n++] = (i - prev) | levelBits;
+ ld[n++] = i - lstart;
+ levelBits = curLevel << Layout.RUN_LEVEL_SHIFT;
+ prev = i;
+ }
+ }
+ ld[n] = (lstart + visLen - prev) | levelBits;
+ if (visLen < len) {
+ ld[++n] = visLen;
+ ld[++n] = (len - visLen) | (baseLevel << Layout.RUN_LEVEL_SHIFT);
+ }
+ }
+
+ // See if we need to swap any runs.
+ // If the min level run direction doesn't match the base
+ // direction, we always need to swap (at this point
+ // we have more than one run).
+ // Otherwise, we don't need to swap the lowest level.
+ // Since there are no logically adjacent runs at the same
+ // level, if the max level is the same as the (new) min
+ // level, we have a series of alternating levels that
+ // is already in order, so there's no more to do.
+ //
+ boolean swap;
+ if ((minLevel & 1) == baseLevel) {
+ minLevel += 1;
+ swap = maxLevel > minLevel;
+ } else {
+ swap = runCount > 1;
+ }
+ if (swap) {
+ for (int level = maxLevel - 1; level >= minLevel; --level) {
+ for (int i = 0; i < ld.length; i += 2) {
+ if (levels[ld[i]] >= level) {
+ int e = i + 2;
+ while (e < ld.length && levels[ld[e]] >= level) {
+ e += 2;
+ }
+ for (int low = i, hi = e - 2; low < hi; low += 2, hi -= 2) {
+ int x = ld[low]; ld[low] = ld[hi]; ld[hi] = x;
+ x = ld[low+1]; ld[low+1] = ld[hi+1]; ld[hi+1] = x;
+ }
+ i = e + 2;
+ }
+ }
+ }
+ }
+ return new Directions(ld);
+ }
+
private native static int runBidi(int dir, char[] chs, byte[] chInfo, int n, boolean haveInfo);
}
View
25 core/java/android/text/BoringLayout.java
@@ -208,11 +208,11 @@ public BoringLayout(CharSequence source,
* width because the width that was passed in was for the
* full text, not the ellipsized form.
*/
- synchronized (sTemp) {
- mMax = (int) (FloatMath.ceil(Styled.measureText(paint, sTemp,
- source, 0, source.length(),
- null)));
- }
+ TextLine line = TextLine.obtain();
+ line.set(paint, source, 0, source.length(), Layout.DIR_LEFT_TO_RIGHT,
+ Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null);
+ mMax = (int) FloatMath.ceil(line.metrics(null));
+ TextLine.recycle(line);
}
if (includepad) {
@@ -276,14 +276,13 @@ public static Metrics isBoring(CharSequence text, TextPaint paint,
if (fm == null) {
fm = new Metrics();
}
-
- int wid;
- synchronized (sTemp) {
- wid = (int) (FloatMath.ceil(Styled.measureText(paint, sTemp,
- text, 0, text.length(), fm)));
- }
- fm.width = wid;
+ TextLine line = TextLine.obtain();
+ line.set(paint, text, 0, text.length(), Layout.DIR_LEFT_TO_RIGHT,
+ Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null);
+ fm.width = (int) FloatMath.ceil(line.metrics(fm));
+ TextLine.recycle(line);
+
return fm;
} else {
return null;
@@ -389,7 +388,7 @@ public void ellipsized(int start, int end) {
public static class Metrics extends Paint.FontMetricsInt {
public int width;
-
+
@Override public String toString() {
return super.toString() + " width=" + width;
}
View
631 core/java/android/text/Layout.java
@@ -19,12 +19,10 @@
import com.android.internal.util.ArrayUtils;
import android.emoji.EmojiFactory;
-import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
-import android.graphics.RectF;
import android.text.method.TextKeyListener;
import android.text.style.AlignmentSpan;
import android.text.style.LeadingMarginSpan;
@@ -60,9 +58,7 @@
MIN_EMOJI = -1;
MAX_EMOJI = -1;
}
- };
-
- private RectF mEmojiRect;
+ }
/**
* Return how wide a layout must be in order to display the
@@ -91,8 +87,8 @@ public static float getDesiredWidth(CharSequence source,
next = end;
// note, omits trailing paragraph char
- float w = measureText(paint, workPaint,
- source, i, next, null, true, null);
+ float w = measurePara(paint, workPaint,
+ source, i, next, true, null);
if (w > need)
need = w;
@@ -122,6 +118,15 @@ protected Layout(CharSequence text, TextPaint paint,
if (width < 0)
throw new IllegalArgumentException("Layout: " + width + " < 0");
+ // Ensure paint doesn't have baselineShift set.
+ // While normally we don't modify the paint the user passed in,
+ // we were already doing this in Styled.drawUniformRun with both
+ // baselineShift and bgColor. We probably should reevaluate bgColor.
+ if (paint != null) {
+ paint.bgColor = 0;
+ paint.baselineShift = 0;
+ }
+
mText = text;
mPaint = paint;
mWorkPaint = new TextPaint();
@@ -262,6 +267,7 @@ public void draw(Canvas c, Path highlight, Paint highlightPaint,
Alignment align = mAlignment;
+ TextLine tl = TextLine.obtain();
// Next draw the lines, one at a time.
// the baseline is the top of the following line minus the current
// line's descent.
@@ -373,11 +379,11 @@ public void draw(Canvas c, Path highlight, Paint highlightPaint,
// XXX: assumes there's nothing additional to be done
c.drawText(buf, start, end, x, lbaseline, paint);
} else {
- drawText(c, buf, start, end, dir, directions,
- x, ltop, lbaseline, lbottom, paint, mWorkPaint,
- hasTab, spans);
+ tl.set(paint, buf, start, end, dir, directions, hasTab, spans);
+ tl.draw(c, x, ltop, lbaseline, lbottom);
}
}
+ TextLine.recycle(tl);
}
/**
@@ -639,7 +645,7 @@ private float getHorizontal(int offset, boolean trailing) {
private float getHorizontal(int offset, boolean trailing, int line) {
int start = getLineStart(line);
- int end = getLineVisibleEnd(line);
+ int end = getLineEnd(line);
int dir = getParagraphDirection(line);
boolean tab = getLineContainsTab(line);
Directions directions = getLineDirections(line);
@@ -649,17 +655,10 @@ private float getHorizontal(int offset, boolean trailing, int line) {
tabs = ((Spanned) mText).getSpans(start, end, TabStopSpan.class);
}
- float wid = measureText(mPaint, mWorkPaint, mText, start, offset, end,
- dir, directions, trailing, 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);
- }
+ TextLine tl = TextLine.obtain();
+ tl.set(mPaint, mText, start, end, dir, directions, tab, tabs);
+ float wid = tl.measure(offset - start, trailing, null);
+ TextLine.recycle(tl);
Alignment align = getParagraphAlignment(line);
int left = getParagraphLeft(line);
@@ -761,21 +760,15 @@ public float getLineWidth(int line) {
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);
- }
+ int end = full ? getLineEnd(line) : getLineVisibleEnd(line);
+ boolean hasTabs = getLineContainsTab(line);
+ Directions directions = getLineDirections(line);
- return measureText(mPaint, mWorkPaint,
- mText, start, end, null, tab, tabs);
+ TextLine tl = TextLine.obtain();
+ tl.set(mPaint, mText, start, end, 1, directions, hasTabs, tabs);
+ float width = tl.metrics(null);
+ TextLine.recycle(tl);
+ return width;
}
/**
@@ -975,165 +968,49 @@ public int getOffsetToRightOf(int offset) {
return getOffsetToLeftRightOf(offset, false);
}
- // 1) The caret marks the leading edge of a character. The character
- // logically before it might be on a different level, and the active caret
- // position is on the character at the lower level. If that character
- // was the previous character, the caret is on its trailing edge.
- // 2) Take this character/edge and move it in the indicated direction.
- // This gives you a new character and a new edge.
- // 3) This position is between two visually adjacent characters. One of
- // these might be at a lower level. The active position is on the
- // character at the lower level.
- // 4) If the active position is on the trailing edge of the character,
- // the new caret position is the following logical character, else it
- // is the character.
private int getOffsetToLeftRightOf(int caret, boolean toLeft) {
int line = getLineForOffset(caret);
int lineStart = getLineStart(line);
int lineEnd = getLineEnd(line);
-
- boolean paraIsRtl = getParagraphDirection(line) == -1;
- int[] runs = getLineDirections(line).mDirections;
-
- int runIndex, runLevel = 0, runStart = lineStart, runLimit = lineEnd, newCaret = -1;
- boolean trailing = false;
-
- if (caret == lineStart) {
- runIndex = -2;
- } else if (caret == lineEnd) {
- runIndex = runs.length;
- } else {
- // First, get information about the run containing the character with
- // the active caret.
- for (runIndex = 0; runIndex < runs.length; runIndex += 2) {
- runStart = lineStart + runs[runIndex];
- if (caret >= runStart) {
- runLimit = runStart + (runs[runIndex+1] & RUN_LENGTH_MASK);
- if (runLimit > lineEnd) {
- runLimit = lineEnd;
- }
- if (caret < runLimit) {
- runLevel = (runs[runIndex+1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK;
- if (caret == runStart) {
- // The caret is on a run boundary, see if we should
- // use the position on the trailing edge of the previous
- // logical character instead.
- int prevRunIndex, prevRunLevel, prevRunStart, prevRunLimit;
- int pos = caret - 1;
- for (prevRunIndex = 0; prevRunIndex < runs.length; prevRunIndex += 2) {
- prevRunStart = lineStart + runs[prevRunIndex];
- if (pos >= prevRunStart) {
- prevRunLimit = prevRunStart + (runs[prevRunIndex+1] & RUN_LENGTH_MASK);
- if (prevRunLimit > lineEnd) {
- prevRunLimit = lineEnd;
- }
- if (pos < prevRunLimit) {
- prevRunLevel = (runs[prevRunIndex+1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK;
- if (prevRunLevel < runLevel) {
- // Start from logically previous character.
- runIndex = prevRunIndex;
- runLevel = prevRunLevel;
- runStart = prevRunStart;
- runLimit = prevRunLimit;
- trailing = true;
- break;
- }
- }
- }
- }
+ int lineDir = getParagraphDirection(line);
+
+ boolean advance = toLeft == (lineDir == DIR_RIGHT_TO_LEFT);
+ if (caret == (advance ? lineEnd : lineStart)) {
+ // walking off line, so look at the line we're headed to
+ if (caret == lineStart) {
+ if (line > 0) {
+ --line;
+ } else {
+ return caret; // at very start, don't move
+ }
+ } else {
+ if (line < getLineCount() - 1) {
+ ++line;
+ } else {
+ return caret; // at very end, don't move
}
- break;
- }
- }
- }
-
- // caret might be = lineEnd. This is generally a space or paragraph
- // separator and has an associated run, but might be the end of
- // text, in which case it doesn't. If that happens, we ran off the
- // end of the run list, and runIndex == runs.length. In this case,
- // we are at a run boundary so we skip the below test.
- if (runIndex != runs.length) {
- boolean rtlRun = (runLevel & 0x1) != 0;
- boolean advance = toLeft == rtlRun;
- if (caret != (advance ? runLimit : runStart) || advance != trailing) {
- // Moving within or into the run, so we can move logically.
- newCaret = getOffsetBeforeAfter(caret, advance);
- // If the new position is internal to the run, we're at the strong
- // position already so we're finished.
- if (newCaret != (advance ? runLimit : runStart)) {
- return newCaret;
- }
- }
- }
- }
-
- // If newCaret is -1, we're starting at a run boundary and crossing
- // into another run. Otherwise we've arrived at a run boundary, and
- // need to figure out which character to attach to. Note we might
- // need to run this twice, if we cross a run boundary and end up at
- // another run boundary.
- while (true) {
- boolean advance = toLeft == paraIsRtl;
- int otherRunIndex = runIndex + (advance ? 2 : -2);
- if (otherRunIndex >= 0 && otherRunIndex < runs.length) {
- int otherRunStart = lineStart + runs[otherRunIndex];
- int otherRunLimit = otherRunStart + (runs[otherRunIndex+1] & RUN_LENGTH_MASK);
- if (otherRunLimit > lineEnd) {
- otherRunLimit = lineEnd;
- }
- int otherRunLevel = runs[otherRunIndex+1] >>> RUN_LEVEL_SHIFT & RUN_LEVEL_MASK;
- boolean otherRunIsRtl = (otherRunLevel & 1) != 0;
-
- advance = toLeft == otherRunIsRtl;
- if (newCaret == -1) {
- newCaret = getOffsetBeforeAfter(advance ? otherRunStart : otherRunLimit, advance);
- if (newCaret == (advance ? otherRunLimit : otherRunStart)) {
- // Crossed and ended up at a new boundary, repeat a second and final time.
- runIndex = otherRunIndex;
- runLevel = otherRunLevel;
- continue;
- }
- break;
}
- // The new caret is at a boundary.
- if (otherRunLevel < runLevel) {
- // The strong character is in the other run.
- newCaret = advance ? otherRunStart : otherRunLimit;
+ lineStart = getLineStart(line);
+ lineEnd = getLineEnd(line);
+ int newDir = getParagraphDirection(line);
+ if (newDir != lineDir) {
+ // unusual case. we want to walk onto the line, but it runs
+ // in a different direction than this one, so we fake movement
+ // in the opposite direction.
+ toLeft = !toLeft;
+ lineDir = newDir;
}
- break;
- }
-
- if (newCaret == -1) {
- // We're walking off the end of the line. The paragraph
- // level is always equal to or lower than any internal level, so
- // the boundaries get the strong caret.
- newCaret = getOffsetBeforeAfter(caret, advance);
- break;
- }
- // Else we've arrived at the end of the line. That's a strong position.
- // We might have arrived here by crossing over a run with no internal
- // breaks and dropping out of the above loop before advancing one final
- // time, so reset the caret.
- // Note, we use '<=' below to handle a situation where the only run
- // on the line is a counter-directional run. If we're not advancing,
- // we can end up at the 'lineEnd' position but the caret we want is at
- // the lineStart.
- if (newCaret <= lineEnd) {
- newCaret = advance ? lineEnd : lineStart;
- }
- break;
}
- return newCaret;
- }
+ Directions directions = getLineDirections(line);
- // utility, maybe just roll into the above.
- private int getOffsetBeforeAfter(int offset, boolean after) {
- if (after) {
- return TextUtils.getOffsetAfter(mText, offset);
- }
- return TextUtils.getOffsetBefore(mText, offset);
+ TextLine tl = TextLine.obtain();
+ // XXX: we don't care about tabs
+ tl.set(mPaint, mText, lineStart, lineEnd, lineDir, directions, false, null);
+ caret = lineStart + tl.getOffsetToLeftRightOf(caret - lineStart, toLeft);
+ tl = TextLine.recycle(tl);
+ return caret;
}
private int getOffsetAtStartOf(int offset) {
@@ -1427,373 +1304,28 @@ public final int getParagraphRight(int line) {
return right;
}
- private 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 (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 lastRunIndex = directions.mDirections.length - 2;
- for (int i = 0; i < directions.mDirections.length; i += 2) {
- int here = start + directions.mDirections[i];
- int there = here + (directions.mDirections[i+1] & RUN_LENGTH_MASK);
- boolean runIsRtl = (directions.mDirections[i+1] & RUN_RTL_FLAG) != 0;
-
- if (there > end)
- there = end;
-
- int segstart = here;
- for (int j = hasTabs ? here : there; j <= there; j++) {
- int pj = j - start;
- if (j == there || buf[pj] == '\t') {
- h += Styled.drawText(canvas, text,
- segstart, j,
- dir, runIsRtl, x + h,
- top, y, bottom, paint, workPaint,
- i != lastRunIndex || j != end);
-
- if (j != there)
- h = dir * nextTab(text, start, end, h * dir, parspans);
-
- segstart = j + 1;
- } else if (hasTabs && buf[pj] >= 0xD800 && buf[pj] <= 0xDFFF && j + 1 < there) {
- int emoji = Character.codePointAt(buf, pj);
-
- if (emoji >= MIN_EMOJI && emoji <= MAX_EMOJI) {
- Bitmap bm = EMOJI_FACTORY.
- getBitmapFromAndroidPua(emoji);
-
- if (bm != null) {
- h += Styled.drawText(canvas, text,
- segstart, j,
- dir, runIsRtl, x + h,
- top, y, bottom, paint, workPaint,
- i != lastRunIndex || j != end);
-
- if (mEmojiRect == null) {
- mEmojiRect = new RectF();
- }
-
- workPaint.set(paint);
- Styled.measureText(paint, workPaint, text,
- j, 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;
- }
- }
- }
- }
- }
-
- if (hasTabs)
- TextUtils.recycle(buf);
- }
-
- /**
- * Get the distance from the margin to the requested edge of the character
- * at offset on the line from start to end. Trailing indicates the edge
- * should be the trailing edge of the character at offset-1.
- */
- /* package */ static float measureText(TextPaint paint,
- TextPaint workPaint,
- CharSequence text,
- int start, int offset, int end,
- int dir, Directions directions,
- boolean trailing, boolean hasTabs,
- Object[] tabs) {
- char[] buf = null;
-
- if (hasTabs) {
- buf = TextUtils.obtain(end - start);
- TextUtils.getChars(text, start, end, buf, 0);
- }
-
- float h = 0;
-
- int target = trailing ? offset - 1 : offset;
- if (target < start) {
- return 0;
- }
-
- int[] runs = directions.mDirections;
- for (int i = 0; i < runs.length; i += 2) {
- int here = start + runs[i];
- int there = here + (runs[i+1] & RUN_LENGTH_MASK);
- if (there > end) {
- there = end;
- }
- boolean runIsRtl = (runs[i+1] & RUN_RTL_FLAG) != 0;
-
- 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 - start];
- }
-
- if (codept >= 0xD800 && codept <= 0xDFFF && j + 1 < there) {
- codept = Character.codePointAt(buf, j - start);
-
- if (codept >= MIN_EMOJI && codept <= MAX_EMOJI) {
- bm = EMOJI_FACTORY.getBitmapFromAndroidPua(codept);
- }
- }
-
- if (j == there || codept == '\t' || bm != null) {
- float segw;
-
- boolean inSegment = target >= segstart && target < j;
-
- if (inSegment) {
- if (dir == DIR_LEFT_TO_RIGHT && !runIsRtl) {
- h += Styled.measureText(paint, workPaint, text,
- segstart, offset,
- null);
- return h;
- }
-
- if (dir == DIR_RIGHT_TO_LEFT && runIsRtl) {
- h -= Styled.measureText(paint, workPaint, text,
- segstart, offset,
- null);
- return h;
- }
- }
-
- // XXX Style.measureText assumes LTR?
- segw = Styled.measureText(paint, workPaint, text,
- segstart, j,
- null);
-
- if (inSegment) {
- if (dir == DIR_LEFT_TO_RIGHT) {
- h += segw - Styled.measureText(paint, workPaint,
- text,
- segstart,
- offset, null);
- return h;
- }
-
- if (dir == DIR_RIGHT_TO_LEFT) {
- h -= segw - Styled.measureText(paint, workPaint,
- text,
- segstart,
- offset, null);
- return h;
- }
- }
-
- if (dir == DIR_RIGHT_TO_LEFT)
- h -= segw;
- else
- h += segw;
-
- if (j != there && buf[j - start] == '\t') {
- if (offset == j)
- return h;
-
- h = dir * nextTab(text, start, end, h * dir, tabs);
-
- if (target == j) {
- return h;
- }
- }
-
- if (bm != null) {
- workPaint.set(paint);
- Styled.measureText(paint, workPaint, text,
- j, j + 2, null);
-
- float wid = bm.getWidth() *
- -workPaint.ascent() / bm.getHeight();
-
- if (dir == DIR_RIGHT_TO_LEFT) {
- h -= wid;
- } else {
- h += wid;
- }
-
- j++;
- }
-
- segstart = j + 1;
- }
- }
- }
-
- if (hasTabs)
- TextUtils.recycle(buf);
-
- return h;
- }
-
- /**
- * Measure width of a run of text on a single line that is known to all be
- * in the same direction as the paragraph base direction. Returns the width,
- * and the line metrics in fm if fm is not null.
- *
- * @param paint the paint for the text; will not be modified
- * @param workPaint paint available for modification
- * @param text text
- * @param start start of the line
- * @param end limit of the line
- * @param fm object to return integer metrics in, can be null
- * @param hasTabs true if it is known that the line has tabs
- * @param tabs tab position information
- * @return the width of the text from start to end
- */
- /* 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 lastPos = 0;
- float width = 0;
- int ascent = 0, descent = 0, top = 0, bottom = 0;
-
- if (fm != null) {
- fm.ascent = 0;
- fm.descent = 0;
- }
-
- for (int pos = hasTabs ? 0 : len; pos <= len; pos++) {
- int codept = 0;
- Bitmap bm = null;
-
- if (hasTabs && pos < len) {
- codept = buf[pos];
- }
-
- if (codept >= 0xD800 && codept <= 0xDFFF && pos < len) {
- codept = Character.codePointAt(buf, pos);
-
- if (codept >= MIN_EMOJI && codept <= MAX_EMOJI) {
- bm = EMOJI_FACTORY.getBitmapFromAndroidPua(codept);
- }
- }
-
- if (pos == len || codept == '\t' || bm != null) {
- workPaint.baselineShift = 0;
-
- // XXX Styled.measureText assumes the run direction is LTR,
- // but it might not be. Check this.
- width += Styled.measureText(paint, workPaint, text,
- start + lastPos, start + pos,
- 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 (pos != len) {
- if (bm == null) {
- // no emoji, must have hit a tab
- width = nextTab(text, start, end, width, tabs);
- } else {
- // This sets up workPaint with the font on the emoji
- // text, so that we can extract the ascent and scale.
-
- // We can't use the result of the previous call to
- // measureText because the emoji might have its own style.
- // We have to initialize workPaint here because if the
- // text is unstyled measureText might not use workPaint
- // at all.
- workPaint.set(paint);
- Styled.measureText(paint, workPaint, text,
- start + pos, start + pos + 1, null);
-
- width += bm.getWidth() *
- -workPaint.ascent() / bm.getHeight();
-
- // Since we had an emoji, we bump past the second half
- // of the surrogate pair.
- pos++;
- }
- }
-
- if (fm != null) {
- if (fm.ascent < ascent) {
- ascent = fm.ascent;
- }
- if (fm.descent > descent) {
- descent = fm.descent;
- }
-
- if (fm.top < top) {
- top = fm.top;
- }
- if (fm.bottom > bottom) {
- bottom = fm.bottom;
- }
-
- // No need to take bitmap height into account here,
- // since it is scaled to match the text height.
- }
-
- lastPos = pos + 1;
+ /* package */
+ static float measurePara(TextPaint paint, TextPaint workPaint,
+ CharSequence text, int start, int end, boolean hasTabs,
+ Object[] tabs) {
+
+ MeasuredText mt = MeasuredText.obtain();
+ TextLine tl = TextLine.obtain();
+ try {
+ mt.setPara(text, start, end, DIR_REQUEST_LTR);
+ Directions directions;
+ if (mt.mEasy){
+ directions = DIRS_ALL_LEFT_TO_RIGHT;
+ } else {
+ directions = AndroidBidi.directions(mt.mDir, mt.mLevels,
+ 0, mt.mChars, 0, mt.mLen);
}
+ tl.set(paint, text, start, end, 1, directions, hasTabs, tabs);
+ return tl.metrics(null);
+ } finally {
+ TextLine.recycle(tl);
+ MeasuredText.recycle(mt);
}
-
- if (fm != null) {
- fm.ascent = ascent;
- fm.descent = descent;
- fm.top = top;
- fm.bottom = bottom;
- }
-
- if (hasTabs)
- TextUtils.recycle(buf);
-
- return width;
}
/**
@@ -1898,6 +1430,7 @@ private void ellipsize(int start, int end, int line,
* 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.
View
250 core/java/android/text/MeasuredText.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2010 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 com.android.internal.util.ArrayUtils;
+
+import android.graphics.Paint;
+import android.icu.text.ArabicShaping;
+import android.text.style.MetricAffectingSpan;
+import android.text.style.ReplacementSpan;
+import android.util.Log;
+
+/**
+ * @hide
+ */
+class MeasuredText {
+ /* package */ CharSequence mText;
+ /* package */ int mTextStart;
+ /* package */ float[] mWidths;
+ /* package */ char[] mChars;
+ /* package */ byte[] mLevels;
+ /* package */ int mDir;
+ /* package */ boolean mEasy;
+ /* package */ int mLen;
+ private int mPos;
+ private float[] mWorkWidths; // temp buffer for Paint.measureText, arrgh
+ private TextPaint mWorkPaint;
+
+ private MeasuredText() {
+ mWorkPaint = new TextPaint();
+ }
+
+ private static MeasuredText[] cached = new MeasuredText[3];
+
+ /* package */
+ static MeasuredText obtain() {
+ MeasuredText mt;
+ synchronized (cached) {
+ for (int i = cached.length; --i >= 0;) {
+ if (cached[i] != null) {
+ mt = cached[i];
+ cached[i] = null;
+ return mt;
+ }
+ }
+ }
+ mt = new MeasuredText();
+ Log.e("MEAS", "new: " + mt);
+ return mt;
+ }
+
+ /* package */
+ static MeasuredText recycle(MeasuredText mt) {
+ mt.mText = null;
+ if (mt.mLen < 1000) {
+ synchronized(cached) {
+ for (int i = 0; i < cached.length; ++i) {
+ if (cached[i] == null) {
+ cached[i] = mt;
+ break;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Analyzes text for
+ * bidirectional runs. Allocates working buffers.
+ */
+ /* package */
+ void setPara(CharSequence text, int start, int end, int bidiRequest) {
+ mText = text;
+ mTextStart = start;
+
+ int len = end - start;
+ mLen = len;
+ mPos = 0;
+
+ if (mWidths == null || mWidths.length < len) {
+ mWidths = new float[ArrayUtils.idealFloatArraySize(len)];
+ mWorkWidths = new float[mWidths.length];
+ }
+ if (mChars == null || mChars.length < len) {
+ mChars = new char[ArrayUtils.idealCharArraySize(len)];
+ }
+ TextUtils.getChars(text, start, end, mChars, 0);
+
+ if (text instanceof Spanned) {
+ Spanned spanned = (Spanned) text;
+ ReplacementSpan[] spans = spanned.getSpans(start, end,
+ ReplacementSpan.class);
+
+ for (int i = 0; i < spans.length; i++) {
+ int startInPara = spanned.getSpanStart(spans[i]) - start;
+ int endInPara = spanned.getSpanEnd(spans[i]) - start;
+ for (int j = startInPara; j < endInPara; j++) {
+ mChars[j] = '\uFFFC';
+ }
+ }
+ }
+
+ if (TextUtils.doesNotNeedBidi(mChars, 0, len)) {
+ mDir = 1;
+ mEasy = true;
+ } else {
+ if (mLevels == null || mLevels.length < len) {
+ mLevels = new byte[ArrayUtils.idealByteArraySize(len)];
+ }
+ mDir = AndroidBidi.bidi(bidiRequest, mChars, mLevels, len, false);
+ mEasy = false;
+
+ // shape
+ if (mLen > 0) {
+ byte[] levels = mLevels;
+ char[] chars = mChars;
+ byte level = levels[0];
+ int pi = 0;
+ for (int i = 1, e = mLen;; ++i) {
+ if (i == e || levels[i] != level) {
+ if ((level & 0x1) != 0) {
+ AndroidCharacter.mirror(chars, pi, i - pi);
+ ArabicShaping.SHAPER.shape(chars, pi, i - pi);
+ }
+ if (i == e) {
+ break;
+ }
+ pi = i;
+ level = levels[i];
+ }
+ }
+ }
+ }
+ }
+
+ float addStyleRun(TextPaint paint, int len, Paint.FontMetricsInt fm) {
+ int p = mPos;
+ float[] w = mWidths, ww = mWorkWidths;
+ int count = paint.getTextWidths(mChars, p, len, ww);
+ int width = 0;
+ if (count < len) {
+ // must have surrogate pairs in here, pad out the array with zero
+ // for the trailing surrogates
+ char[] chars = mChars;
+ for (int i = 0, e = mLen; i < count; ++i) {
+ width += (w[p++] = ww[i]);
+ if (p < e && chars[p] >= '\udc00' && chars[p] < '\ue000' &&
+ chars[p-1] >= '\ud800' && chars[p-1] < '\udc00') {
+ w[p++] = 0;
+ }
+ }
+ } else {
+ for (int i = 0; i < len; ++i) {
+ width += (w[p++] = ww[i]);
+ }
+ }
+ mPos = p;
+ if (fm != null) {
+ paint.getFontMetricsInt(fm);
+ }
+ return width;
+ }
+
+ float addStyleRun(TextPaint paint, MetricAffectingSpan[] spans, int len,
+ Paint.FontMetricsInt fm) {
+
+ TextPaint workPaint = mWorkPaint;
+ workPaint.set(paint);
+ // XXX paint should not have a baseline shift, but...
+ workPaint.baselineShift = 0;
+
+ ReplacementSpan replacement = null;
+ for (int i = 0; i < spans.length; i++) {
+ MetricAffectingSpan span = spans[i];
+ if (span instanceof ReplacementSpan) {
+ replacement = (ReplacementSpan)span;
+ } else {
+ span.updateMeasureState(workPaint);
+ }
+ }
+
+ float wid;
+ if (replacement == null) {
+ wid = addStyleRun(workPaint, len, fm);
+ } else {
+ // Use original text. Shouldn't matter.
+ wid = replacement.getSize(workPaint, mText, mTextStart + mPos,
+ mTextStart + mPos + len, fm);
+ float[] w = mWidths;
+ w[mPos] = wid;
+ for (int i = mPos + 1, e = mPos + len; i < e; i++)
+ w[i] = 0;
+ }
+
+ if (fm != null) {
+ if (workPaint.baselineShift < 0) {
+ fm.ascent += workPaint.baselineShift;
+ fm.top += workPaint.baselineShift;
+ } else {
+ fm.descent += workPaint.baselineShift;
+ fm.bottom += workPaint.baselineShift;
+ }
+ }
+
+ return wid;
+ }
+
+ int breakText(int start, int limit, boolean forwards, float width) {
+ float[] w = mWidths;
+ if (forwards) {
+ for (int i = start; i < limit; ++i) {
+ if ((width -= w[i]) < 0) {
+ return i - start;
+ }
+ }
+ } else {
+ for (int i = limit; --i >= start;) {
+ if ((width -= w[i]) < 0) {
+ return limit - i -1;
+ }
+ }
+ }
+
+ return limit - start;
+ }
+
+ float measure(int start, int limit) {
+ float width = 0;
+ float[] w = mWidths;
+ for (int i = start; i < limit; ++i) {
+ width += w[i];
+ }
+ return width;
+ }
+}
View
432 core/java/android/text/StaticLayout.java
@@ -23,8 +23,6 @@
import android.text.style.LeadingMarginSpan;
import android.text.style.LineHeightSpan;
import android.text.style.MetricAffectingSpan;
-import android.text.style.ReplacementSpan;
-import android.util.Log;
/**
* StaticLayout is a Layout for text that will not be edited after it
@@ -96,13 +94,13 @@ public StaticLayout(CharSequence source, int bufstart, int bufend,
mLineDirections = new Directions[
ArrayUtils.idealIntArraySize(2 * mColumns)];
+ mMeasured = MeasuredText.obtain();
+
generate(source, bufstart, bufend, paint, outerwidth, align,
spacingmult, spacingadd, includepad, includepad,
ellipsize != null, ellipsizedWidth, ellipsize);
- mChdirs = null;
- mChs = null;
- mWidths = null;
+ mMeasured = MeasuredText.recycle(mMeasured);
mFontMetricsInt = null;
}
@@ -113,6 +111,7 @@ public StaticLayout(CharSequence source, int bufstart, int bufend,
mLines = new int[ArrayUtils.idealIntArraySize(2 * mColumns)];
mLineDirections = new Directions[
ArrayUtils.idealIntArraySize(2 * mColumns)];
+ mMeasured = MeasuredText.obtain();
}
/* package */ void generate(CharSequence source, int bufstart, int bufend,
@@ -130,38 +129,22 @@ public StaticLayout(CharSequence source, int bufstart, int bufend,
Paint.FontMetricsInt fm = mFontMetricsInt;
int[] choosehtv = null;
- int end = TextUtils.indexOf(source, '\n', bufstart, bufend);
- int bufsiz = end >= 0 ? end - bufstart : bufend - bufstart;
- boolean first = true;
-
- if (mChdirs == null) {
- mChdirs = new byte[ArrayUtils.idealByteArraySize(bufsiz + 1)];
- mChs = new char[ArrayUtils.idealCharArraySize(bufsiz + 1)];
- mWidths = new float[ArrayUtils.idealIntArraySize((bufsiz + 1) * 2)];
- }
-
- byte[] chdirs = mChdirs;
- char[] chs = mChs;
- float[] widths = mWidths;
+ MeasuredText measured = mMeasured;
- AlteredCharSequence alter = null;
Spanned spanned = null;
-
if (source instanceof Spanned)
spanned = (Spanned) source;
int DEFAULT_DIR = DIR_LEFT_TO_RIGHT; // XXX
- for (int start = bufstart; start <= bufend; start = end) {
- if (first)
- first = false;
- else
- end = TextUtils.indexOf(source, '\n', start, bufend);
-
- if (end < 0)
- end = bufend;
+ int paraEnd;
+ for (int paraStart = bufstart; paraStart <= bufend; paraStart = paraEnd) {
+ paraEnd = TextUtils.indexOf(source, '\n', paraStart, bufend);
+ if (paraEnd < 0)
+ paraEnd = bufend;
else
- end++;
+ paraEnd++;
+ int paraLen = paraEnd - paraStart;
int firstWidthLineCount = 1;
int firstwidth = outerwidth;
@@ -170,19 +153,20 @@ public StaticLayout(CharSequence source, int bufstart, int bufend,
LineHeightSpan[] chooseht = null;
if (spanned != null) {
- LeadingMarginSpan[] sp;
-
- sp = spanned.getSpans(start, end, LeadingMarginSpan.class);
+ LeadingMarginSpan[] sp = spanned.getSpans(paraStart, paraEnd,
+ LeadingMarginSpan.class);
for (int i = 0; i < sp.length; i++) {
LeadingMarginSpan lms = sp[i];
firstwidth -= sp[i].getLeadingMargin(true);
restwidth -= sp[i].getLeadingMargin(false);
if (lms instanceof LeadingMarginSpan.LeadingMarginSpan2) {
- firstWidthLineCount = ((LeadingMarginSpan.LeadingMarginSpan2)lms).getLeadingMarginLineCount();
+ firstWidthLineCount =
+ ((LeadingMarginSpan.LeadingMarginSpan2)lms)
+ .getLeadingMarginLineCount();
}
}
- chooseht = spanned.getSpans(start, end, LineHeightSpan.class);
+ chooseht = spanned.getSpans(paraStart, paraEnd, LineHeightSpan.class);
if (chooseht.length != 0) {
if (choosehtv == null ||
@@ -194,7 +178,7 @@ public StaticLayout(CharSequence source, int bufstart, int bufend,
for (int i = 0; i < chooseht.length; i++) {
int o = spanned.getSpanStart(chooseht[i]);
- if (o < start) {
+ if (o < paraStart) {
// starts in this layout, before the
// current paragraph
@@ -208,135 +192,48 @@ public StaticLayout(CharSequence source, int bufstart, int bufend,
}
}
- if (end - start > chdirs.length) {
- chdirs = new byte[ArrayUtils.idealByteArraySize(end - start)];
- mChdirs = chdirs;
- }
- if (end - start > chs.length) {
- chs = new char[ArrayUtils.idealCharArraySize(end - start)];
- mChs = chs;
- }
- if ((end - start) * 2 > widths.length) {
- widths = new float[ArrayUtils.idealIntArraySize((end - start) * 2)];
- mWidths = widths;
- }
-
- TextUtils.getChars(source, start, end, chs, 0);
- final int n = end - start;
-
- boolean easy = true;
- boolean altered = false;
- int dir = DEFAULT_DIR; // XXX pass value in
-
- for (int i = 0; i < n; i++) {
- if (chs[i] >= FIRST_RIGHT_TO_LEFT) {
- easy = false;
- break;
- }
- }
-
- // Ensure that none of the underlying characters are treated
- // as viable breakpoints, and that the entire run gets the
- // same bidi direction.
-
- if (source instanceof Spanned) {
- Spanned sp = (Spanned) source;
- ReplacementSpan[] spans = sp.getSpans(start, end, ReplacementSpan.class);
-
- for (int y = 0; y < spans.length; y++) {
- int a = sp.getSpanStart(spans[y]);
- int b = sp.getSpanEnd(spans[y]);
-
- for (int x = a; x < b; x++) {
- chs[x - start] = '\uFFFC';
- }
- }
- }
-
- if (!easy) {
- // XXX put override flags, etc. into chdirs
- // XXX supply dir rather than force
- dir = AndroidBidi.bidi(DIR_REQUEST_DEFAULT_LTR, chs, chdirs, n, false);
-
- // Do mirroring for right-to-left segments
-
- for (int i = 0; i < n; i++) {
- if (chdirs[i] == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
- int j;
-
- for (j = i; j < n; j++) {
- if (chdirs[j] !=
- Character.DIRECTIONALITY_RIGHT_TO_LEFT)
- break;
- }
-
- if (AndroidCharacter.mirror(chs, i, j - i))
- altered = true;
-
- i = j - 1;
- }
- }
- }
-
- CharSequence sub;
+ measured.setPara(source, paraStart, paraEnd, DIR_REQUEST_DEFAULT_LTR);
+ char[] chs = measured.mChars;
+ float[] widths = measured.mWidths;
+ byte[] chdirs = measured.mLevels;
+ int dir = measured.mDir;
+ boolean easy = measured.mEasy;
- if (altered) {
- if (alter == null)
- alter = AlteredCharSequence.make(source, chs, start, end);
- else
- alter.update(chs, start, end);
-
- sub = alter;
- } else {
- sub = source;
- }
+ CharSequence sub = source;
int width = firstwidth;
float w = 0;
- int here = start;
+ int here = paraStart;
- int ok = start;
+ int ok = paraStart;
float okwidth = w;
int okascent = 0, okdescent = 0, oktop = 0, okbottom = 0;
- int fit = start;
+ int fit = paraStart;
float fitwidth = w;
int fitascent = 0, fitdescent = 0, fittop = 0, fitbottom = 0;
boolean tab = false;
- int next;
- for (int i = start; i < end; i = next) {
+ int spanEnd;
+ for (int spanStart = paraStart; spanStart < paraEnd; spanStart = spanEnd) {
if (spanned == null)
- next = end;
+ spanEnd = paraEnd;
else
- next = spanned.nextSpanTransition(i, end,
- MetricAffectingSpan.
- class);
+ spanEnd = spanned.nextSpanTransition(spanStart, paraEnd,
+ MetricAffectingSpan.class);
- if (spanned == null) {
- paint.getTextWidths(sub, i, next, widths);
- System.arraycopy(widths, 0, widths,
- end - start + (i - start), next - i);
+ int spanLen = spanEnd - spanStart;
+ int startInPara = spanStart - paraStart;
+ int endInPara = spanEnd - paraStart;
- paint.getFontMetricsInt(fm);
+ if (spanned == null) {
+ measured.addStyleRun(paint, spanLen, fm);
} else {
- mWorkPaint.baselineShift = 0;
-
- Styled.getTextWidths(paint, mWorkPaint,
- spanned, i, next,
- widths, fm);
- System.arraycopy(widths, 0, widths,
- end - start + (i - start), next - i);
-
- if (mWorkPaint.baselineShift < 0) {
- fm.ascent += mWorkPaint.baselineShift;
- fm.top += mWorkPaint.baselineShift;
- } else {
- fm.descent += mWorkPaint.baselineShift;
- fm.bottom += mWorkPaint.baselineShift;
- }
+ MetricAffectingSpan[] spans =
+ spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class);
+ measured.addStyleRun(paint, spans, spanLen, fm);
}
int fmtop = fm.top;
@@ -344,27 +241,17 @@ public StaticLayout(CharSequence source, int bufstart, int bufend,
int fmascent = fm.ascent;
int fmdescent = fm.descent;
- if (false) {
- StringBuilder sb = new StringBuilder();
- for (int j = i; j < next; j++) {
- sb.append(widths[j - start + (end - start)]);
- sb.append(' ');
- }
-
- Log.e("text", sb.toString());
- }
-
- for (int j = i; j < next; j++) {
- char c = chs[j - start];
+ for (int j = spanStart; j < spanEnd; j++) {
+ char c = chs[j - paraStart];
float before = w;
if (c == '\n') {
;
} else if (c == '\t') {
- w = Layout.nextTab(sub, start, end, w, null);
+ w = Layout.nextTab(sub, paraStart, paraEnd, w, null);
tab = true;
- } else if (c >= 0xD800 && c <= 0xDFFF && j + 1 < next) {
- int emoji = Character.codePointAt(chs, j - start);
+ } else if (c >= 0xD800 && c <= 0xDFFF && j + 1 < spanEnd) {
+ int emoji = Character.codePointAt(chs, j - paraStart);
if (emoji >= MIN_EMOJI && emoji <= MAX_EMOJI) {
Bitmap bm = EMOJI_FACTORY.
@@ -387,13 +274,13 @@ public StaticLayout(CharSequence source, int bufstart, int bufend,
tab = true;
j++;
} else {
- w += widths[j - start + (end - start)];
+ w += widths[j - paraStart];
}
} else {
- w += widths[j - start + (end - start)];
+ w += widths[j - paraStart];
}
} else {
- w += widths[j - start + (end - start)];
+ w += widths[j - paraStart];
}
// Log.e("text", "was " + before + " now " + w + " after " + c + " within " + width);
@@ -429,12 +316,12 @@ public StaticLayout(CharSequence source, int bufstart, int bufend,
if (c == ' ' || c == '\t' ||
((c == '.' || c == ',' || c == ':' || c == ';') &&
- (j - 1 < here || !Character.isDigit(chs[j - 1 - start])) &&
- (j + 1 >= next || !Character.isDigit(chs[j + 1 - start]))) ||
+ (j - 1 < here || !Character.isDigit(chs[j - 1 - paraStart])) &&
+ (j + 1 >= spanEnd || !Character.isDigit(chs[j + 1 - paraStart]))) ||
((c == '/' || c == '-') &&
- (j + 1 >= next || !Character.isDigit(chs[j + 1 - start]))) ||
+ (j + 1 >= spanEnd || !Character.isDigit(chs[j + 1 - paraStart]))) ||
(c >= FIRST_CJK && isIdeographic(c, true) &&
- j + 1 < next && isIdeographic(chs[j + 1 - start], false))) {
+ j + 1 < spanEnd && isIdeographic(chs[j + 1 - paraStart], false))) {
okwidth = w;
ok = j + 1;
@@ -451,7 +338,7 @@ public StaticLayout(CharSequence source, int bufstart, int bufend,
if (ok != here) {
// Log.e("text", "output ok " + here + " to " +ok);
- while (ok < next && chs[ok - start] == ' ') {
+ while (ok < spanEnd && chs[ok - paraStart] == ' ') {
ok++;
}
@@ -461,9 +348,9 @@ public StaticLayout(CharSequence source, int bufstart, int bufend,
v,
spacingmult, spacingadd, chooseht,
choosehtv, fm, tab,
- needMultiply, start, chdirs, dir, easy,
+ needMultiply, paraStart, chdirs, dir, easy,
ok == bufend, includepad, trackpad,
- widths, start, end - start,
+ chs, widths, here - paraStart,
where, ellipsizedWidth, okwidth,
paint);
@@ -487,7 +374,7 @@ public StaticLayout(CharSequence source, int bufstart, int bufend,
if (ok != here) {
// Log.e("text", "output ok " + here + " to " +ok);
- while (ok < next && chs[ok - start] == ' ') {
+ while (ok < spanEnd && chs[ok - paraStart] == ' ') {
ok++;
}
@@ -497,9 +384,9 @@ public StaticLayout(CharSequence source, int bufstart, int bufend,
v,
spacingmult, spacingadd, chooseht,
choosehtv, fm, tab,
- needMultiply, start, chdirs, dir, easy,
+ needMultiply, paraStart, chdirs, dir, easy,
ok == bufend, includepad, trackpad,
- widths, start, end - start,
+ chs, widths, here - paraStart,
where, ellipsizedWidth, okwidth,
paint);
@@ -513,18 +400,19 @@ public StaticLayout(CharSequence source, int bufstart, int bufend,
v,
spacingmult, spacingadd, chooseht,
choosehtv, fm, tab,
- needMultiply, start, chdirs, dir, easy,
+ needMultiply, paraStart, chdirs, dir, easy,
fit == bufend, includepad, trackpad,
- widths, start, end - start,
+ chs, widths, here - paraStart,
where, ellipsizedWidth, fitwidth,
paint);
here = fit;
} else {
// Log.e("text", "output one " + here + " to " +(here + 1));
- measureText(paint, mWorkPaint,
- source, here, here + 1, fm, tab,
- null);
+ // XXX not sure why the existing fm wasn't ok.
+ // measureText(paint, mWorkPaint,
+ // source, here, here + 1, fm, tab,
+ // null);
v = out(source,
here, here+1,
@@ -533,18 +421,18 @@ public StaticLayout(CharSequence source, int bufstart, int bufend,
v,
spacingmult, spacingadd, chooseht,
choosehtv, fm, tab,
- needMultiply, start, chdirs, dir, easy,
+ needMultiply, paraStart, chdirs, dir, easy,
here + 1 == bufend, includepad,
trackpad,
- widths, start, end - start,
+ chs, widths, here - paraStart,
where, ellipsizedWidth,
- widths[here - start], paint);
+ widths[here - paraStart], paint);
here = here + 1;
}
- if (here < i) {
- j = next = here; // must remeasure
+ if (here < spanStart) {
+ j = spanEnd = here; // must remeasure
} else {
j = here - 1; // continue looping
}
@@ -561,7 +449,7 @@ public StaticLayout(CharSequence source, int bufstart, int bufend,
}
}
- if (end != here) {
+ if (paraEnd != here) {
if ((fittop | fitbottom | fitdescent | fitascent) == 0) {
paint.getFontMetricsInt(fm);
@@ -574,20 +462,20 @@ public StaticLayout(CharSequence source, int bufstart, int bufend,
// Log.e("text", "output rest " + here + " to " + end);
v = out(source,
- here, end, fitascent, fitdescent,
+ here, paraEnd, fitascent, fitdescent,
fittop, fitbottom,
v,
spacingmult, spacingadd, chooseht,
choosehtv, fm, tab,
- needMultiply, start, chdirs, dir, easy,
- end == bufend, includepad, trackpad,
- widths, start, end - start,
+ needMultiply, paraStart, chdirs, dir, easy,
+ paraEnd == bufend, includepad, trackpad,
+ chs, widths, here - paraStart,
where, ellipsizedWidth, w, paint);
}
- start = end;
+ paraStart = paraEnd;
- if (end == bufend)
+ if (paraEnd == bufend)
break;
}
@@ -602,9 +490,9 @@ public StaticLayout(CharSequence source, int bufstart, int bufend,
v,
spacingmult, spacingadd, null,
null, fm, false,
- needMultiply, bufend, chdirs, DEFAULT_DIR, true,
+ needMultiply, bufend, null, DEFAULT_DIR, true,
true, includepad, trackpad,
- widths, bufstart, 0,
+ null, null, bufstart,
where, ellipsizedWidth, 0, paint);
}
}
@@ -714,28 +602,6 @@ private static void dump(byte[] data, int count, String label) {
}
*/
- private static int getFit(TextPaint paint,
- TextPaint workPaint,
- CharSequence text, int start, int end,
- float wid) {
- int high = end + 1, low = start - 1, guess;
-
- while (high - low > 1) {
- guess = (high + low) / 2;
-
- if (measureText(paint, workPaint,
- text, start, guess, null, true, null) > wid)
- high = guess;
- else
- low = guess;
- }
-
- if (low < start)
- return start;
- else
- return low;
- }
-
private int out(CharSequence text, int start, int end,
int above, int below, int top, int bottom, int v,
float spacingmult, float spacingadd,
@@ -744,7 +610,7 @@ private int out(CharSequence text, int start, int end,
boolean needMultiply, int pstart, byte[] chdirs,
int dir, boolean easy, boolean last,
boolean includepad, boolean trackpad,
- float[] widths, int widstart, int widoff,
+ char[] chs, float[] widths, int widstart,
TextUtils.TruncateAt ellipsize, float ellipsiswidth,
float textwidth, TextPaint paint) {
int j = mLineCount;
@@ -752,8 +618,6 @@ private int out(CharSequence text, int start, int end,
int want = off + mColumns + TOP;
int[] lines = mLines;
- // Log.e("text", "line " + start + " to " + end + (last ? "===" : ""));
-
if (want >= lines.length) {
int nlen = ArrayUtils.idealIntArraySize(want + 1);
int[] grow = new int[nlen];
@@ -840,122 +704,12 @@ private int out(CharSequence text, int start, int end,
if (easy) {
mLineDirections[j] = linedirs;
} else {
- int startOff = start - pstart;
- int baseLevel = dir == DIR_LEFT_TO_RIGHT ? 0 : 1;
- int curLevel = chdirs[startOff];
- int minLevel = curLevel;
- int runCount = 1;
- for (int i = start + 1; i < end; ++i) {
- int level = chdirs[i - pstart];
- if (level != curLevel) {
- curLevel = level;
- ++runCount;
- }
- }
-
- // add final run for trailing counter-directional whitespace
- int visEnd = end;
- if ((curLevel & 1) != (baseLevel & 1)) {
- // look for visible end
- while (--visEnd >= start) {
- char ch = text.charAt(visEnd);
-
- if (ch == '\n') {
- --visEnd;
- break;
- }
-
- if (ch != ' ' && ch != '\t') {
- break;
- }
- }
- ++visEnd;
- if (visEnd != end) {
- ++runCount;
- }
- }
-
- if (runCount == 1 && minLevel == baseLevel) {
- if ((minLevel & 1) != 0) {
- linedirs = DIRS_ALL_RIGHT_TO_LEFT;
- }
- // we're done, only one run on this line
- } else {
- int[] ld = new int[runCount * 2];
- int maxLevel = minLevel;
- int levelBits = minLevel << RUN_LEVEL_SHIFT;
- {
- // Start of first pair is always 0, we write
- // length then start at each new run, and the
- // last run length after we're done.
- int n = 1;
- int prev = start;
- curLevel = minLevel;
- for (int i = start; i < visEnd; ++i) {
- int level = chdirs[i - pstart];
- if (level != curLevel) {
- curLevel = level;
- if (level > maxLevel) {
- maxLevel = level;
- } else if (level < minLevel) {
- minLevel = level;
- }
- // XXX ignore run length limit of 2^RUN_LEVEL_SHIFT
- ld[n++] = (i - prev) | levelBits;
- ld[n++] = i - start;
- levelBits = curLevel << RUN_LEVEL_SHIFT;
- prev = i;
- }
- }
- ld[n] = (visEnd - prev) | levelBits;
- if (visEnd < end) {
- ld[++n] = visEnd - start;
- ld[++n] = (end - visEnd) | (baseLevel << RUN_LEVEL_SHIFT);
- }
- }
-
- // See if we need to swap any runs.
- // If the min level run direction doesn't match the base
- // direction, we always need to swap (at this point
- // we have more than one run).
- // Otherwise, we don't need to swap the lowest level.
- // Since there are no logically adjacent runs at the same
- // level, if the max level is the same as the (new) min
- // level, we have a series of alternating levels that
- // is already in order, so there's no more to do.
- //
- boolean swap;
- if ((minLevel & 1) == baseLevel) {
- minLevel += 1;
- swap = maxLevel > minLevel;
- } else {
- swap = runCount > 1;
- }
- if (swap) {
- for (int level = maxLevel - 1; level >= minLevel; --level) {
- for (int i = 0; i < ld.length; i += 2) {
- if (chdirs[startOff + ld[i]] >= level) {
- int e = i + 2;
- while (e < ld.length && chdirs[startOff + ld[e]] >= level) {
- e += 2;
- }
- for (int low = i, hi = e - 2; low < hi; low += 2, hi -= 2) {
- int x = ld[low]; ld[low] = ld[hi]; ld[hi] = x;
- x = ld[low+1]; ld[low+1] = ld[hi+1]; ld[hi+1] = x;
- }
- i = e + 2;
- }
- }
- }
- }
- linedirs = new Directions(ld);
- }
-
- mLineDirections[j] = linedirs;
+ mLineDirections[j] = AndroidBidi.directions(dir, chdirs, widstart, chs,
+ widstart, end - start);
// If ellipsize is in marquee mode, do not apply ellipsis on the first line
if (ellipsize != null && (ellipsize != TextUtils.TruncateAt.MARQUEE || j != 0)) {
- calculateEllipsis(start, end, widths, widstart, widoff,
+ calculateEllipsis(start, end, widths, widstart,
ellipsiswidth, ellipsize, j,
textwidth, paint);
}
@@ -966,7 +720,7 @@ private int out(CharSequence text, int start, int end,
}
private void calculateEllipsis(int linestart, int lineend,
- float[] widths, int widstart, int widoff,
+ float[] widths, int widstart,
float avail, TextUtils.TruncateAt where,
int line, float textwidth, TextPaint paint) {
int len = lineend - linestart;
@@ -986,7 +740,7 @@ private void calculateEllipsis(int linestart, int lineend,
int i;
for (i = len; i >= 0; i--) {
- float w = widths[i - 1 + linestart - widstart + widoff];
+ float w = widths[i - 1 + linestart - widstart];
if (w + sum + ellipsiswid > avail) {
break;
@@ -1002,7 +756,7 @@ private void calculateEllipsis(int linestart, int lineend,
int i;
for (i = 0; i < len; i++) {
- float w = widths[i + linestart - widstart + widoff];
+ float w = widths[i + linestart - widstart];
if (w + sum + ellipsiswid > avail) {
break;
@@ -1019,7 +773,7 @@ private void calculateEllipsis(int linestart, int lineend,
float ravail = (avail - ellipsiswid) / 2;
for (right = len; right >= 0; right--) {
- float w = widths[right - 1 + linestart - widstart + widoff];
+ float w = widths[right - 1 + linestart - widstart];
if (w + rsum > ravail) {
break;
@@ -1030,7 +784,7 @@ private void calculateEllipsis(int linestart, int lineend,
float lavail = avail - ellipsiswid - rsum;
for (left = 0; left < right; left++) {
- float w = widths[left + linestart - widstart + widoff];
+ float w = widths[left + linestart - widstart];
if (w + lsum > lavail) {
break;
@@ -1047,7 +801,7 @@ private void calculateEllipsis(int linestart, int lineend,
mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
}
- // Override the baseclass so we can directly access our members,
+ // Override the base class so we can directly access our members,
// rather than relying on member functions.
// The logic mirrors that of Layout.getLineForVertical
// FIXME: It may be faster to do a linear search for layouts without many lines.
@@ -1156,10 +910,8 @@ public int getEllipsizedWidth() {
private static final char FIRST_RIGHT_TO_LEFT = '\u0590';
/*
- * These are reused across calls to generate()
+ * This is reused across calls to generate()
*/
- private byte[] mChdirs;
- private char[] mChs;
- private float[] mWidths;
+ private MeasuredText mMeasured;
private Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
}
View
434 core/java/android/text/Styled.java
@@ -1,434 +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.text.style.CharacterStyle;
-import android.text.style.MetricAffectingSpan;
-import android.text.style.ReplacementSpan;
-
-/**
- * This class provides static methods for drawing and measuring styled text,
- * like {@link android.text.Spanned} object with
- * {@link android.text.style.ReplacementSpan}.
- *
- * @hide
- */
-public class Styled
-{
- /**
- * Draws and/or measures a uniform run of text on a single line. No span of
- * interest should start or end in the middle of this run (if not
- * drawing, character spans that don't affect metrics can be ignored).
- * Neither should the run direction change in the middle of the run.
- *
- * <p>The x position is the leading edge of the text. In a right-to-left
- * paragraph, this will be to the right of the text to be drawn. Paint
- * should not have an Align value other than LEFT or positioning will get
- * confused.
- *
- * <p>On return, workPaint will reflect the original paint plus any
- * modifications made by character styles on the run.
- *
- * <p>The returned width is signed and will be < 0 if the paragraph
- * direction is right-to-left.
- */
- private static float drawUniformRun(Canvas canvas,
- Spanned text, int start, int end,
- int dir, boolean runIsRtl,
- float x, int top, int y, int bottom,
- Paint.FontMetricsInt fmi,
- TextPaint paint,
- TextPaint workPaint,
- boolean needWidth) {
-
- boolean haveWidth = false;
- float ret = 0;
- CharacterStyle[] spans = text.getSpans(start, end, CharacterStyle.class);
-
- ReplacementSpan replacement = null;
-
- // XXX: This shouldn't be modifying paint, only workPaint.
- // However, the members belonging to TextPaint should have default
- // values anyway. Better to ensure this in the Layout constructor.
- paint.bgColor = 0;
- paint.baselineShift = 0;
- workPaint.set(paint);
-
- if (spans.length > 0) {
- for (int i = 0; i < spans.length; i++) {
- CharacterStyle span = spans[i];
-
- if (span instanceof ReplacementSpan) {
- replacement = (ReplacementSpan)span;
- }
- else {
- span.updateDrawState(workPaint);
- }
- }
- }
-
- if (replacement == null) {
- CharSequence tmp;
- int tmpstart, tmpend;
-
- if (runIsRtl) {
- tmp = TextUtils.getReverse(text, start, end);
- tmpstart = 0;
- // XXX: assumes getReverse doesn't change the length of the text
- tmpend = end - start;
- } else {
- tmp = text;
- tmpstart = start;
- tmpend = end;
- }
-
- if (fmi != null) {
- workPaint.getFontMetricsInt(fmi);
- }
-
- if (canvas != null) {
- if (workPaint.bgColor != 0) {
- int c = workPaint.getColor();
- Paint.Style s = workPaint.getStyle();
- workPaint.setColor(workPaint.bgColor);
- workPaint.setStyle(Paint.Style.FILL);
-
- if (!haveWidth) {
- ret = workPaint.measureText(tmp, tmpstart, tmpend);
- haveWidth = true;
- }
-
- if (dir == Layout.DIR_RIGHT_TO_LEFT)
- canvas.drawRect(x - ret, top, x, bottom, workPaint);
- else
- canvas.drawRect(x, top, x + ret, bottom, workPaint);
-
- workPaint.setStyle(s);
- workPaint.setColor(c);
- }
-
- if (dir == Layout.DIR_RIGHT_TO_LEFT) {
- if (!haveWidth) {
- ret = workPaint.measureText(tmp, tmpstart, tmpend);
- haveWidth = true;
- }
-
- canvas.drawText(tmp, tmpstart, tmpend,
- x - ret, y + workPaint.baselineShift, workPaint);
- } else {
- if (needWidth) {
- if (!haveWidth) {
- ret = workPaint.measureText(tmp, tmpstart, tmpend);
- haveWidth = true;
- }
- }
-
- canvas.drawText(tmp, tmpstart, tmpend,
- x, y + workPaint.baselineShift, workPaint);
- }
- } else {
- if (needWidth && !haveWidth) {
- ret = workPaint.measureText(tmp, tmpstart, tmpend);
- haveWidth = true;
- }
- }
- } else {
- ret = replacement.getSize(workPaint, text, start, end, fmi);
-
- if (canvas != null) {
- if (dir == Layout.DIR_RIGHT_TO_LEFT)
- replacement.draw(canvas, text, start, end,
- x - ret, top, y, bottom, workPaint);
- else
- replacement.draw(canvas, text, start, end,
- x, top, y, bottom, workPaint);
- }
- }
-
- if (dir == Layout.DIR_RIGHT_TO_LEFT)
- return -ret;
- else
- return ret;
- }
-
- /**
- * Returns the advance widths for a uniform left-to-right run of text with
- * no style changes in the middle of the run. If any style is replacement
- * text, the first character will get the width of the replacement and the
- * remaining characters will get a width of 0.
- *
- * @param paint the paint, will not be modified
- * @param workPaint a paint to modify; on return will reflect the original
- * paint plus the effect of all spans on the run
- * @param text the text
- * @param start the start of the run
- * @param end the limit of the run
- * @param widths array to receive the advance widths of the characters. Must
- * be at least a large as (end - start).
- * @param fmi FontMetrics information; can be null
- * @return the actual number of widths returned
- */
- public static int getTextWidths(TextPaint paint,
- TextPaint workPaint,
- Spanned text, int start, int end,
- float[] widths, Paint.FontMetricsInt fmi) {
- MetricAffectingSpan[] spans =
- text.getSpans(start, end, MetricAffectingSpan.class);
-
- ReplacementSpan replacement = null;
- workPaint.set(paint);
-
- for (int i = 0; i < spans.length; i++) {
- MetricAffectingSpan span = spans[i];
- if (span instanceof ReplacementSpan) {
- replacement = (ReplacementSpan)span;
- }
- else {
- span.updateMeasureState(workPaint);
- }
- }
-
- if (replacement == null) {
- workPaint.getFontMetricsInt(fmi);
- workPaint.getTextWidths(text, start, end, widths);
- } else {
- int wid = replacement.getSize(workPaint, text, start, end, fmi);
-
- if (end > start) {
- widths[0] = wid;
- for (int i = start + 1; i < end; i++)
- widths[i - start] = 0;
- }
- }
- return end - start;
- }
-
- /**
- * Renders and/or measures a directional run of text on a single line.
- * Unlike {@link #drawUniformRun}, this can render runs that cross style
- * boundaries. Returns the signed advance width, if requested.
- *
- * <p>The x position is the leading edge of the text. In a right-to-left
- * paragraph, this will be to the right of the text to be drawn. Paint
- * should not have an Align value other than LEFT or positioning will get
- * confused.
- *
- * <p>This optimizes for unstyled text and so workPaint might not be
- * modified by this call.
- *
- * <p>The returned advance width will be < 0 if the paragraph
- * direction is right-to-left.
- */
- private static float drawDirectionalRun(Canvas canvas,
- CharSequence text, int start, int end,
- int dir, boolean runIsRtl,
- float x, int top, int y, int bottom,
- Paint.FontMetricsInt fmi,
- TextPaint paint,
- TextPaint workPaint,
- boolean needWidth) {
-
- // XXX: It looks like all calls to this API match dir and runIsRtl, so
- // having both parameters is redundant and confusing.
-
- // fast path for unstyled text
- if (!(text instanceof Spanned)) {
- float ret = 0;
-
- if (runIsRtl) {
- CharSequence tmp = TextUtils.getReverse(text, start, end);
- // XXX: this assumes getReverse doesn't tweak the length of
- // the text
- int tmpend = end - start;
-
- if (canvas != null || needWidth)
- ret = paint.measureText(tmp, 0, tmpend);
-
- if (canvas != null)
- canvas.drawText(tmp, 0, tmpend,
- x - ret, y, paint);
- } else {
- if (needWidth)
- ret = paint.measureText(text, start, end);
-
- if (canvas != null)
- canvas.drawText(text, start, end, x, y, paint);
- }
-
- if (fmi != null) {
- paint.getFontMetricsInt(fmi);
- }
-
- return ret * dir; // Layout.DIR_RIGHT_TO_LEFT == -1
- }
-
- float ox = x;
- int minAscent = 0, maxDescent = 0, minTop = 0, maxBottom = 0;
-
- Spanned sp = (Spanned) text;
- Class<?> division;
-
- if (canvas == null)
- division = MetricAffectingSpan.class;
- else
- division = CharacterStyle.class;
-
- int next;
- for (int i = start; i < end; i = next) {
- next = sp.nextSpanTransition(i, end, division);
-
- // XXX: if dir and runIsRtl were not the same, this would draw
- // spans in the wrong order, but no one appears to call it this
- // way.
- x += drawUniformRun(canvas, sp, i, next, dir, runIsRtl,
- x, top, y, bottom, fmi, paint, workPaint,
- needWidth || next != end);
-
- if (fmi != null) {
- if (fmi.ascent < minAscent)
- minAscent = fmi.ascent;
- if (fmi.descent > maxDescent)
- maxDescent = fmi.descent;
-
- if (fmi.top < minTop)
- minTop = fmi.top;
- if (fmi.bottom > maxBottom)
- maxBottom = fmi.bottom;
- }
- }
-
- if (fmi != null) {
- if (start == end) {
- paint.getFontMetricsInt(fmi);
- } else {
- fmi.ascent = minAscent;
- fmi.descent = maxDescent;
- fmi.top = minTop;
- fmi.bottom = maxBottom;
- }
- }
-
- return x - ox;
- }
-
- /**
- * Draws a unidirectional run of text on a single line, and optionally
- * returns the signed advance. Unlike drawDirectionalRun, the paragraph
- * direction and run direction can be different.
- */
- /* package */ static float drawText(Canvas canvas,
- CharSequence text, int start, int end,
- int dir, boolean runIsRtl,
- float x, int top, int y, int bottom,
- TextPaint paint,
- TextPaint workPaint,
- boolean needWidth) {
- // XXX this logic is (dir == DIR_LEFT_TO_RIGHT) == runIsRtl
- if ((dir == Layout.DIR_RIGHT_TO_LEFT && !runIsRtl) ||
- (runIsRtl && dir == Layout.DIR_LEFT_TO_RIGHT)) {
- // TODO: this needs the real direction
- float ch = drawDirectionalRun(null, text, start, end,
- Layout.DIR_LEFT_TO_RIGHT, false, 0, 0, 0, 0, null, paint,
- workPaint, true);
-
- ch *= dir; // DIR_RIGHT_TO_LEFT == -1
- drawDirectionalRun(canvas, text, start, end, -dir,
- runIsRtl, x + ch, top, y, bottom, null, paint,
- workPaint, true);
-
- return ch;