[automerger] Optimise the hit test algorithm am: 71ecf5bd5c am: 42eaa8f932 am: a72cb45f89
Change-Id: I9fa897b6bd6b5291d2956a38a3740b4f6d783ab2
This commit is contained in:
@@ -824,6 +824,32 @@ public abstract class Layout {
|
||||
return TextUtils.packRangeInLong(0, getLineEnd(line));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the trailing BiDi level should be used for an offset
|
||||
*
|
||||
* This method is useful when the offset is at the BiDi level transition point and determine
|
||||
* which run need to be used. For example, let's think about following input: (L* denotes
|
||||
* Left-to-Right characters, R* denotes Right-to-Left characters.)
|
||||
* Input (Logical Order): L1 L2 L3 R1 R2 R3 L4 L5 L6
|
||||
* Input (Display Order): L1 L2 L3 R3 R2 R1 L4 L5 L6
|
||||
*
|
||||
* Then, think about selecting the range (3, 6). The offset=3 and offset=6 are ambiguous here
|
||||
* since they are at the BiDi transition point. In Android, the offset is considered to be
|
||||
* associated with the trailing run if the BiDi level of the trailing run is higher than of the
|
||||
* previous run. In this case, the BiDi level of the input text is as follows:
|
||||
*
|
||||
* Input (Logical Order): L1 L2 L3 R1 R2 R3 L4 L5 L6
|
||||
* BiDi Run: [ Run 0 ][ Run 1 ][ Run 2 ]
|
||||
* BiDi Level: 0 0 0 1 1 1 0 0 0
|
||||
*
|
||||
* Thus, offset = 3 is part of Run 1 and this method returns true for offset = 3, since the BiDi
|
||||
* level of Run 1 is higher than the level of Run 0. Similarly, the offset = 6 is a part of Run
|
||||
* 1 and this method returns false for the offset = 6 since the BiDi level of Run 1 is higher
|
||||
* than the level of Run 2.
|
||||
*
|
||||
* @returns true if offset is at the BiDi level transition point and trailing BiDi level is
|
||||
* higher than previous BiDi level. See above for the detail.
|
||||
*/
|
||||
private boolean primaryIsTrailingPrevious(int offset) {
|
||||
int line = getLineForOffset(offset);
|
||||
int lineStart = getLineStart(line);
|
||||
@@ -873,6 +899,41 @@ public abstract class Layout {
|
||||
return levelBefore < levelAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes in linear time the results of calling
|
||||
* #primaryIsTrailingPrevious for all offsets on a line.
|
||||
* @param line The line giving the offsets we compute the information for
|
||||
* @return The array of results, indexed from 0, where 0 corresponds to the line start offset
|
||||
*/
|
||||
private boolean[] primaryIsTrailingPreviousAllLineOffsets(int line) {
|
||||
int lineStart = getLineStart(line);
|
||||
int lineEnd = getLineEnd(line);
|
||||
int[] runs = getLineDirections(line).mDirections;
|
||||
|
||||
boolean[] trailing = new boolean[lineEnd - lineStart + 1];
|
||||
|
||||
byte[] level = new byte[lineEnd - lineStart + 1];
|
||||
for (int i = 0; i < runs.length; i += 2) {
|
||||
int start = lineStart + runs[i];
|
||||
int limit = start + (runs[i + 1] & RUN_LENGTH_MASK);
|
||||
if (limit > lineEnd) {
|
||||
limit = lineEnd;
|
||||
}
|
||||
level[limit - lineStart - 1] =
|
||||
(byte) ((runs[i + 1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK);
|
||||
}
|
||||
|
||||
for (int i = 0; i < runs.length; i += 2) {
|
||||
int start = lineStart + runs[i];
|
||||
byte currentLevel = (byte) ((runs[i + 1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK);
|
||||
trailing[start - lineStart] = currentLevel > (start == lineStart
|
||||
? (getParagraphDirection(line) == 1 ? 0 : 1)
|
||||
: level[start - lineStart - 1]);
|
||||
}
|
||||
|
||||
return trailing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the primary horizontal position for the specified text offset.
|
||||
* This is the location where a new character would be inserted in
|
||||
@@ -952,6 +1013,60 @@ public abstract class Layout {
|
||||
return getLineStartPos(line, left, right) + wid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes in linear time the results of calling
|
||||
* #getHorizontal for all offsets on a line.
|
||||
* @param line The line giving the offsets we compute information for
|
||||
* @param clamped Whether to clamp the results to the width of the layout
|
||||
* @param primary Whether the results should be the primary or the secondary horizontal
|
||||
* @return The array of results, indexed from 0, where 0 corresponds to the line start offset
|
||||
*/
|
||||
private float[] getLineHorizontals(int line, boolean clamped, boolean primary) {
|
||||
int start = getLineStart(line);
|
||||
int end = getLineEnd(line);
|
||||
int dir = getParagraphDirection(line);
|
||||
boolean hasTab = getLineContainsTab(line);
|
||||
Directions directions = getLineDirections(line);
|
||||
|
||||
TabStops tabStops = null;
|
||||
if (hasTab && mText instanceof Spanned) {
|
||||
// Just checking this line should be good enough, tabs should be
|
||||
// consistent across all lines in a paragraph.
|
||||
TabStopSpan[] tabs = getParagraphSpans((Spanned) mText, start, end, TabStopSpan.class);
|
||||
if (tabs.length > 0) {
|
||||
tabStops = new TabStops(TAB_INCREMENT, tabs); // XXX should reuse
|
||||
}
|
||||
}
|
||||
|
||||
TextLine tl = TextLine.obtain();
|
||||
tl.set(mPaint, mText, start, end, dir, directions, hasTab, tabStops);
|
||||
boolean[] trailings = primaryIsTrailingPreviousAllLineOffsets(line);
|
||||
if (!primary) {
|
||||
for (int offset = 0; offset < trailings.length; ++offset) {
|
||||
trailings[offset] = !trailings[offset];
|
||||
}
|
||||
}
|
||||
float[] wid = tl.measureAllOffsets(trailings, null);
|
||||
TextLine.recycle(tl);
|
||||
|
||||
if (clamped) {
|
||||
for (int offset = 0; offset <= wid.length; ++offset) {
|
||||
if (wid[offset] > mWidth) {
|
||||
wid[offset] = mWidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
int left = getParagraphLeft(line);
|
||||
int right = getParagraphRight(line);
|
||||
|
||||
int lineStartPos = getLineStartPos(line, left, right);
|
||||
float[] horizontal = new float[end - start + 1];
|
||||
for (int offset = 0; offset < horizontal.length; ++offset) {
|
||||
horizontal[offset] = lineStartPos + wid[offset];
|
||||
}
|
||||
return horizontal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the leftmost position that should be exposed for horizontal
|
||||
* scrolling on the specified line.
|
||||
@@ -1167,6 +1282,8 @@ public abstract class Layout {
|
||||
// XXX: we don't care about tabs as we just use TextLine#getOffsetToLeftRightOf here.
|
||||
tl.set(mPaint, mText, lineStartOffset, lineEndOffset, getParagraphDirection(line), dirs,
|
||||
false, null);
|
||||
final HorizontalMeasurementProvider horizontal =
|
||||
new HorizontalMeasurementProvider(line, primary);
|
||||
|
||||
final int max;
|
||||
if (line == getLineCount() - 1) {
|
||||
@@ -1176,7 +1293,7 @@ public abstract class Layout {
|
||||
!isRtlCharAt(lineEndOffset - 1)) + lineStartOffset;
|
||||
}
|
||||
int best = lineStartOffset;
|
||||
float bestdist = Math.abs(getHorizontal(best, primary) - horiz);
|
||||
float bestdist = Math.abs(horizontal.get(lineStartOffset) - horiz);
|
||||
|
||||
for (int i = 0; i < dirs.mDirections.length; i += 2) {
|
||||
int here = lineStartOffset + dirs.mDirections[i];
|
||||
@@ -1192,7 +1309,7 @@ public abstract class Layout {
|
||||
guess = (high + low) / 2;
|
||||
int adguess = getOffsetAtStartOf(guess);
|
||||
|
||||
if (getHorizontal(adguess, primary) * swap >= horiz * swap)
|
||||
if (horizontal.get(adguess) * swap >= horiz * swap)
|
||||
high = guess;
|
||||
else
|
||||
low = guess;
|
||||
@@ -1205,9 +1322,9 @@ public abstract class Layout {
|
||||
int aft = tl.getOffsetToLeftRightOf(low - lineStartOffset, isRtl) + lineStartOffset;
|
||||
low = tl.getOffsetToLeftRightOf(aft - lineStartOffset, !isRtl) + lineStartOffset;
|
||||
if (low >= here && low < there) {
|
||||
float dist = Math.abs(getHorizontal(low, primary) - horiz);
|
||||
float dist = Math.abs(horizontal.get(low) - horiz);
|
||||
if (aft < there) {
|
||||
float other = Math.abs(getHorizontal(aft, primary) - horiz);
|
||||
float other = Math.abs(horizontal.get(aft) - horiz);
|
||||
|
||||
if (other < dist) {
|
||||
dist = other;
|
||||
@@ -1222,7 +1339,7 @@ public abstract class Layout {
|
||||
}
|
||||
}
|
||||
|
||||
float dist = Math.abs(getHorizontal(here, primary) - horiz);
|
||||
float dist = Math.abs(horizontal.get(here) - horiz);
|
||||
|
||||
if (dist < bestdist) {
|
||||
bestdist = dist;
|
||||
@@ -1230,7 +1347,7 @@ public abstract class Layout {
|
||||
}
|
||||
}
|
||||
|
||||
float dist = Math.abs(getHorizontal(max, primary) - horiz);
|
||||
float dist = Math.abs(horizontal.get(max) - horiz);
|
||||
|
||||
if (dist <= bestdist) {
|
||||
bestdist = dist;
|
||||
@@ -1241,6 +1358,46 @@ public abstract class Layout {
|
||||
return best;
|
||||
}
|
||||
|
||||
/**
|
||||
* Responds to #getHorizontal queries, by selecting the better strategy between:
|
||||
* - calling #getHorizontal explicitly for each query
|
||||
* - precomputing all #getHorizontal measurements, and responding to any query in constant time
|
||||
* The first strategy is used for LTR-only text, while the second is used for all other cases.
|
||||
* The class is currently only used in #getOffsetForHorizontal, so reuse with care in other
|
||||
* contexts.
|
||||
*/
|
||||
private class HorizontalMeasurementProvider {
|
||||
private final int mLine;
|
||||
private final boolean mPrimary;
|
||||
|
||||
private float[] mHorizontals;
|
||||
private int mLineStartOffset;
|
||||
|
||||
HorizontalMeasurementProvider(final int line, final boolean primary) {
|
||||
mLine = line;
|
||||
mPrimary = primary;
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
final Directions dirs = getLineDirections(mLine);
|
||||
if (dirs == DIRS_ALL_LEFT_TO_RIGHT) {
|
||||
return;
|
||||
}
|
||||
|
||||
mHorizontals = getLineHorizontals(mLine, false, mPrimary);
|
||||
mLineStartOffset = getLineStart(mLine);
|
||||
}
|
||||
|
||||
float get(final int offset) {
|
||||
if (mHorizontals == null) {
|
||||
return getHorizontal(offset, mPrimary);
|
||||
} else {
|
||||
return mHorizontals[offset - mLineStartOffset];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the text offset after the last character on the specified line.
|
||||
*/
|
||||
|
||||
@@ -338,6 +338,98 @@ class TextLine {
|
||||
return h;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #measure(int, boolean, FontMetricsInt)
|
||||
* @return The measure results for all possible offsets
|
||||
*/
|
||||
float[] measureAllOffsets(boolean[] trailing, FontMetricsInt fmi) {
|
||||
float[] measurement = new float[mLen + 1];
|
||||
|
||||
int[] target = new int[mLen + 1];
|
||||
for (int offset = 0; offset < target.length; ++offset) {
|
||||
target[offset] = trailing[offset] ? offset - 1 : offset;
|
||||
}
|
||||
if (target[0] < 0) {
|
||||
measurement[0] = 0;
|
||||
}
|
||||
|
||||
float h = 0;
|
||||
|
||||
if (!mHasTabs) {
|
||||
if (mDirections == Layout.DIRS_ALL_LEFT_TO_RIGHT) {
|
||||
for (int offset = 0; offset <= mLen; ++offset) {
|
||||
measurement[offset] = measureRun(0, offset, mLen, false, fmi);
|
||||
}
|
||||
return measurement;
|
||||
}
|
||||
if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT) {
|
||||
for (int offset = 0; offset <= mLen; ++offset) {
|
||||
measurement[offset] = measureRun(0, offset, mLen, true, fmi);
|
||||
}
|
||||
return measurement;
|
||||
}
|
||||
}
|
||||
|
||||
char[] chars = mChars;
|
||||
int[] runs = mDirections.mDirections;
|
||||
for (int i = 0; i < runs.length; i += 2) {
|
||||
int runStart = runs[i];
|
||||
int runLimit = runStart + (runs[i + 1] & Layout.RUN_LENGTH_MASK);
|
||||
if (runLimit > mLen) {
|
||||
runLimit = mLen;
|
||||
}
|
||||
boolean runIsRtl = (runs[i + 1] & Layout.RUN_RTL_FLAG) != 0;
|
||||
|
||||
int segstart = runStart;
|
||||
for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; ++j) {
|
||||
int codept = 0;
|
||||
if (mHasTabs && j < runLimit) {
|
||||
codept = chars[j];
|
||||
if (codept >= 0xD800 && codept < 0xDC00 && j + 1 < runLimit) {
|
||||
codept = Character.codePointAt(chars, j);
|
||||
if (codept > 0xFFFF) {
|
||||
++j;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (j == runLimit || codept == '\t') {
|
||||
float oldh = h;
|
||||
boolean advance = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;
|
||||
float w = measureRun(segstart, j, j, runIsRtl, fmi);
|
||||
h += advance ? w : -w;
|
||||
|
||||
float baseh = advance ? oldh : h;
|
||||
FontMetricsInt crtfmi = advance ? fmi : null;
|
||||
for (int offset = segstart; offset <= j && offset <= mLen; ++offset) {
|
||||
if (target[offset] >= segstart && target[offset] < j) {
|
||||
measurement[offset] =
|
||||
baseh + measureRun(segstart, offset, j, runIsRtl, crtfmi);
|
||||
}
|
||||
}
|
||||
|
||||
if (codept == '\t') {
|
||||
if (target[j] == j) {
|
||||
measurement[j] = h;
|
||||
}
|
||||
h = mDir * nextTab(h * mDir);
|
||||
if (target[j + 1] == j) {
|
||||
measurement[j + 1] = h;
|
||||
}
|
||||
}
|
||||
|
||||
segstart = j + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (target[mLen] == mLen) {
|
||||
measurement[mLen] = h;
|
||||
}
|
||||
|
||||
return measurement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a unidirectional (but possibly multi-styled) run of text.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user