Merge "Refactor Styled utility functions into reusable objects."
This commit is contained in:
@@ -16,6 +16,8 @@
|
||||
|
||||
package android.text;
|
||||
|
||||
import android.text.Layout.Directions;
|
||||
|
||||
/**
|
||||
* Access the ICU bidi implementation.
|
||||
* @hide
|
||||
@@ -44,5 +46,132 @@ package android.text;
|
||||
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);
|
||||
}
|
||||
@@ -208,11 +208,11 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback
|
||||
* 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 class BoringLayout extends Layout implements TextUtils.EllipsizeCallback
|
||||
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 class BoringLayout extends Layout implements TextUtils.EllipsizeCallback
|
||||
|
||||
public static class Metrics extends Paint.FontMetricsInt {
|
||||
public int width;
|
||||
|
||||
|
||||
@Override public String toString() {
|
||||
return super.toString() + " width=" + width;
|
||||
}
|
||||
|
||||
@@ -19,12 +19,10 @@ package android.text;
|
||||
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 @@ public abstract class Layout {
|
||||
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 abstract class Layout {
|
||||
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 @@ public abstract class Layout {
|
||||
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 abstract class Layout {
|
||||
|
||||
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 abstract class Layout {
|
||||
// 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 @@ public abstract class Layout {
|
||||
|
||||
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 @@ public abstract class Layout {
|
||||
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 abstract class Layout {
|
||||
|
||||
private float getLineMax(int line, Object[] tabs, boolean full) {
|
||||
int start = getLineStart(line);
|
||||
int end;
|
||||
int end = full ? getLineEnd(line) : getLineVisibleEnd(line);
|
||||
boolean hasTabs = getLineContainsTab(line);
|
||||
Directions directions = getLineDirections(line);
|
||||
|
||||
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);
|
||||
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 abstract class Layout {
|
||||
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);
|
||||
int lineDir = getParagraphDirection(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
Directions directions = getLineDirections(line);
|
||||
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
// 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 abstract class Layout {
|
||||
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;
|
||||
/* 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);
|
||||
}
|
||||
buf = null;
|
||||
} else {
|
||||
buf = TextUtils.obtain(end - start);
|
||||
TextUtils.getChars(text, start, end, buf, 0);
|
||||
tl.set(paint, text, start, end, 1, directions, hasTabs, tabs);
|
||||
return tl.metrics(null);
|
||||
} finally {
|
||||
TextLine.recycle(tl);
|
||||
MeasuredText.recycle(mt);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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 @@ public abstract class Layout {
|
||||
* 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.
|
||||
|
||||
250
core/java/android/text/MeasuredText.java
Normal file
250
core/java/android/text/MeasuredText.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -23,8 +23,6 @@ import android.graphics.Paint;
|
||||
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 @@ extends Layout
|
||||
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 @@ extends Layout
|
||||
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 @@ extends Layout
|
||||
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;
|
||||
MeasuredText measured = mMeasured;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
int paraEnd;
|
||||
for (int paraStart = bufstart; paraStart <= bufend; paraStart = paraEnd) {
|
||||
paraEnd = TextUtils.indexOf(source, '\n', paraStart, bufend);
|
||||
if (paraEnd < 0)
|
||||
paraEnd = bufend;
|
||||
else
|
||||
end = TextUtils.indexOf(source, '\n', start, bufend);
|
||||
|
||||
if (end < 0)
|
||||
end = bufend;
|
||||
else
|
||||
end++;
|
||||
paraEnd++;
|
||||
int paraLen = paraEnd - paraStart;
|
||||
|
||||
int firstWidthLineCount = 1;
|
||||
int firstwidth = outerwidth;
|
||||
@@ -170,19 +153,20 @@ extends Layout
|
||||
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 @@ extends Layout
|
||||
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 @@ extends Layout
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
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);
|
||||
|
||||
int spanLen = spanEnd - spanStart;
|
||||
int startInPara = spanStart - paraStart;
|
||||
int endInPara = spanEnd - paraStart;
|
||||
|
||||
if (spanned == null) {
|
||||
paint.getTextWidths(sub, i, next, widths);
|
||||
System.arraycopy(widths, 0, widths,
|
||||
end - start + (i - start), next - i);
|
||||
|
||||
paint.getFontMetricsInt(fm);
|
||||
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 @@ extends Layout
|
||||
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 @@ extends Layout
|
||||
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 @@ extends Layout
|
||||
|
||||
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 @@ extends Layout
|
||||
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 @@ extends Layout
|
||||
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 @@ extends Layout
|
||||
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 @@ extends Layout
|
||||
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 @@ extends Layout
|
||||
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 @@ extends Layout
|
||||
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 @@ extends Layout
|
||||
}
|
||||
}
|
||||
|
||||
if (end != here) {
|
||||
if (paraEnd != here) {
|
||||
if ((fittop | fitbottom | fitdescent | fitascent) == 0) {
|
||||
paint.getFontMetricsInt(fm);
|
||||
|
||||
@@ -574,20 +462,20 @@ extends Layout
|
||||
// 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 @@ extends Layout
|
||||
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 @@ extends Layout
|
||||
}
|
||||
*/
|
||||
|
||||
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 @@ extends Layout
|
||||
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 @@ extends Layout
|
||||
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 @@ extends Layout
|
||||
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 @@ extends Layout
|
||||
}
|
||||
|
||||
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 @@ extends Layout
|
||||
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 @@ extends Layout
|
||||
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 @@ extends Layout
|
||||
|
||||
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 @@ extends Layout
|
||||
|
||||
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 @@ extends Layout
|
||||
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 @@ extends Layout
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
return drawDirectionalRun(canvas, text, start, end, dir, runIsRtl,
|
||||
x, top, y, bottom, null, paint, workPaint,
|
||||
needWidth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a run of text on a single line, with its
|
||||
* origin at (x,y), in the specified Paint. The origin is interpreted based
|
||||
* on the Align setting in the Paint.
|
||||
*
|
||||
* This method considers style information in the text (e.g. even when text
|
||||
* is an instance of {@link android.text.Spanned}, this method correctly
|
||||
* draws the text). See also
|
||||
* {@link android.graphics.Canvas#drawText(CharSequence, int, int, float,
|
||||
* float, Paint)} and
|
||||
* {@link android.graphics.Canvas#drawRect(float, float, float, float,
|
||||
* Paint)}.
|
||||
*
|
||||
* @param canvas The target canvas
|
||||
* @param text The text to be drawn
|
||||
* @param start The index of the first character in text to draw
|
||||
* @param end (end - 1) is the index of the last character in text to draw
|
||||
* @param direction The direction of the text. This must be
|
||||
* {@link android.text.Layout#DIR_LEFT_TO_RIGHT} or
|
||||
* {@link android.text.Layout#DIR_RIGHT_TO_LEFT}.
|
||||
* @param x The x-coordinate of origin for where to draw the text
|
||||
* @param top The top side of the rectangle to be drawn
|
||||
* @param y The y-coordinate of origin for where to draw the text
|
||||
* @param bottom The bottom side of the rectangle to be drawn
|
||||
* @param paint The main {@link TextPaint} object.
|
||||
* @param workPaint The {@link TextPaint} object used for temporal
|
||||
* workspace.
|
||||
* @param needWidth If true, this method returns the width of drawn text
|
||||
* @return Width of the drawn text if needWidth is true
|
||||
*/
|
||||
public static float drawText(Canvas canvas,
|
||||
CharSequence text, int start, int end,
|
||||
int direction,
|
||||
float x, int top, int y, int bottom,
|
||||
TextPaint paint,
|
||||
TextPaint workPaint,
|
||||
boolean needWidth) {
|
||||
// For safety.
|
||||
direction = direction >= 0 ? Layout.DIR_LEFT_TO_RIGHT
|
||||
: Layout.DIR_RIGHT_TO_LEFT;
|
||||
|
||||
// Hide runIsRtl parameter since it is meaningless for external
|
||||
// developers.
|
||||
// XXX: the runIsRtl probably ought to be the same as direction, then
|
||||
// this could draw rtl text.
|
||||
return drawText(canvas, text, start, end, direction, false,
|
||||
x, top, y, bottom, paint, workPaint, needWidth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the width of a run of left-to-right text on a single line,
|
||||
* considering style information in the text (e.g. even when text is an
|
||||
* instance of {@link android.text.Spanned}, this method correctly measures
|
||||
* the width of the text).
|
||||
*
|
||||
* @param paint the main {@link TextPaint} object; will not be modified
|
||||
* @param workPaint the {@link TextPaint} object available for modification;
|
||||
* will not necessarily be used
|
||||
* @param text the text to measure
|
||||
* @param start the index of the first character to start measuring
|
||||
* @param end 1 beyond the index of the last character to measure
|
||||
* @param fmi FontMetrics information; can be null
|
||||
* @return The width of the text
|
||||
*/
|
||||
public static float measureText(TextPaint paint,
|
||||
TextPaint workPaint,
|
||||
CharSequence text, int start, int end,
|
||||
Paint.FontMetricsInt fmi) {
|
||||
return drawDirectionalRun(null, text, start, end,
|
||||
Layout.DIR_LEFT_TO_RIGHT, false,
|
||||
0, 0, 0, 0, fmi, paint, workPaint, true);
|
||||
}
|
||||
}
|
||||
1053
core/java/android/text/TextLine.java
Normal file
1053
core/java/android/text/TextLine.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -17,12 +17,11 @@
|
||||
package android.text;
|
||||
|
||||
import com.android.internal.R;
|
||||
import com.android.internal.util.ArrayUtils;
|
||||
|
||||
import android.content.res.ColorStateList;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.text.method.TextKeyListener.Capitalize;
|
||||
import android.text.style.AbsoluteSizeSpan;
|
||||
import android.text.style.AlignmentSpan;
|
||||
import android.text.style.BackgroundColorSpan;
|
||||
@@ -45,10 +44,8 @@ import android.text.style.URLSpan;
|
||||
import android.text.style.UnderlineSpan;
|
||||
import android.util.Printer;
|
||||
|
||||
import com.android.internal.util.ArrayUtils;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.Iterator;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class TextUtils {
|
||||
private TextUtils() { /* cannot be instantiated */ }
|
||||
@@ -983,7 +980,7 @@ public class TextUtils {
|
||||
/**
|
||||
* Returns the original text if it fits in the specified width
|
||||
* given the properties of the specified Paint,
|
||||
* or, if it does not fit, a copy with ellipsis character added
|
||||
* or, if it does not fit, a copy with ellipsis character added
|
||||
* at the specified edge or center.
|
||||
* If <code>preserveLength</code> is specified, the returned copy
|
||||
* will be padded with zero-width spaces to preserve the original
|
||||
@@ -992,7 +989,7 @@ public class TextUtils {
|
||||
* report the start and end of the ellipsized range.
|
||||
*/
|
||||
public static CharSequence ellipsize(CharSequence text,
|
||||
TextPaint p,
|
||||
TextPaint paint,
|
||||
float avail, TruncateAt where,
|
||||
boolean preserveLength,
|
||||
EllipsizeCallback callback) {
|
||||
@@ -1003,13 +1000,12 @@ public class TextUtils {
|
||||
|
||||
int len = text.length();
|
||||
|
||||
// Use Paint.breakText() for the non-Spanned case to avoid having
|
||||
// to allocate memory and accumulate the character widths ourselves.
|
||||
MeasuredText mt = MeasuredText.obtain();
|
||||
try {
|
||||
float width = setPara(mt, paint, text, 0, text.length(),
|
||||
Layout.DIR_REQUEST_DEFAULT_LTR);
|
||||
|
||||
if (!(text instanceof Spanned)) {
|
||||
float wid = p.measureText(text, 0, len);
|
||||
|
||||
if (wid <= avail) {
|
||||
if (width <= avail) {
|
||||
if (callback != null) {
|
||||
callback.ellipsized(0, 0);
|
||||
}
|
||||
@@ -1017,250 +1013,69 @@ public class TextUtils {
|
||||
return text;
|
||||
}
|
||||
|
||||
float ellipsiswid = p.measureText(sEllipsis);
|
||||
// XXX assumes ellipsis string does not require shaping and
|
||||
// is unaffected by style
|
||||
float ellipsiswid = paint.measureText(sEllipsis);
|
||||
avail -= ellipsiswid;
|
||||
|
||||
if (ellipsiswid > avail) {
|
||||
if (callback != null) {
|
||||
callback.ellipsized(0, len);
|
||||
}
|
||||
|
||||
if (preserveLength) {
|
||||
char[] buf = obtain(len);
|
||||
for (int i = 0; i < len; i++) {
|
||||
buf[i] = '\uFEFF';
|
||||
}
|
||||
String ret = new String(buf, 0, len);
|
||||
recycle(buf);
|
||||
return ret;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
if (where == TruncateAt.START) {
|
||||
int fit = p.breakText(text, 0, len, false,
|
||||
avail - ellipsiswid, null);
|
||||
|
||||
if (callback != null) {
|
||||
callback.ellipsized(0, len - fit);
|
||||
}
|
||||
|
||||
if (preserveLength) {
|
||||
return blank(text, 0, len - fit);
|
||||
} else {
|
||||
return sEllipsis + text.toString().substring(len - fit, len);
|
||||
}
|
||||
int left = 0;
|
||||
int right = len;
|
||||
if (avail < 0) {
|
||||
// it all goes
|
||||
} else if (where == TruncateAt.START) {
|
||||
right = len - mt.breakText(0, len, false, avail);
|
||||
} else if (where == TruncateAt.END) {
|
||||
int fit = p.breakText(text, 0, len, true,
|
||||
avail - ellipsiswid, null);
|
||||
|
||||
if (callback != null) {
|
||||
callback.ellipsized(fit, len);
|
||||
}
|
||||
|
||||
if (preserveLength) {
|
||||
return blank(text, fit, len);
|
||||
} else {
|
||||
return text.toString().substring(0, fit) + sEllipsis;
|
||||
}
|
||||
} else /* where == TruncateAt.MIDDLE */ {
|
||||
int right = p.breakText(text, 0, len, false,
|
||||
(avail - ellipsiswid) / 2, null);
|
||||
float used = p.measureText(text, len - right, len);
|
||||
int left = p.breakText(text, 0, len - right, true,
|
||||
avail - ellipsiswid - used, null);
|
||||
|
||||
if (callback != null) {
|
||||
callback.ellipsized(left, len - right);
|
||||
}
|
||||
|
||||
if (preserveLength) {
|
||||
return blank(text, left, len - right);
|
||||
} else {
|
||||
String s = text.toString();
|
||||
return s.substring(0, left) + sEllipsis +
|
||||
s.substring(len - right, len);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// But do the Spanned cases by hand, because it's such a pain
|
||||
// to iterate the span transitions backwards and getTextWidths()
|
||||
// will give us the information we need.
|
||||
|
||||
// getTextWidths() always writes into the start of the array,
|
||||
// so measure each span into the first half and then copy the
|
||||
// results into the second half to use later.
|
||||
|
||||
float[] wid = new float[len * 2];
|
||||
TextPaint temppaint = new TextPaint();
|
||||
Spanned sp = (Spanned) text;
|
||||
|
||||
int next;
|
||||
for (int i = 0; i < len; i = next) {
|
||||
next = sp.nextSpanTransition(i, len, MetricAffectingSpan.class);
|
||||
|
||||
Styled.getTextWidths(p, temppaint, sp, i, next, wid, null);
|
||||
System.arraycopy(wid, 0, wid, len + i, next - i);
|
||||
}
|
||||
|
||||
float sum = 0;
|
||||
for (int i = 0; i < len; i++) {
|
||||
sum += wid[len + i];
|
||||
}
|
||||
|
||||
if (sum <= avail) {
|
||||
if (callback != null) {
|
||||
callback.ellipsized(0, 0);
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
float ellipsiswid = p.measureText(sEllipsis);
|
||||
|
||||
if (ellipsiswid > avail) {
|
||||
if (callback != null) {
|
||||
callback.ellipsized(0, len);
|
||||
}
|
||||
|
||||
if (preserveLength) {
|
||||
char[] buf = obtain(len);
|
||||
for (int i = 0; i < len; i++) {
|
||||
buf[i] = '\uFEFF';
|
||||
}
|
||||
SpannableString ss = new SpannableString(new String(buf, 0, len));
|
||||
recycle(buf);
|
||||
copySpansFrom(sp, 0, len, Object.class, ss, 0);
|
||||
return ss;
|
||||
left = mt.breakText(0, len, true, avail);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
if (where == TruncateAt.START) {
|
||||
sum = 0;
|
||||
int i;
|
||||
|
||||
for (i = len; i >= 0; i--) {
|
||||
float w = wid[len + i - 1];
|
||||
|
||||
if (w + sum + ellipsiswid > avail) {
|
||||
break;
|
||||
}
|
||||
|
||||
sum += w;
|
||||
}
|
||||
|
||||
if (callback != null) {
|
||||
callback.ellipsized(0, i);
|
||||
}
|
||||
|
||||
if (preserveLength) {
|
||||
SpannableString ss = new SpannableString(blank(text, 0, i));
|
||||
copySpansFrom(sp, 0, len, Object.class, ss, 0);
|
||||
return ss;
|
||||
} else {
|
||||
SpannableStringBuilder out = new SpannableStringBuilder(sEllipsis);
|
||||
out.insert(1, text, i, len);
|
||||
|
||||
return out;
|
||||
}
|
||||
} else if (where == TruncateAt.END) {
|
||||
sum = 0;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
float w = wid[len + i];
|
||||
|
||||
if (w + sum + ellipsiswid > avail) {
|
||||
break;
|
||||
}
|
||||
|
||||
sum += w;
|
||||
}
|
||||
|
||||
if (callback != null) {
|
||||
callback.ellipsized(i, len);
|
||||
}
|
||||
|
||||
if (preserveLength) {
|
||||
SpannableString ss = new SpannableString(blank(text, i, len));
|
||||
copySpansFrom(sp, 0, len, Object.class, ss, 0);
|
||||
return ss;
|
||||
} else {
|
||||
SpannableStringBuilder out = new SpannableStringBuilder(sEllipsis);
|
||||
out.insert(0, text, 0, i);
|
||||
|
||||
return out;
|
||||
}
|
||||
} else /* where = TruncateAt.MIDDLE */ {
|
||||
float lsum = 0, rsum = 0;
|
||||
int left = 0, right = len;
|
||||
|
||||
float ravail = (avail - ellipsiswid) / 2;
|
||||
for (right = len; right >= 0; right--) {
|
||||
float w = wid[len + right - 1];
|
||||
|
||||
if (w + rsum > ravail) {
|
||||
break;
|
||||
}
|
||||
|
||||
rsum += w;
|
||||
}
|
||||
|
||||
float lavail = avail - ellipsiswid - rsum;
|
||||
for (left = 0; left < right; left++) {
|
||||
float w = wid[len + left];
|
||||
|
||||
if (w + lsum > lavail) {
|
||||
break;
|
||||
}
|
||||
|
||||
lsum += w;
|
||||
right = len - mt.breakText(0, len, false, avail / 2);
|
||||
avail -= mt.measure(right, len);
|
||||
left = mt.breakText(0, right, true, avail);
|
||||
}
|
||||
|
||||
if (callback != null) {
|
||||
callback.ellipsized(left, right);
|
||||
}
|
||||
|
||||
char[] buf = mt.mChars;
|
||||
Spanned sp = text instanceof Spanned ? (Spanned) text : null;
|
||||
|
||||
int remaining = len - (right - left);
|
||||
if (preserveLength) {
|
||||
SpannableString ss = new SpannableString(blank(text, left, right));
|
||||
if (remaining > 0) { // else eliminate the ellipsis too
|
||||
buf[left++] = '\u2026';
|
||||
}
|
||||
for (int i = left; i < right; i++) {
|
||||
buf[i] = '\uFEFF';
|
||||
}
|
||||
String s = new String(buf, 0, len);
|
||||
if (sp == null) {
|
||||
return s;
|
||||
}
|
||||
SpannableString ss = new SpannableString(s);
|
||||
copySpansFrom(sp, 0, len, Object.class, ss, 0);
|
||||
return ss;
|
||||
} else {
|
||||
SpannableStringBuilder out = new SpannableStringBuilder(sEllipsis);
|
||||
out.insert(0, text, 0, left);
|
||||
out.insert(out.length(), text, right, len);
|
||||
|
||||
return out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String blank(CharSequence source, int start, int end) {
|
||||
int len = source.length();
|
||||
char[] buf = obtain(len);
|
||||
|
||||
if (start != 0) {
|
||||
getChars(source, 0, start, buf, 0);
|
||||
}
|
||||
if (end != len) {
|
||||
getChars(source, end, len, buf, end);
|
||||
}
|
||||
|
||||
if (start != end) {
|
||||
buf[start] = '\u2026';
|
||||
|
||||
for (int i = start + 1; i < end; i++) {
|
||||
buf[i] = '\uFEFF';
|
||||
if (remaining == 0) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
String ret = new String(buf, 0, len);
|
||||
recycle(buf);
|
||||
|
||||
return ret;
|
||||
if (sp == null) {
|
||||
StringBuilder sb = new StringBuilder(remaining + sEllipsis.length());
|
||||
sb.append(buf, 0, left);
|
||||
sb.append(sEllipsis);
|
||||
sb.append(buf, right, len - right);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
SpannableStringBuilder ssb = new SpannableStringBuilder();
|
||||
ssb.append(text, 0, left);
|
||||
ssb.append(sEllipsis);
|
||||
ssb.append(text, right, len);
|
||||
return ssb;
|
||||
} finally {
|
||||
MeasuredText.recycle(mt);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1278,80 +1093,121 @@ public class TextUtils {
|
||||
TextPaint p, float avail,
|
||||
String oneMore,
|
||||
String more) {
|
||||
int len = text.length();
|
||||
char[] buf = new char[len];
|
||||
TextUtils.getChars(text, 0, len, buf, 0);
|
||||
|
||||
int commaCount = 0;
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (buf[i] == ',') {
|
||||
commaCount++;
|
||||
}
|
||||
}
|
||||
|
||||
float[] wid;
|
||||
|
||||
if (text instanceof Spanned) {
|
||||
Spanned sp = (Spanned) text;
|
||||
TextPaint temppaint = new TextPaint();
|
||||
wid = new float[len * 2];
|
||||
|
||||
int next;
|
||||
for (int i = 0; i < len; i = next) {
|
||||
next = sp.nextSpanTransition(i, len, MetricAffectingSpan.class);
|
||||
|
||||
Styled.getTextWidths(p, temppaint, sp, i, next, wid, null);
|
||||
System.arraycopy(wid, 0, wid, len + i, next - i);
|
||||
MeasuredText mt = MeasuredText.obtain();
|
||||
try {
|
||||
int len = text.length();
|
||||
float width = setPara(mt, p, text, 0, len, Layout.DIR_REQUEST_DEFAULT_LTR);
|
||||
if (width <= avail) {
|
||||
return text;
|
||||
}
|
||||
|
||||
System.arraycopy(wid, len, wid, 0, len);
|
||||
} else {
|
||||
wid = new float[len];
|
||||
p.getTextWidths(text, 0, len, wid);
|
||||
}
|
||||
char[] buf = mt.mChars;
|
||||
|
||||
int ok = 0;
|
||||
int okRemaining = commaCount + 1;
|
||||
String okFormat = "";
|
||||
|
||||
int w = 0;
|
||||
int count = 0;
|
||||
|
||||
for (int i = 0; i < len; i++) {
|
||||
w += wid[i];
|
||||
|
||||
if (buf[i] == ',') {
|
||||
count++;
|
||||
|
||||
int remaining = commaCount - count + 1;
|
||||
float moreWid;
|
||||
String format;
|
||||
|
||||
if (remaining == 1) {
|
||||
format = " " + oneMore;
|
||||
} else {
|
||||
format = " " + String.format(more, remaining);
|
||||
}
|
||||
|
||||
moreWid = p.measureText(format);
|
||||
|
||||
if (w + moreWid <= avail) {
|
||||
ok = i + 1;
|
||||
okRemaining = remaining;
|
||||
okFormat = format;
|
||||
int commaCount = 0;
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (buf[i] == ',') {
|
||||
commaCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (w <= avail) {
|
||||
return text;
|
||||
} else {
|
||||
int remaining = commaCount + 1;
|
||||
|
||||
int ok = 0;
|
||||
int okRemaining = remaining;
|
||||
String okFormat = "";
|
||||
|
||||
int w = 0;
|
||||
int count = 0;
|
||||
float[] widths = mt.mWidths;
|
||||
|
||||
int request = mt.mDir == 1 ? Layout.DIR_REQUEST_LTR :
|
||||
Layout.DIR_REQUEST_RTL;
|
||||
|
||||
MeasuredText tempMt = MeasuredText.obtain();
|
||||
for (int i = 0; i < len; i++) {
|
||||
w += widths[i];
|
||||
|
||||
if (buf[i] == ',') {
|
||||
count++;
|
||||
|
||||
String format;
|
||||
// XXX should not insert spaces, should be part of string
|
||||
// XXX should use plural rules and not assume English plurals
|
||||
if (--remaining == 1) {
|
||||
format = " " + oneMore;
|
||||
} else {
|
||||
format = " " + String.format(more, remaining);
|
||||
}
|
||||
|
||||
// XXX this is probably ok, but need to look at it more
|
||||
tempMt.setPara(format, 0, format.length(), request);
|
||||
float moreWid = mt.addStyleRun(p, mt.mLen, null);
|
||||
|
||||
if (w + moreWid <= avail) {
|
||||
ok = i + 1;
|
||||
okRemaining = remaining;
|
||||
okFormat = format;
|
||||
}
|
||||
}
|
||||
}
|
||||
MeasuredText.recycle(tempMt);
|
||||
|
||||
SpannableStringBuilder out = new SpannableStringBuilder(okFormat);
|
||||
out.insert(0, text, 0, ok);
|
||||
return out;
|
||||
} finally {
|
||||
MeasuredText.recycle(mt);
|
||||
}
|
||||
}
|
||||
|
||||
private static float setPara(MeasuredText mt, TextPaint paint,
|
||||
CharSequence text, int start, int end, int bidiRequest) {
|
||||
|
||||
mt.setPara(text, start, end, bidiRequest);
|
||||
|
||||
float width;
|
||||
Spanned sp = text instanceof Spanned ? (Spanned) text : null;
|
||||
int len = end - start;
|
||||
if (sp == null) {
|
||||
width = mt.addStyleRun(paint, len, null);
|
||||
} else {
|
||||
width = 0;
|
||||
int spanEnd;
|
||||
for (int spanStart = 0; spanStart < len; spanStart = spanEnd) {
|
||||
spanEnd = sp.nextSpanTransition(spanStart, len,
|
||||
MetricAffectingSpan.class);
|
||||
MetricAffectingSpan[] spans = sp.getSpans(
|
||||
spanStart, spanEnd, MetricAffectingSpan.class);
|
||||
width += mt.addStyleRun(paint, spans, spanEnd - spanStart, null);
|
||||
}
|
||||
}
|
||||
|
||||
return width;
|
||||
}
|
||||
|
||||
private static final char FIRST_RIGHT_TO_LEFT = '\u0590';
|
||||
|
||||
/* package */
|
||||
static boolean doesNotNeedBidi(CharSequence s, int start, int end) {
|
||||
for (int i = start; i < end; i++) {
|
||||
if (s.charAt(i) >= FIRST_RIGHT_TO_LEFT) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* package */
|
||||
static boolean doesNotNeedBidi(char[] text, int start, int len) {
|
||||
for (int i = start, e = i + len; i < e; i++) {
|
||||
if (text[i] >= FIRST_RIGHT_TO_LEFT) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* package */ static char[] obtain(int len) {
|
||||
char[] buf;
|
||||
|
||||
@@ -1529,7 +1385,7 @@ public class TextUtils {
|
||||
*/
|
||||
public static final int CAP_MODE_CHARACTERS
|
||||
= InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS;
|
||||
|
||||
|
||||
/**
|
||||
* Capitalization mode for {@link #getCapsMode}: capitalize the first
|
||||
* character of all words. This value is explicitly defined to be the same as
|
||||
@@ -1537,7 +1393,7 @@ public class TextUtils {
|
||||
*/
|
||||
public static final int CAP_MODE_WORDS
|
||||
= InputType.TYPE_TEXT_FLAG_CAP_WORDS;
|
||||
|
||||
|
||||
/**
|
||||
* Capitalization mode for {@link #getCapsMode}: capitalize the first
|
||||
* character of each sentence. This value is explicitly defined to be the same as
|
||||
@@ -1545,13 +1401,13 @@ public class TextUtils {
|
||||
*/
|
||||
public static final int CAP_MODE_SENTENCES
|
||||
= InputType.TYPE_TEXT_FLAG_CAP_SENTENCES;
|
||||
|
||||
|
||||
/**
|
||||
* Determine what caps mode should be in effect at the current offset in
|
||||
* the text. Only the mode bits set in <var>reqModes</var> will be
|
||||
* checked. Note that the caps mode flags here are explicitly defined
|
||||
* to match those in {@link InputType}.
|
||||
*
|
||||
*
|
||||
* @param cs The text that should be checked for caps modes.
|
||||
* @param off Location in the text at which to check.
|
||||
* @param reqModes The modes to be checked: may be any combination of
|
||||
@@ -1651,7 +1507,7 @@ public class TextUtils {
|
||||
|
||||
return mode;
|
||||
}
|
||||
|
||||
|
||||
private static Object sLock = new Object();
|
||||
private static char[] sTemp = null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user