summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Kenny Root <kroot@google.com> 2010-06-10 14:25:39 -0700
committer Android (Google) Code Review <android-gerrit@google.com> 2010-06-10 14:25:39 -0700
commit92a4d078e54aa9da1d48941496bb7eb835a79ce3 (patch)
treecc8e6e9d0218480ad51cb5cf72d09805b7b3a130
parentaefed3626efeccf87692aba5b7a1aafe07a25308 (diff)
parent0c702b88c5d0d4380930b920f5be6e66dd95a0d8 (diff)
Merge "Move shaping to native."
-rw-r--r--core/java/android/text/GraphicsOperations.java19
-rw-r--r--core/java/android/text/Layout.java39
-rw-r--r--core/java/android/text/MeasuredText.java75
-rw-r--r--core/java/android/text/SpannableStringBuilder.java86
-rw-r--r--core/java/android/text/TextLine.java354
-rw-r--r--core/java/android/widget/TextView.java24
-rw-r--r--core/jni/android/graphics/Canvas.cpp139
-rw-r--r--core/jni/android/graphics/Paint.cpp193
-rw-r--r--graphics/java/android/graphics/Canvas.java58
-rw-r--r--graphics/java/android/graphics/Paint.java354
10 files changed, 961 insertions, 380 deletions
diff --git a/core/java/android/text/GraphicsOperations.java b/core/java/android/text/GraphicsOperations.java
index 05697c680ce3..d426d1247c7b 100644
--- a/core/java/android/text/GraphicsOperations.java
+++ b/core/java/android/text/GraphicsOperations.java
@@ -37,17 +37,30 @@ extends CharSequence
* Just like {@link Canvas#drawTextRun}.
* {@hide}
*/
- void drawTextRun(Canvas c, int start, int end,
- float x, float y, int flags, Paint p);
+ void drawTextRun(Canvas c, int start, int end, int contextStart, int contextEnd,
+ float x, float y, int flags, Paint p);
/**
* Just like {@link Paint#measureText}.
*/
float measureText(int start, int end, Paint p);
-
/**
* Just like {@link Paint#getTextWidths}.
*/
public int getTextWidths(int start, int end, float[] widths, Paint p);
+
+ /**
+ * Just like {@link Paint#getTextRunAdvances}.
+ * @hide
+ */
+ float getTextRunAdvances(int start, int end, int contextStart, int contextEnd,
+ int flags, float[] advances, int advancesIndex, Paint paint);
+
+ /**
+ * Just like {@link Paint#getTextRunCursor}.
+ * @hide
+ */
+ int getTextRunCursor(int contextStart, int contextEnd, int flags, int offset,
+ int cursorOpt, Paint p);
}
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 2f7720aed061..f533944ed60c 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -211,7 +211,7 @@ public abstract class Layout {
int textLength = 0;
// First, draw LineBackgroundSpans.
- // LineBackgroundSpans know nothing about the alignment, margins, or
+ // LineBackgroundSpans know nothing about the alignment, margins, or
// direction of the layout or line. XXX: Should they?
// They are evaluated at each line.
if (spannedText) {
@@ -230,7 +230,7 @@ public abstract class Layout {
if (start >= spanEnd) {
// These should be infrequent, so we'll use this so that
// we don't have to check as often.
- spanEnd = sp.nextSpanTransition(start, textLength,
+ spanEnd = sp.nextSpanTransition(start, textLength,
LineBackgroundSpan.class);
// All LineBackgroundSpans on a line contribute to its
// background.
@@ -295,7 +295,7 @@ public abstract class Layout {
Spanned sp = (Spanned) buf;
boolean isFirstParaLine = (start == 0 ||
buf.charAt(start - 1) == '\n');
-
+
// New batch of paragraph styles, collect into spans array.
// Compute the alignment, last alignment style wins.
// Reset tabStops, we'll rebuild if we encounter a line with
@@ -318,7 +318,7 @@ public abstract class Layout {
break;
}
}
-
+
tabStopsIsInitialized = false;
}
@@ -399,7 +399,7 @@ public abstract class Layout {
/**
* Return the start position of the line, given the left and right bounds
* of the margins.
- *
+ *
* @param line the line index
* @param left the left bounds (0, or leading margin if ltr para)
* @param right the right bounds (width, minus leading margin if rtl para)
@@ -785,7 +785,7 @@ public abstract class Layout {
}
/**
- * Gets the unsigned horizontal extent of the specified line, including
+ * Gets the unsigned horizontal extent of the specified line, including
* leading margin indent, but excluding trailing whitespace.
*/
public float getLineMax(int line) {
@@ -1356,22 +1356,22 @@ public abstract class Layout {
return 0;
}
Spanned spanned = (Spanned) mText;
-
+
int lineStart = getLineStart(line);
int lineEnd = getLineEnd(line);
- int spanEnd = spanned.nextSpanTransition(lineStart, lineEnd,
+ int spanEnd = spanned.nextSpanTransition(lineStart, lineEnd,
LeadingMarginSpan.class);
LeadingMarginSpan[] spans = spanned.getSpans(lineStart, spanEnd,
LeadingMarginSpan.class);
if (spans.length == 0) {
return 0; // no leading margin span;
}
-
+
int margin = 0;
-
- boolean isFirstParaLine = lineStart == 0 ||
+
+ boolean isFirstParaLine = lineStart == 0 ||
spanned.charAt(lineStart - 1) == '\n';
-
+
for (int i = 0; i < spans.length; i++) {
LeadingMarginSpan span = spans[i];
boolean useFirstLineMargin = isFirstParaLine;
@@ -1379,7 +1379,7 @@ public abstract class Layout {
int spStart = spanned.getSpanStart(span);
int spanLine = getLineForOffset(spStart);
int count = ((LeadingMarginSpan2)span).getLeadingMarginLineCount();
- useFirstLineMargin = line < spanLine + count;
+ useFirstLineMargin = line < spanLine + count;
}
margin += span.getLeadingMargin(useFirstLineMargin);
}
@@ -1414,9 +1414,9 @@ public abstract class Layout {
hasTabs = true;
if (text instanceof Spanned) {
Spanned spanned = (Spanned) text;
- int spanEnd = spanned.nextSpanTransition(start, end,
+ int spanEnd = spanned.nextSpanTransition(start, end,
TabStopSpan.class);
- TabStopSpan[] spans = spanned.getSpans(start, spanEnd,
+ TabStopSpan[] spans = spanned.getSpans(start, spanEnd,
TabStopSpan.class);
if (spans.length > 0) {
tabStops = new TabStops(TAB_INCREMENT, spans);
@@ -1440,11 +1440,11 @@ public abstract class Layout {
private int[] mStops;
private int mNumStops;
private int mIncrement;
-
+
TabStops(int increment, Object[] spans) {
reset(increment, spans);
}
-
+
void reset(int increment, Object[] spans) {
this.mIncrement = increment;
@@ -1474,7 +1474,7 @@ public abstract class Layout {
}
this.mNumStops = ns;
}
-
+
float nextTab(float h) {
int ns = this.mNumStops;
if (ns > 0) {
@@ -1493,7 +1493,7 @@ public abstract class Layout {
return ((int) ((h + inc) / inc)) * inc;
}
}
-
+
/**
* Returns the position of the next tab stop after h on the line.
*
@@ -1728,4 +1728,3 @@ public abstract class Layout {
/* package */ static final Directions DIRS_ALL_RIGHT_TO_LEFT =
new Directions(new int[] { 0, RUN_LENGTH_MASK | RUN_RTL_FLAG });
}
-
diff --git a/core/java/android/text/MeasuredText.java b/core/java/android/text/MeasuredText.java
index e3a113da2de3..d5699f1de1bb 100644
--- a/core/java/android/text/MeasuredText.java
+++ b/core/java/android/text/MeasuredText.java
@@ -18,8 +18,8 @@ package android.text;
import com.android.internal.util.ArrayUtils;
+import android.graphics.Canvas;
import android.graphics.Paint;
-import android.icu.text.ArabicShaping;
import android.text.style.MetricAffectingSpan;
import android.text.style.ReplacementSpan;
import android.util.Log;
@@ -37,7 +37,6 @@ class MeasuredText {
/* package */ boolean mEasy;
/* package */ int mLen;
private int mPos;
- private float[] mWorkWidths; // temp buffer for Paint.measureText, arrgh
private TextPaint mWorkPaint;
private MeasuredText() {
@@ -80,8 +79,7 @@ class MeasuredText {
}
/**
- * Analyzes text for
- * bidirectional runs. Allocates working buffers.
+ * Analyzes text for bidirectional runs. Allocates working buffers.
*/
/* package */
void setPara(CharSequence text, int start, int end, int bidiRequest) {
@@ -94,7 +92,6 @@ class MeasuredText {
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)];
@@ -116,7 +113,7 @@ class MeasuredText {
}
if (TextUtils.doesNotNeedBidi(mChars, 0, len)) {
- mDir = 1;
+ mDir = Layout.DIR_LEFT_TO_RIGHT;
mEasy = true;
} else {
if (mLevels == null || mLevels.length < len) {
@@ -124,56 +121,38 @@ class MeasuredText {
}
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) {
+ if (fm != null) {
+ paint.getFontMetricsInt(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;
+ mPos = p + len;
+
+ if (mEasy) {
+ int flags = mDir == Layout.DIR_LEFT_TO_RIGHT
+ ? Canvas.DIRECTION_LTR : Canvas.DIRECTION_RTL;
+ return paint.getTextRunAdvances(mChars, p, len, p, len, flags, mWidths, p);
+ }
+
+ float totalAdvance = 0;
+ int level = mLevels[p];
+ for (int q = p, i = p + 1, e = p + len;; ++i) {
+ if (i == e || mLevels[i] != level) {
+ int flags = (level & 0x1) == 0 ? Canvas.DIRECTION_LTR : Canvas.DIRECTION_RTL;
+ totalAdvance +=
+ paint.getTextRunAdvances(mChars, q, i - q, q, i - q, flags, mWidths, q);
+ if (i == e) {
+ break;
}
- }
- } else {
- for (int i = 0; i < len; ++i) {
- width += (w[p++] = ww[i]);
+ q = i;
+ level = mLevels[i];
}
}
- mPos = p;
- if (fm != null) {
- paint.getFontMetricsInt(fm);
- }
- return width;
+ return totalAdvance;
}
float addStyleRun(TextPaint paint, MetricAffectingSpan[] spans, int len,
diff --git a/core/java/android/text/SpannableStringBuilder.java b/core/java/android/text/SpannableStringBuilder.java
index 756317932d06..56f130284464 100644
--- a/core/java/android/text/SpannableStringBuilder.java
+++ b/core/java/android/text/SpannableStringBuilder.java
@@ -1055,35 +1055,27 @@ implements CharSequence, GetChars, Spannable, Editable, Appendable,
}
}
+
/**
* Don't call this yourself -- exists for Canvas to use internally.
* {@hide}
*/
public void drawTextRun(Canvas c, int start, int end,
- float x, float y, int flags, Paint p) {
+ int contextStart, int contextEnd,
+ float x, float y, int flags, Paint p) {
checkRange("drawTextRun", start, end);
- // Assume context requires no more than 8 chars on either side.
- // This is ample, only decomposed U+FDFA falls into this
- // category, and no one should put a style break within it
- // anyway.
- int cstart = start - 8;
- if (cstart < 0) {
- cstart = 0;
- }
- int cend = end + 8;
- int max = length();
- if (cend > max) {
- cend = max;
- }
- if (cend <= mGapStart) {
- c.drawTextRun(mText, start, end - start, x, y, flags, p);
- } else if (cstart >= mGapStart) {
- c.drawTextRun(mText, start + mGapLength, end - start, x, y, flags, p);
+ int contextLen = contextEnd - contextStart;
+ int len = end - start;
+ if (contextEnd <= mGapStart) {
+ c.drawTextRun(mText, start, len, contextStart, contextLen, x, y, flags, p);
+ } else if (contextStart >= mGapStart) {
+ c.drawTextRun(mText, start + mGapLength, len, contextStart + mGapLength,
+ contextLen, x, y, flags, p);
} else {
- char[] buf = TextUtils.obtain(cend - cstart);
- getChars(cstart, cend, buf, 0);
- c.drawTextRun(buf, start - cstart, end - start, x, y, flags, p);
+ char[] buf = TextUtils.obtain(contextLen);
+ getChars(contextStart, contextEnd, buf, 0);
+ c.drawTextRun(buf, start - contextStart, len, 0, contextLen, x, y, flags, p);
TextUtils.recycle(buf);
}
}
@@ -1137,6 +1129,58 @@ implements CharSequence, GetChars, Spannable, Editable, Appendable,
return ret;
}
+ /**
+ * Don't call this yourself -- exists for Paint to use internally.
+ * {@hide}
+ */
+ public float getTextRunAdvances(int start, int end, int contextStart, int contextEnd, int flags,
+ float[] advances, int advancesPos, Paint p) {
+
+ float ret;
+
+ int contextLen = contextEnd - contextStart;
+ int len = end - start;
+
+ if (end <= mGapStart) {
+ ret = p.getTextRunAdvances(mText, start, len, contextStart, contextLen,
+ flags, advances, advancesPos);
+ } else if (start >= mGapStart) {
+ ret = p.getTextRunAdvances(mText, start + mGapLength, len,
+ contextStart + mGapLength, contextLen, flags, advances, advancesPos);
+ } else {
+ char[] buf = TextUtils.obtain(contextLen);
+ getChars(contextStart, contextEnd, buf, 0);
+ ret = p.getTextRunAdvances(buf, start - contextStart, len,
+ 0, contextLen, flags, advances, advancesPos);
+ TextUtils.recycle(buf);
+ }
+
+ return ret;
+ }
+
+ public int getTextRunCursor(int contextStart, int contextEnd, int flags, int offset,
+ int cursorOpt, Paint p) {
+
+ int ret;
+
+ int contextLen = contextEnd - contextStart;
+ if (contextEnd <= mGapStart) {
+ ret = p.getTextRunCursor(mText, contextStart, contextLen,
+ flags, offset, cursorOpt);
+ } else if (contextStart >= mGapStart) {
+ ret = p.getTextRunCursor(mText, contextStart + mGapLength, contextLen,
+ flags, offset + mGapLength, cursorOpt);
+ } else {
+ char[] buf = TextUtils.obtain(contextLen);
+ getChars(contextStart, contextEnd, buf, 0);
+ ret = p.getTextRunCursor(buf, 0, contextLen,
+ flags, offset - contextStart, cursorOpt) + contextStart;
+ TextUtils.recycle(buf);
+ }
+
+ return ret;
+ }
+
// Documentation from interface
public void setFilters(InputFilter[] filters) {
if (filters == null) {
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index bd410c8f7b09..e0ccbb41eb46 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -23,7 +23,6 @@ import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Paint.FontMetricsInt;
-import android.icu.text.ArabicShaping;
import android.text.Layout.Directions;
import android.text.Layout.TabStops;
import android.text.style.CharacterStyle;
@@ -52,13 +51,10 @@ class TextLine {
private Directions mDirections;
private boolean mHasTabs;
private TabStops mTabs;
-
private char[] mChars;
private boolean mCharsValid;
private Spanned mSpanned;
private TextPaint mWorkPaint = new TextPaint();
- private int mPreppedIndex;
- private int mPreppedLimit;
private static TextLine[] cached = new TextLine[3];
@@ -129,8 +125,6 @@ class TextLine {
mDirections = directions;
mHasTabs = hasTabs;
mSpanned = null;
- mPreppedIndex = 0;
- mPreppedLimit = 0;
boolean hasReplacement = false;
if (text instanceof Spanned) {
@@ -147,6 +141,25 @@ class TextLine {
mChars = new char[ArrayUtils.idealCharArraySize(mLen)];
}
TextUtils.getChars(text, start, limit, mChars, 0);
+ if (hasReplacement) {
+ // Handle these all at once so we don't have to do it as we go.
+ // Replace the first character of each replacement run with the
+ // object-replacement character and the remainder with zero width
+ // non-break space aka BOM. Cursor movement code skips these
+ // zero-width characters.
+ char[] chars = mChars;
+ for (int i = start, inext; i < limit; i = inext) {
+ inext = mSpanned.nextSpanTransition(i, limit,
+ ReplacementSpan.class);
+ if (mSpanned.getSpans(i, inext, ReplacementSpan.class)
+ .length > 0) { // transition into a span
+ chars[i - start] = '\ufffc';
+ for (int j = i - start + 1, e = inext - start; j < e; ++j) {
+ chars[j] = '\ufeff'; // used as ZWNBS, marks positions to skip
+ }
+ }
+ }
+ }
}
mTabs = tabStops;
}
@@ -264,7 +277,7 @@ class TextLine {
if (!mHasTabs) {
if (mDirections == Layout.DIRS_ALL_LEFT_TO_RIGHT) {
- return measureRun( 0, 0, offset, mLen, false, fmi);
+ return measureRun(0, 0, offset, mLen, false, fmi);
}
if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT) {
return measureRun(0, 0, offset, mLen, true, fmi);
@@ -362,12 +375,12 @@ class TextLine {
if ((mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) {
float w = -measureRun(runIndex, start, limit, limit, runIsRtl, null);
handleRun(runIndex, start, limit, limit, runIsRtl, c, x + w, top,
- y, bottom, null, false, PREP_NONE);
+ y, bottom, null, false);
return w;
}
return handleRun(runIndex, start, limit, limit, runIsRtl, c, x, top,
- y, bottom, null, needWidth, PREP_NEEDED);
+ y, bottom, null, needWidth);
}
/**
@@ -386,23 +399,7 @@ class TextLine {
private float measureRun(int runIndex, int start,
int offset, int limit, boolean runIsRtl, FontMetricsInt fmi) {
return handleRun(runIndex, start, offset, limit, runIsRtl, null,
- 0, 0, 0, 0, fmi, true, PREP_NEEDED);
- }
-
- /**
- * Prepares a run for measurement or rendering. This ensures that any
- * required shaping of the text in the run has been performed so that
- * measurements reflect the shaped text.
- *
- * @param runIndex the run index
- * @param start the line-relative start of the run
- * @param limit the line-relative limit of the run
- * @param runIsRtl true if the run is right-to-left
- */
- private void prepRun(int runIndex, int start, int limit,
- boolean runIsRtl) {
- handleRun(runIndex, start, limit, limit, runIsRtl, null, 0, 0, 0,
- 0, null, false, PREP_ONLY);
+ 0, 0, 0, 0, fmi, true);
}
/**
@@ -414,11 +411,6 @@ class TextLine {
* that might affect the cursor position. Callers must either avoid these
* situations or handle the result specially.
*
- * <p>The paint is required because the region around the cursor might not
- * have been formatted yet, and the valid positions can depend on the glyphs
- * used to render the text, which in turn depends on the paint.
- *
- * @param paint the base paint of the line
* @param cursor the starting position of the cursor, between 0 and the
* length of the line, inclusive
* @param toLeft true if the caret is moving to the left.
@@ -510,8 +502,8 @@ class TextLine {
boolean advance = toLeft == runIsRtl;
if (cursor != (advance ? runLimit : runStart) || advance != trailing) {
// Moving within or into the run, so we can move logically.
- prepRun(runIndex, runStart, runLimit, runIsRtl);
- newCaret = getOffsetBeforeAfter(runIndex, cursor, advance);
+ newCaret = getOffsetBeforeAfter(runIndex, runStart, runLimit,
+ runIsRtl, cursor, 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)) {
@@ -542,9 +534,8 @@ class TextLine {
advance = toLeft == otherRunIsRtl;
if (newCaret == -1) {
- prepRun(otherRunIndex, otherRunStart, otherRunLimit,
- otherRunIsRtl);
- newCaret = getOffsetBeforeAfter(otherRunIndex,
+ newCaret = getOffsetBeforeAfter(otherRunIndex, otherRunStart,
+ otherRunLimit, otherRunIsRtl,
advance ? otherRunStart : otherRunLimit, advance);
if (newCaret == (advance ? otherRunLimit : otherRunStart)) {
// Crossed and ended up at a new boundary,
@@ -568,7 +559,7 @@ class TextLine {
// 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(-1, cursor, advance);
+ newCaret = advance ? mLen + 1 : -1;
break;
}
@@ -592,41 +583,83 @@ class TextLine {
/**
* Returns the next valid offset within this directional run, skipping
* conjuncts and zero-width characters. This should not be called to walk
- * off the end of the run.
+ * off the end of the line, since the the returned values might not be valid
+ * on neighboring lines. If the returned offset is less than zero or
+ * greater than the line length, the offset should be recomputed on the
+ * preceding or following line, respectively.
*
* @param runIndex the run index
+ * @param runStart the start of the run
+ * @param runLimit the limit of the run
+ * @param runIsRtl true if the run is right-to-left
* @param offset the offset
* @param after true if the new offset should logically follow the provided
* offset
* @return the new offset
*/
- private int getOffsetBeforeAfter(int runIndex, int offset, boolean after) {
- // XXX note currently there is no special handling of zero-width
- // combining marks, since the only analysis involves mock shaping.
+ private int getOffsetBeforeAfter(int runIndex, int runStart, int runLimit,
+ boolean runIsRtl, int offset, boolean after) {
- boolean offEnd = offset == (after ? mLen : 0);
- if (runIndex >= 0 && !offEnd && mCharsValid) {
- char[] chars = mChars;
+ if (runIndex < 0 || offset == (after ? mLen : 0)) {
+ // Walking off end of line. Since we don't know
+ // what cursor positions are available on other lines, we can't
+ // return accurate values. These are a guess.
if (after) {
- int cp = Character.codePointAt(chars, offset, mLen);
- if (cp >= 0x10000) {
- ++offset;
+ return TextUtils.getOffsetAfter(mText, offset + mStart) - mStart;
+ }
+ return TextUtils.getOffsetBefore(mText, offset + mStart) - mStart;
+ }
+
+ TextPaint wp = mWorkPaint;
+ wp.set(mPaint);
+
+ int spanStart = runStart;
+ int spanLimit;
+ if (mSpanned == null) {
+ spanLimit = runLimit;
+ } else {
+ int target = after ? offset + 1 : offset;
+ int limit = mStart + runLimit;
+ while (true) {
+ spanLimit = mSpanned.nextSpanTransition(mStart + spanStart, limit,
+ MetricAffectingSpan.class) - mStart;
+ if (spanLimit >= target) {
+ break;
}
- while (++offset < mLen && chars[offset] == '\ufeff'){}
- } else {
- while (--offset >= 0 && chars[offset] == '\ufeff'){}
- int cp = Character.codePointBefore(chars, offset + 1);
- if (cp >= 0x10000) {
- --offset;
+ spanStart = spanLimit;
+ }
+
+ MetricAffectingSpan[] spans = mSpanned.getSpans(mStart + spanStart,
+ mStart + spanLimit, MetricAffectingSpan.class);
+
+ if (spans.length > 0) {
+ ReplacementSpan replacement = null;
+ for (int j = 0; j < spans.length; j++) {
+ MetricAffectingSpan span = spans[j];
+ if (span instanceof ReplacementSpan) {
+ replacement = (ReplacementSpan)span;
+ } else {
+ span.updateMeasureState(wp);
+ }
+ }
+
+ if (replacement != null) {
+ // If we have a replacement span, we're moving either to
+ // the start or end of this span.
+ return after ? spanLimit : spanStart;
}
}
- return offset;
}
- if (after) {
- return TextUtils.getOffsetAfter(mText, offset + mStart) - mStart;
+ int flags = runIsRtl ? Paint.DIRECTION_RTL : Paint.DIRECTION_LTR;
+ int cursorOpt = after ? Paint.CURSOR_AFTER : Paint.CURSOR_BEFORE;
+ if (mCharsValid) {
+ return wp.getTextRunCursor(mChars, spanStart, spanLimit - spanStart,
+ flags, offset, cursorOpt);
+ } else {
+ return wp.getTextRunCursor(mText, mStart + spanStart,
+ mStart + spanLimit, flags, mStart + offset, cursorOpt);
}
- return TextUtils.getOffsetBefore(mText, offset + mStart) - mStart;
}
/**
@@ -635,7 +668,7 @@ class TextLine {
*
* @param wp the working paint
* @param start the start of the text
- * @param limit the limit of the text
+ * @param end the end of the text
* @param runIsRtl true if the run is right-to-left
* @param c the canvas, can be null if rendering is not needed
* @param x the edge of the run closest to the leading margin
@@ -647,19 +680,25 @@ class TextLine {
* @return the signed width of the run based on the run direction; only
* valid if needWidth is true
*/
- private float handleText(TextPaint wp, int start, int limit,
- boolean runIsRtl, Canvas c, float x, int top, int y, int bottom,
+ private float handleText(TextPaint wp, int start, int end,
+ int contextStart, int contextEnd, boolean runIsRtl,
+ Canvas c, float x, int top, int y, int bottom,
FontMetricsInt fmi, boolean needWidth) {
float ret = 0;
- int runLen = limit - start;
+ int runLen = end - start;
+ int contextLen = contextEnd - contextStart;
if (needWidth || (c != null && (wp.bgColor != 0 || runIsRtl))) {
+ int flags = runIsRtl ? Paint.DIRECTION_RTL : Paint.DIRECTION_LTR;
if (mCharsValid) {
- ret = wp.measureText(mChars, start, runLen);
+ ret = wp.getTextRunAdvances(mChars, start, runLen,
+ contextStart, contextLen, flags, null, 0);
} else {
- ret = wp.measureText(mText, mStart + start,
- mStart + start + runLen);
+ int delta = mStart;
+ ret = wp.getTextRunAdvances(mText, delta + start,
+ delta + end, delta + contextStart, delta + contextEnd,
+ flags, null, 0);
}
}
@@ -684,7 +723,8 @@ class TextLine {
wp.setColor(color);
}
- drawTextRun(c, wp, start, limit, runIsRtl, x, y + wp.baselineShift);
+ drawTextRun(c, wp, start, end, contextStart, contextEnd, runIsRtl,
+ x, y + wp.baselineShift);
}
return runIsRtl ? -ret : ret;
@@ -706,48 +746,29 @@ class TextLine {
* @param bottom the bottom of the line
* @param fmi receives metrics information, can be null
* @param needWidth true if the width of the replacement is needed
- * @param prepFlags one of PREP_NONE, PREP_REQUIRED, or PREP_ONLY
* @return the signed width of the run based on the run direction; only
* valid if needWidth is true
*/
private float handleReplacement(ReplacementSpan replacement, TextPaint wp,
int runIndex, int start, int limit, boolean runIsRtl, Canvas c,
float x, int top, int y, int bottom, FontMetricsInt fmi,
- boolean needWidth, int prepFlags) {
+ boolean needWidth) {
float ret = 0;
- // Preparation replaces the first character of the series with the
- // object-replacement character and the remainder with zero width
- // non-break space aka BOM. Cursor movement code skips over the BOMs
- // so that the replacement character is the only character 'seen'.
- if (prepFlags != PREP_NONE && limit > start &&
- (runIndex > mPreppedIndex ||
- (runIndex == mPreppedIndex && start >= mPreppedLimit))) {
- char[] chars = mChars;
- chars[start] = '\ufffc';
- for (int i = start + 1; i < limit; ++i) {
- chars[i] = '\ufeff'; // used as ZWNBS, marks positions to skip
- }
- mPreppedIndex = runIndex;
- mPreppedLimit = limit;
- }
-
- if (prepFlags != PREP_ONLY) {
- int textStart = mStart + start;
- int textLimit = mStart + limit;
+ int textStart = mStart + start;
+ int textLimit = mStart + limit;
- if (needWidth || (c != null && runIsRtl)) {
- ret = replacement.getSize(wp, mText, textStart, textLimit, fmi);
- }
+ if (needWidth || (c != null && runIsRtl)) {
+ ret = replacement.getSize(wp, mText, textStart, textLimit, fmi);
+ }
- if (c != null) {
- if (runIsRtl) {
- x -= ret;
- }
- replacement.draw(c, mText, textStart, textLimit,
- x, top, y, bottom, wp);
+ if (c != null) {
+ if (runIsRtl) {
+ x -= ret;
}
+ replacement.draw(c, mText, textStart, textLimit,
+ x, top, y, bottom, wp);
}
return runIsRtl ? -ret : ret;
@@ -757,10 +778,9 @@ class TextLine {
* Utility function for handling a unidirectional run. The run must not
* contain tabs or emoji but can contain styles.
*
- * @param p the base paint
* @param runIndex the run index
* @param start the line-relative start of the run
- * @param offset the offset to measure to, between start and limit inclusive
+ * @param measureLimit the offset to measure to, between start and limit inclusive
* @param limit the limit of the run
* @param runIsRtl true if the run is right-to-left
* @param c the canvas, can be null
@@ -770,35 +790,34 @@ class TextLine {
* @param bottom the bottom of the line
* @param fmi receives metrics information, can be null
* @param needWidth true if the width is required
- * @param prepFlags one of PREP_NONE, PREP_REQUIRED, or PREP_ONLY
* @return the signed width of the run based on the run direction; only
* valid if needWidth is true
*/
- private float handleRun(int runIndex, int start, int offset,
+ private float handleRun(int runIndex, int start, int measureLimit,
int limit, boolean runIsRtl, Canvas c, float x, int top, int y,
- int bottom, FontMetricsInt fmi, boolean needWidth, int prepFlags) {
+ int bottom, FontMetricsInt fmi, boolean needWidth) {
// Shaping needs to take into account context up to metric boundaries,
// but rendering needs to take into account character style boundaries.
- // So we iterate through metric runs, shape using the initial
- // paint (the same typeface is used up to the next metric boundary),
- // then within each metric run iterate through character style runs.
+ // So we iterate through metric runs to get metric bounds,
+ // then within each metric run iterate through character style runs
+ // for the run bounds.
float ox = x;
- for (int i = start, inext; i < offset; i = inext) {
+ for (int i = start, inext; i < measureLimit; i = inext) {
TextPaint wp = mWorkPaint;
wp.set(mPaint);
- int mnext;
+ int mlimit;
if (mSpanned == null) {
inext = limit;
- mnext = offset;
+ mlimit = measureLimit;
} else {
inext = mSpanned.nextSpanTransition(mStart + i, mStart + limit,
MetricAffectingSpan.class) - mStart;
- mnext = inext < offset ? inext : offset;
+ mlimit = inext < measureLimit ? inext : measureLimit;
MetricAffectingSpan[] spans = mSpanned.getSpans(mStart + i,
- mStart + mnext, MetricAffectingSpan.class);
+ mStart + mlimit, MetricAffectingSpan.class);
if (spans.length > 0) {
ReplacementSpan replacement = null;
@@ -807,44 +826,40 @@ class TextLine {
if (span instanceof ReplacementSpan) {
replacement = (ReplacementSpan)span;
} else {
- span.updateDrawState(wp); // XXX or measureState?
+ // We might have a replacement that uses the draw
+ // state, otherwise measure state would suffice.
+ span.updateDrawState(wp);
}
}
if (replacement != null) {
x += handleReplacement(replacement, wp, runIndex, i,
- mnext, runIsRtl, c, x, top, y, bottom, fmi,
- needWidth || mnext < offset, prepFlags);
+ mlimit, runIsRtl, c, x, top, y, bottom, fmi,
+ needWidth || mlimit < measureLimit);
continue;
}
}
}
- if (prepFlags != PREP_NONE) {
- handlePrep(wp, runIndex, i, inext, runIsRtl);
- }
+ if (mSpanned == null || c == null) {
+ x += handleText(wp, i, mlimit, i, inext, runIsRtl, c, x, top,
+ y, bottom, fmi, needWidth || mlimit < measureLimit);
+ } else {
+ for (int j = i, jnext; j < mlimit; j = jnext) {
+ jnext = mSpanned.nextSpanTransition(mStart + j,
+ mStart + mlimit, CharacterStyle.class) - mStart;
- if (prepFlags != PREP_ONLY) {
- if (mSpanned == null || c == null) {
- x += handleText(wp, i, mnext, runIsRtl, c, x, top,
- y, bottom, fmi, needWidth || mnext < offset);
- } else {
- for (int j = i, jnext; j < mnext; j = jnext) {
- jnext = mSpanned.nextSpanTransition(mStart + j,
- mStart + mnext, CharacterStyle.class) - mStart;
-
- CharacterStyle[] spans = mSpanned.getSpans(mStart + j,
- mStart + jnext, CharacterStyle.class);
-
- wp.set(mPaint);
- for (int k = 0; k < spans.length; k++) {
- CharacterStyle span = spans[k];
- span.updateDrawState(wp);
- }
+ CharacterStyle[] spans = mSpanned.getSpans(mStart + j,
+ mStart + jnext, CharacterStyle.class);
- x += handleText(wp, j, jnext, runIsRtl, c, x,
- top, y, bottom, fmi, needWidth || jnext < offset);
+ wp.set(mPaint);
+ for (int k = 0; k < spans.length; k++) {
+ CharacterStyle span = spans[k];
+ span.updateDrawState(wp);
}
+
+ x += handleText(wp, j, jnext, i, inext, runIsRtl, c, x,
+ top, y, bottom, fmi, needWidth || jnext < measureLimit);
}
}
}
@@ -852,79 +867,32 @@ class TextLine {
return x - ox;
}
- private static final int PREP_NONE = 0;
- private static final int PREP_NEEDED = 1;
- private static final int PREP_ONLY = 2;
-
- /**
- * Prepares text for measuring or rendering.
- *
- * @param paint the paint used to shape the text
- * @param runIndex the run index
- * @param start the start of the text to prepare
- * @param limit the limit of the text to prepare
- * @param runIsRtl true if the run is right-to-left
- */
- private void handlePrep(TextPaint paint, int runIndex, int start, int limit,
- boolean runIsRtl) {
-
- // The current implementation 'prepares' text by manipulating the
- // character array. In order to keep track of what ranges have
- // already been prepared, it uses the runIndex and the limit of
- // the prepared text within that run. This index is required
- // since operations that prepare the text always proceed in visual
- // order and the limit itself does not let us know which runs have
- // been processed and which have not.
- //
- // This bookkeeping is an attempt to let us process a line partially,
- // for example, by only shaping up to the cursor position. This may
- // not make sense if we can reuse the line, say by caching repeated
- // accesses to the same line for both measuring and drawing, since in
- // those cases we'd always prepare the entire line. At the
- // opposite extreme, we might shape and then immediately discard only
- // the run of text we're working with at the moment, instead of retaining
- // the results of shaping (as the chars array is). In this case as well
- // we would not need to do the index/limit bookkeeping.
- //
- // Technically, the only reason for bookkeeping is so that we don't
- // re-mirror already-mirrored glyphs, since the shaping and object
- // replacement operations will not change already-processed text.
-
- if (runIndex > mPreppedIndex ||
- (runIndex == mPreppedIndex && start >= mPreppedLimit)) {
- if (runIsRtl) {
- int runLen = limit - start;
- AndroidCharacter.mirror(mChars, start, runLen);
- ArabicShaping.SHAPER.shape(mChars, start, runLen);
-
- // Note: tweaked MockShaper to put '\ufeff' in place of
- // alef when it forms lam-alef ligatures, so no extra
- // processing is necessary here.
- }
- mPreppedIndex = runIndex;
- mPreppedLimit = limit;
- }
- }
-
/**
* Render a text run with the set-up paint.
*
* @param c the canvas
* @param wp the paint used to render the text
- * @param start the run start
- * @param limit the run limit
+ * @param start the start of the run
+ * @param end the end of the run
+ * @param contextStart the start of context for the run
+ * @param contextEnd the end of the context for the run
* @param runIsRtl true if the run is right-to-left
* @param x the x position of the left edge of the run
* @param y the baseline of the run
*/
- private void drawTextRun(Canvas c, TextPaint wp, int start, int limit,
- boolean runIsRtl, float x, int y) {
+ private void drawTextRun(Canvas c, TextPaint wp, int start, int end,
+ int contextStart, int contextEnd, boolean runIsRtl, float x, int y) {
int flags = runIsRtl ? Canvas.DIRECTION_RTL : Canvas.DIRECTION_LTR;
if (mCharsValid) {
- c.drawTextRun(mChars, start, limit - start, x, y, flags, wp);
+ int count = end - start;
+ int contextCount = contextEnd - contextStart;
+ c.drawTextRun(mChars, start, count, contextStart, contextCount,
+ x, y, flags, wp);
} else {
- c.drawTextRun(mText, mStart + start, mStart + limit, x, y, flags, wp);
+ int delta = mStart;
+ c.drawTextRun(mText, delta + start, delta + end,
+ delta + contextStart, delta + contextEnd, x, y, flags, wp);
}
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index a24668a570db..b90ac9dd8674 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -2782,8 +2782,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
public void drawTextRun(Canvas c, int start, int end,
- float x, float y, int flags, Paint p) {
- c.drawTextRun(mChars, start + mStart, end - start, x, y, flags, p);
+ int contextStart, int contextEnd, float x, float y, int flags, Paint p) {
+ int count = end - start;
+ int contextCount = contextEnd - contextStart;
+ c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
+ contextCount, x, y, flags, p);
}
public float measureText(int start, int end, Paint p) {
@@ -2793,6 +2796,23 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
public int getTextWidths(int start, int end, float[] widths, Paint p) {
return p.getTextWidths(mChars, start + mStart, end - start, widths);
}
+
+ public float getTextRunAdvances(int start, int end, int contextStart,
+ int contextEnd, int flags, float[] advances, int advancesIndex,
+ Paint p) {
+ int count = end - start;
+ int contextCount = contextEnd - contextStart;
+ return p.getTextRunAdvances(mChars, start + mStart, count,
+ contextStart + mStart, contextCount, flags, advances,
+ advancesIndex);
+ }
+
+ public int getTextRunCursor(int contextStart, int contextEnd, int flags,
+ int offset, int cursorOpt, Paint p) {
+ int contextCount = contextEnd - contextStart;
+ return p.getTextRunCursor(mChars, contextStart + mStart,
+ contextCount, flags, offset + mStart, cursorOpt);
+ }
}
/**
diff --git a/core/jni/android/graphics/Canvas.cpp b/core/jni/android/graphics/Canvas.cpp
index 4c7e76237db8..b044271b961d 100644
--- a/core/jni/android/graphics/Canvas.cpp
+++ b/core/jni/android/graphics/Canvas.cpp
@@ -31,6 +31,10 @@
#include "SkMeshUtils.h"
#include "unicode/ubidi.h"
+#include "unicode/ushape.h"
+
+// temporary for debugging
+#include <utils/Log.h>
#define TIME_DRAWx
@@ -764,19 +768,43 @@ public:
indices, indexCount, *paint);
}
- static void shapeRtlText__(const jchar* text, jsize len, jsize start, jsize count, jchar* shaped) {
- // fake shaping, just reverse the text
- for (int i = 0; i < count; ++i) {
- shaped[i] = text[start + count - 1 - i];
+ /**
+ * @context the text context
+ * @start the start of the text to render
+ * @count the length of the text to render, start + count must be <= len
+ * @contextCount the length of the context
+ * @shaped where to put the shaped text, must have capacity for count uchars
+ * @return the length of the shaped text, or -1 if error
+ */
+ static int shapeRtlText__(const jchar* context, jsize start, jsize count, jsize contextCount,
+ jchar* shaped, UErrorCode &status) {
+ jchar buffer[contextCount];
+
+ // We'd rather use harfbuzz here. Use character based shaping for now.
+
+ // Use fixed length since we need to keep start and count valid
+ u_shapeArabic(context, contextCount, buffer, contextCount,
+ U_SHAPE_LENGTH_FIXED_SPACES_NEAR |
+ U_SHAPE_TEXT_DIRECTION_LOGICAL | U_SHAPE_LETTERS_SHAPE |
+ U_SHAPE_X_LAMALEF_SUB_ALTERNATE, &status);
+
+ if (!U_SUCCESS(status)) {
+ return 0;
}
- // fix surrogate pairs, if any
- for (int i = 1; i < count; ++i) {
- if (shaped[i] >= 0xd800 && shaped[i] < 0xdc00 &&
- shaped[i-1] >= 0xdc00 && shaped[i-1] < 0xe000) {
- jchar c = shaped[i]; shaped[i] = shaped[i-1]; shaped[i-1] = c;
- i += 1;
+
+ // trim out 0xffff following ligatures, if any
+ int end = 0;
+ for (int i = start, e = start + count; i < e; ++i) {
+ if (buffer[i] == 0xffff) {
+ continue;
}
+ buffer[end++] = buffer[i];
}
+ count = end;
+ // LOG(LOG_INFO, "CSRTL", "start %d count %d ccount %d\n", start, count, contextCount);
+ ubidi_writeReverse(buffer, count, shaped, count, UBIDI_DO_MIRRORING | UBIDI_OUTPUT_REVERSE
+ | UBIDI_KEEP_BASE_COMBINING, &status);
+ return count;
}
static void drawText__(JNIEnv* env, SkCanvas* canvas, const jchar* text, jsize len,
@@ -784,7 +812,7 @@ public:
SkScalar x_ = SkFloatToScalar(x);
SkScalar y_ = SkFloatToScalar(y);
- SkPaint::Align horiz = paint->getTextAlign();
+ SkPaint::Align horiz = paint->getTextAlign();
bool needBidi = (flags == kBidi_RTL) || (flags == kBidi_Default_RTL);
if (!needBidi && flags < kBidi_Force_LTR) {
@@ -815,7 +843,7 @@ public:
case kBidi_Default_LTR: lineDir = UBIDI_DEFAULT_LTR; break;
case kBidi_Default_RTL: lineDir = UBIDI_DEFAULT_RTL; break;
}
-
+
UBiDi* bidi = ubidi_open();
ubidi_setPara(bidi, text, len, lineDir, NULL, &status);
if (U_SUCCESS(status)) {
@@ -843,9 +871,9 @@ public:
}
}
ubidi_close(bidi);
- }
+ }
} else {
- shapeRtlText__(text, len, 0, len, shaped);
+ len = shapeRtlText__(text, 0, len, len, shaped, status);
}
}
}
@@ -886,7 +914,7 @@ public:
free(shaped);
}
}
-
+
static void drawText___CIIFFIPaint(JNIEnv* env, jobject, SkCanvas* canvas,
jcharArray text, int index, int count,
jfloat x, jfloat y, int flags, SkPaint* paint) {
@@ -894,72 +922,60 @@ public:
drawText__(env, canvas, textArray + index, count, x, y, flags, paint);
env->ReleaseCharArrayElements(text, textArray, JNI_ABORT);
}
-
+
static void drawText__StringIIFFIPaint(JNIEnv* env, jobject,
- SkCanvas* canvas, jstring text,
+ SkCanvas* canvas, jstring text,
int start, int end,
jfloat x, jfloat y, int flags, SkPaint* paint) {
const jchar* textArray = env->GetStringChars(text, NULL);
drawText__(env, canvas, textArray + start, end - start, x, y, flags, paint);
env->ReleaseStringChars(text, textArray);
}
-
- // Draws a unidirectional run of text. Does not run bidi, but does reorder the
- // text and run shaping (or will, when we have harfbuzz support).
- static void drawTextRun__(JNIEnv* env, SkCanvas* canvas, const jchar* chars, int len,
- int start, int count,
+
+ // Draws a unidirectional run of text.
+ static void drawTextRun__(JNIEnv* env, SkCanvas* canvas, const jchar* chars,
+ jint start, jint count, jint contextCount,
jfloat x, jfloat y, int flags, SkPaint* paint) {
SkScalar x_ = SkFloatToScalar(x);
SkScalar y_ = SkFloatToScalar(y);
uint8_t rtl = flags & 0x1;
-
- UErrorCode status = U_ZERO_ERROR;
- jchar *shaped = NULL;
if (rtl) {
- shaped = (jchar *)malloc(count * sizeof(jchar));
- if (!shaped) {
- status = U_MEMORY_ALLOCATION_ERROR;
+ jchar context[contextCount];
+ UErrorCode status = U_ZERO_ERROR;
+ count = shapeRtlText__(chars, start, count, contextCount, context, status);
+ if (U_SUCCESS(status)) {
+ canvas->drawText(context, count << 1, x_, y_, *paint);
} else {
- shapeRtlText__(chars, len, start, count, shaped);
+ doThrowIAE(env, "shaping error");
}
- }
-
- if (!U_SUCCESS(status)) {
- char buffer[30];
- sprintf(buffer, "DrawTextRun error %d", status);
- doThrowIAE(env, buffer);
} else {
- if (shaped) {
- canvas->drawText(shaped, count << 1, x_, y_, *paint);
- } else {
- canvas->drawText(chars + start, count << 1, x_, y_, *paint);
- }
- }
-
- if (shaped) {
- free(shaped);
+ canvas->drawText(chars + start, count << 1, x_, y_, *paint);
}
}
- static void drawTextRun___CIIFFIPaint(
+ static void drawTextRun___CIIIIFFIPaint(
JNIEnv* env, jobject, SkCanvas* canvas, jcharArray text, int index,
- int count, jfloat x, jfloat y, int flags, SkPaint* paint) {
+ int count, int contextIndex, int contextCount,
+ jfloat x, jfloat y, int flags, SkPaint* paint) {
- jint len = env->GetArrayLength(text);
jchar* chars = env->GetCharArrayElements(text, NULL);
- drawTextRun__(env, canvas, chars, len, index, count, x, y, flags, paint);
+ drawTextRun__(env, canvas, chars + contextIndex, index - contextIndex,
+ count, contextCount, x, y, flags, paint);
env->ReleaseCharArrayElements(text, chars, JNI_ABORT);
}
- static void drawTextRun__StringIIFFIPaint(
- JNIEnv* env, jobject obj, SkCanvas* canvas, jstring text, int start,
- int end, jfloat x, jfloat y, int flags, SkPaint* paint) {
+ static void drawTextRun__StringIIIIFFIPaint(
+ JNIEnv* env, jobject obj, SkCanvas* canvas, jstring text, jint start,
+ jint end, jint contextStart, jint contextEnd,
+ jfloat x, jfloat y, jint flags, SkPaint* paint) {
- jint len = env->GetStringLength(text);
+ jint count = end - start;
+ jint contextCount = contextEnd - contextStart;
const jchar* chars = env->GetStringChars(text, NULL);
- drawTextRun__(env, canvas, chars, len, start, end - start, x, y, flags, paint);
+ drawTextRun__(env, canvas, chars + contextStart, start - contextStart,
+ count, contextCount, x, y, flags, paint);
env->ReleaseStringChars(text, chars);
}
@@ -985,7 +1001,7 @@ public:
}
delete[] posPtr;
}
-
+
static void drawPosText__String_FPaint(JNIEnv* env, jobject,
SkCanvas* canvas, jstring text,
jfloatArray pos, SkPaint* paint) {
@@ -1008,7 +1024,7 @@ public:
}
delete[] posPtr;
}
-
+
static void drawTextOnPath___CIIPathFFPaint(JNIEnv* env, jobject,
SkCanvas* canvas, jcharArray text, int index, int count,
SkPath* path, jfloat hOffset, jfloat vOffset, SkPaint* paint) {
@@ -1018,7 +1034,7 @@ public:
SkFloatToScalar(hOffset), SkFloatToScalar(vOffset), *paint);
env->ReleaseCharArrayElements(text, textArray, 0);
}
-
+
static void drawTextOnPath__StringPathFFPaint(JNIEnv* env, jobject,
SkCanvas* canvas, jstring text, SkPath* path,
jfloat hOffset, jfloat vOffset, SkPaint* paint) {
@@ -1028,7 +1044,7 @@ public:
SkFloatToScalar(hOffset), SkFloatToScalar(vOffset), *paint);
env->ReleaseStringChars(text, text_);
}
-
+
static bool getClipBounds(JNIEnv* env, jobject, SkCanvas* canvas,
jobject bounds) {
SkRect r;
@@ -1121,7 +1137,6 @@ static JNINativeMethod gCanvasMethods[] = {
(void*) SkCanvasGlue::drawBitmapRR},
{"native_drawBitmap", "(I[IIIFFIIZI)V",
(void*)SkCanvasGlue::drawBitmapArray},
-
{"nativeDrawBitmapMatrix", "(IIII)V",
(void*)SkCanvasGlue::drawBitmapMatrix},
{"nativeDrawBitmapMesh", "(IIII[FI[III)V",
@@ -1132,10 +1147,10 @@ static JNINativeMethod gCanvasMethods[] = {
(void*) SkCanvasGlue::drawText___CIIFFIPaint},
{"native_drawText","(ILjava/lang/String;IIFFII)V",
(void*) SkCanvasGlue::drawText__StringIIFFIPaint},
- {"native_drawTextRun","(I[CIIFFII)V",
- (void*) SkCanvasGlue::drawTextRun___CIIFFIPaint},
- {"native_drawTextRun","(ILjava/lang/String;IIFFII)V",
- (void*) SkCanvasGlue::drawTextRun__StringIIFFIPaint},
+ {"native_drawTextRun","(I[CIIIIFFII)V",
+ (void*) SkCanvasGlue::drawTextRun___CIIIIFFIPaint},
+ {"native_drawTextRun","(ILjava/lang/String;IIIIFFII)V",
+ (void*) SkCanvasGlue::drawTextRun__StringIIIIFFIPaint},
{"native_drawPosText","(I[CII[FI)V",
(void*) SkCanvasGlue::drawPosText___CII_FPaint},
{"native_drawPosText","(ILjava/lang/String;[FI)V",
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index 780badc253ee..ca9c9defc2f6 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -31,6 +31,10 @@
#include "SkShader.h"
#include "SkTypeface.h"
#include "SkXfermode.h"
+#include "unicode/ushape.h"
+
+// temporary for debugging
+#include <utils/Log.h>
namespace android {
@@ -57,6 +61,9 @@ static void defaultSettingsForAndroid(SkPaint* paint) {
class SkPaintGlue {
public:
+ enum MoveOpt {
+ AFTER, AT_OR_AFTER, BEFORE, AT_OR_BEFORE, AT
+ };
static void finalizer(JNIEnv* env, jobject clazz, SkPaint* obj) {
delete obj;
@@ -395,7 +402,184 @@ public:
env->ReleaseStringChars(text, textArray);
return count;
}
-
+
+ static jfloat doTextRunAdvances(JNIEnv *env, SkPaint *paint, const jchar *text, jint start, jint count, jint contextCount, jint flags,
+ jfloatArray advances, jint advancesIndex) {
+ jfloat advancesArray[count];
+ jchar buffer[contextCount];
+
+ SkScalar* scalarArray = (SkScalar *)advancesArray;
+ jfloat totalAdvance = 0;
+
+ // this is where we'd call harfbuzz
+ // for now we just use ushape.c
+
+ int widths;
+ if (flags & 0x1) { // rtl, call arabic shaping in case
+ UErrorCode status = U_ZERO_ERROR;
+ // Use fixed length since we need to keep start and count valid
+ u_shapeArabic(text, contextCount, buffer, contextCount,
+ U_SHAPE_LENGTH_FIXED_SPACES_NEAR |
+ U_SHAPE_TEXT_DIRECTION_LOGICAL | U_SHAPE_LETTERS_SHAPE |
+ U_SHAPE_X_LAMALEF_SUB_ALTERNATE, &status);
+ // we shouldn't fail unless there's an out of memory condition,
+ // in which case we're hosed anyway
+ for (int i = start, e = i + count; i < e; ++i) {
+ if (buffer[i] == 0xffff) {
+ buffer[i] = 0x200b; // zero-width-space for skia
+ }
+ }
+ widths = paint->getTextWidths(buffer + start, count << 1, scalarArray);
+ } else {
+ widths = paint->getTextWidths(text + start, count << 1, scalarArray);
+ }
+
+ if (widths < count) {
+ // Skia operates on code points, not code units, so surrogate pairs return only
+ // one value. Expand the result so we have one value per UTF-16 code unit.
+
+ // Note, skia's getTextWidth gets confused if it encounters a surrogate pair,
+ // leaving the remaining widths zero. Not nice.
+ const jchar *chars = text + start;
+ for (int i = 0, p = 0; i < widths; ++i) {
+ totalAdvance += advancesArray[p++] = SkScalarToFloat(scalarArray[i]);
+ if (p < count && chars[p] >= 0xdc00 && chars[p] < 0xe000 &&
+ chars[p-1] >= 0xd800 && chars[p-1] < 0xdc00) {
+ advancesArray[p++] = 0;
+ }
+ }
+ } else {
+ for (int i = 0; i < count; i++) {
+ totalAdvance += advancesArray[i] = SkScalarToFloat(scalarArray[i]);
+ }
+ }
+
+ if (advances != NULL) {
+ env->SetFloatArrayRegion(advances, advancesIndex, count, advancesArray);
+ }
+ return totalAdvance;
+ }
+
+ static float getTextRunAdvances___CIIIII_FI(JNIEnv* env, jobject clazz, SkPaint* paint,
+ jcharArray text, jint index, jint count, jint contextIndex, jint contextCount,
+ jint flags, jfloatArray advances, jint advancesIndex) {
+ jchar* textArray = env->GetCharArrayElements(text, NULL);
+ jfloat result = doTextRunAdvances(env, paint, textArray + contextIndex,
+ index - contextIndex, count, contextCount, flags, advances, advancesIndex);
+ env->ReleaseCharArrayElements(text, textArray, JNI_ABORT);
+ return result;
+ }
+
+ static float getTextRunAdvances__StringIIIII_FI(JNIEnv* env, jobject clazz, SkPaint* paint,
+ jstring text, jint start, jint end, jint contextStart, jint contextEnd, jint flags,
+ jfloatArray advances, jint advancesIndex) {
+ const jchar* textArray = env->GetStringChars(text, NULL);
+ jfloat result = doTextRunAdvances(env, paint, textArray + contextStart,
+ start - contextStart, end - start, contextEnd - contextStart, flags, advances,
+ advancesIndex);
+ env->ReleaseStringChars(text, textArray);
+ return result;
+ }
+
+ static jint doTextRunCursor(JNIEnv *env, SkPaint* paint, const jchar *text, jint start,
+ jint count, jint flags, jint offset, jint opt) {
+ SkScalar scalarArray[count];
+ jchar buffer[count];
+
+ // this is where we'd call harfbuzz
+ // for now we just use ushape.c and widths returned from skia
+
+ int widths;
+ if (flags & 0x1) { // rtl, call arabic shaping in case
+ UErrorCode status = U_ZERO_ERROR;
+ // Use fixed length since we need to keep start and count valid
+ u_shapeArabic(text + start, count, buffer, count,
+ U_SHAPE_LENGTH_FIXED_SPACES_NEAR | U_SHAPE_TEXT_DIRECTION_LOGICAL |
+ U_SHAPE_LETTERS_SHAPE | U_SHAPE_X_LAMALEF_SUB_ALTERNATE, &status);
+ // we shouldn't fail unless there's an out of memory condition,
+ // in which case we're hosed anyway
+ for (int i = 0; i < count; ++i) {
+ if (buffer[i] == 0xffff) {
+ buffer[i] = 0x200b; // zero-width-space for skia
+ }
+ }
+ widths = paint->getTextWidths(buffer, count << 1, scalarArray);
+ } else {
+ widths = paint->getTextWidths(text + start, count << 1, scalarArray);
+ }
+
+ if (widths < count) {
+ // Skia operates on code points, not code units, so surrogate pairs return only one
+ // value. Expand the result so we have one value per UTF-16 code unit.
+
+ // Note, skia's getTextWidth gets confused if it encounters a surrogate pair,
+ // leaving the remaining widths zero. Not nice.
+ const jchar *chars = text + start;
+ for (int i = count, p = widths - 1; --i > p;) {
+ if (chars[i] >= 0xdc00 && chars[i] < 0xe000 &&
+ chars[i-1] >= 0xd800 && chars[i-1] < 0xdc00) {
+ scalarArray[i] = 0;
+ } else {
+ scalarArray[i] = scalarArray[--p];
+ }
+ }
+ }
+
+ jint pos = offset - start;
+ switch (opt) {
+ case AFTER:
+ if (pos < count) {
+ pos += 1;
+ }
+ // fall through
+ case AT_OR_AFTER:
+ while (pos < count && scalarArray[pos] == 0) {
+ ++pos;
+ }
+ break;
+ case BEFORE:
+ if (pos > 0) {
+ --pos;
+ }
+ // fall through
+ case AT_OR_BEFORE:
+ while (pos > 0 && scalarArray[pos] == 0) {
+ --pos;
+ }
+ break;
+ case AT:
+ default:
+ if (scalarArray[pos] == 0) {
+ pos = -1;
+ }
+ break;
+ }
+
+ if (pos != -1) {
+ pos += start;
+ }
+
+ return pos;
+ }
+
+ static jint getTextRunCursor___C(JNIEnv* env, jobject clazz, SkPaint* paint, jcharArray text,
+ jint contextStart, jint contextCount, jint flags, jint offset, jint cursorOpt) {
+ jchar* textArray = env->GetCharArrayElements(text, NULL);
+ jint result = doTextRunCursor(env, paint, textArray, contextStart, contextCount, flags,
+ offset, cursorOpt);
+ env->ReleaseCharArrayElements(text, textArray, JNI_ABORT);
+ return result;
+ }
+
+ static jint getTextRunCursor__String(JNIEnv* env, jobject clazz, SkPaint* paint, jstring text,
+ jint contextStart, jint contextEnd, jint flags, jint offset, jint cursorOpt) {
+ const jchar* textArray = env->GetStringChars(text, NULL);
+ jint result = doTextRunCursor(env, paint, textArray, contextStart,
+ contextEnd - contextStart, flags, offset, cursorOpt);
+ env->ReleaseStringChars(text, textArray);
+ return result;
+ }
+
static void getTextPath___CIIFFPath(JNIEnv* env, jobject clazz, SkPaint* paint, jcharArray text, int index, int count, jfloat x, jfloat y, SkPath* path) {
const jchar* textArray = env->GetCharArrayElements(text, NULL);
paint->getTextPath(textArray + index, count << 1, SkFloatToScalar(x), SkFloatToScalar(y), path);
@@ -576,6 +760,13 @@ static JNINativeMethod methods[] = {
{"native_breakText","(Ljava/lang/String;ZF[F)I", (void*) SkPaintGlue::breakTextS},
{"native_getTextWidths","(I[CII[F)I", (void*) SkPaintGlue::getTextWidths___CII_F},
{"native_getTextWidths","(ILjava/lang/String;II[F)I", (void*) SkPaintGlue::getTextWidths__StringII_F},
+ {"native_getTextRunAdvances","(I[CIIIII[FI)F", (void*)
+ SkPaintGlue::getTextRunAdvances___CIIIII_FI},
+ {"native_getTextRunAdvances","(ILjava/lang/String;IIIII[FI)F",
+ (void*) SkPaintGlue::getTextRunAdvances__StringIIIII_FI},
+ {"native_getTextRunCursor", "(I[CIIIII)I", (void*) SkPaintGlue::getTextRunCursor___C},
+ {"native_getTextRunCursor", "(ILjava/lang/String;IIIII)I",
+ (void*) SkPaintGlue::getTextRunCursor__String},
{"native_getTextPath","(I[CIIFFI)V", (void*) SkPaintGlue::getTextPath___CIIFFPath},
{"native_getTextPath","(ILjava/lang/String;IIFFI)V", (void*) SkPaintGlue::getTextPath__StringIIFFPath},
{"nativeGetStringBounds", "(ILjava/lang/String;IILandroid/graphics/Rect;)V",
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index e2634d12db33..e55635035aa1 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -1354,18 +1354,22 @@ public class Canvas {
* determined by the Paint's TextAlign value.
*
* @param text the text to render
- * @param index the start of the text to render. Data before this position
- * can be used for shaping context.
- * @param length the length of the text to render. Data at or after this
- * position (start + length) can be used for shaping context.
+ * @param index the start of the text to render
+ * @param count the count of chars to render
+ * @param contextIndex the start of the context for shaping. Must be
+ * no greater than index.
+ * @param contextCount the number of characters in the context for shaping.
+ * ContexIndex + contextCount must be no less than index
+ * + count.
* @param x the x position at which to draw the text
* @param y the y position at which to draw the text
- * @param dir the run direction, either {@link #DIRECTION_LTR} or
- * {@link #DIRECTION_RTL}.
+ * @param dir the run direction, either {@link #DIRECTION_LTR} or
+ * {@link #DIRECTION_RTL}.
* @param paint the paint
* @hide
*/
- public void drawTextRun(char[] text, int index, int length, float x, float y, int dir,
+ public void drawTextRun(char[] text, int index, int count,
+ int contextIndex, int contextCount, float x, float y, int dir,
Paint paint) {
if (text == null) {
@@ -1374,14 +1378,15 @@ public class Canvas {
if (paint == null) {
throw new NullPointerException("paint is null");
}
- if ((index | length | text.length - index - length) < 0) {
+ if ((index | count | text.length - index - count) < 0) {
throw new IndexOutOfBoundsException();
}
if (dir != DIRECTION_LTR && dir != DIRECTION_RTL) {
throw new IllegalArgumentException("unknown dir: " + dir);
}
- native_drawTextRun(mNativeCanvas, text, index, length, x, y, dir, paint.mNativePaint);
+ native_drawTextRun(mNativeCanvas, text, index, count,
+ contextIndex, contextCount, x, y, dir, paint.mNativePaint);
}
/**
@@ -1389,7 +1394,7 @@ public class Canvas {
* bidi on the provided text, but renders it as a uniform right-to-left or
* left-to-right run, as indicated by dir. Alignment of the text is as
* determined by the Paint's TextAlign value.
- *
+ *
* @param text the text to render
* @param start the start of the text to render. Data before this position
* can be used for shaping context.
@@ -1401,8 +1406,9 @@ public class Canvas {
* @param paint the paint
* @hide
*/
- public void drawTextRun(CharSequence text, int start, int end, float x,
- float y, int dir, Paint paint) {
+ public void drawTextRun(CharSequence text, int start, int end,
+ int contextStart, int contextEnd, float x, float y, int dir,
+ Paint paint) {
if (text == null) {
throw new NullPointerException("text is null");
@@ -1418,16 +1424,18 @@ public class Canvas {
if (text instanceof String || text instanceof SpannedString ||
text instanceof SpannableString) {
- native_drawTextRun(mNativeCanvas, text.toString(), start, end, x, y,
- flags, paint.mNativePaint);
+ native_drawTextRun(mNativeCanvas, text.toString(), start, end,
+ contextStart, contextEnd, x, y, flags, paint.mNativePaint);
} else if (text instanceof GraphicsOperations) {
- ((GraphicsOperations) text).drawTextRun(this, start, end, x, y, flags,
- paint);
+ ((GraphicsOperations) text).drawTextRun(this, start, end,
+ contextStart, contextEnd, x, y, flags, paint);
} else {
- char[] buf = TemporaryBuffer.obtain(end - start);
- TextUtils.getChars(text, start, end, buf, 0);
- native_drawTextRun(mNativeCanvas, buf, 0, end - start, x, y,
- flags, paint.mNativePaint);
+ int contextLen = contextEnd - contextStart;
+ int len = end - start;
+ char[] buf = TemporaryBuffer.obtain(contextLen);
+ TextUtils.getChars(text, contextStart, contextEnd, buf, 0);
+ native_drawTextRun(mNativeCanvas, buf, start - contextStart, len,
+ 0, contextLen, x, y, flags, paint.mNativePaint);
TemporaryBuffer.recycle(buf);
}
}
@@ -1679,11 +1687,13 @@ public class Canvas {
int start, int end, float x,
float y, int flags, int paint);
- private static native void native_drawTextRun(int nativeCanvas, String
- text, int start, int end, float x, float y, int flags, int paint);
+ private static native void native_drawTextRun(int nativeCanvas, String text,
+ int start, int end, int contextStart, int contextEnd,
+ float x, float y, int flags, int paint);
- private static native void native_drawTextRun(int nativeCanvas, char[]
- text, int start, int len, float x, float y, int flags, int paint);
+ private static native void native_drawTextRun(int nativeCanvas, char[] text,
+ int start, int count, int contextStart, int contextCount,
+ float x, float y, int flags, int paint);
private static native void native_drawPosText(int nativeCanvas,
char[] text, int index,
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index b5649293e307..862a2eca0abd 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -125,13 +125,65 @@ public class Paint {
* @hide
*/
private static final int BIDI_MAX_FLAG_VALUE = BIDI_FORCE_RTL;
-
+
/**
* Mask for bidi flags.
* @hide
*/
private static final int BIDI_FLAG_MASK = 0x7;
-
+
+ /**
+ * Flag for getTextRunAdvances indicating left-to-right run direction.
+ * @hide
+ */
+ public static final int DIRECTION_LTR = 0;
+
+ /**
+ * Flag for getTextRunAdvances indicating right-to-left run direction.
+ * @hide
+ */
+ public static final int DIRECTION_RTL = 1;
+
+ /**
+ * Option for getTextRunCursor to compute the valid cursor after
+ * offset or the limit of the context, whichever is less.
+ * @hide
+ */
+ public static final int CURSOR_AFTER = 0;
+
+ /**
+ * Option for getTextRunCursor to compute the valid cursor at or after
+ * the offset or the limit of the context, whichever is less.
+ * @hide
+ */
+ public static final int CURSOR_AT_OR_AFTER = 1;
+
+ /**
+ * Option for getTextRunCursor to compute the valid cursor before
+ * offset or the start of the context, whichever is greater.
+ * @hide
+ */
+ public static final int CURSOR_BEFORE = 2;
+
+ /**
+ * Option for getTextRunCursor to compute the valid cursor at or before
+ * offset or the start of the context, whichever is greater.
+ * @hide
+ */
+ public static final int CURSOR_AT_OR_BEFORE = 3;
+
+ /**
+ * Option for getTextRunCursor to return offset if the cursor at offset
+ * is valid, or -1 if it isn't.
+ * @hide
+ */
+ public static final int CURSOR_AT = 4;
+
+ /**
+ * Maximum cursor option value.
+ */
+ private static final int CURSOR_OPT_MAX_VALUE = CURSOR_AT;
+
/**
* The Style specifies if the primitive being drawn is filled, stroked, or
* both (in the same color). The default is FILL.
@@ -1317,10 +1369,10 @@ public class Paint {
}
char[] buf = TemporaryBuffer.obtain(end - start);
- TextUtils.getChars(text, start, end, buf, 0);
- int result = getTextWidths(buf, 0, end - start, widths);
+ TextUtils.getChars(text, start, end, buf, 0);
+ int result = getTextWidths(buf, 0, end - start, widths);
TemporaryBuffer.recycle(buf);
- return result;
+ return result;
}
/**
@@ -1367,6 +1419,284 @@ public class Paint {
}
/**
+ * Convenience overload that takes a char array instead of a
+ * String.
+ *
+ * @see #getTextRunAdvances(String, int, int, int, int, int, float[], int)
+ * @hide
+ */
+ public float getTextRunAdvances(char[] chars, int index, int count,
+ int contextIndex, int contextCount, int flags, float[] advances,
+ int advancesIndex) {
+
+ if ((index | count | contextIndex | contextCount | advancesIndex
+ | (index - contextIndex)
+ | ((contextIndex + contextCount) - (index + count))
+ | (chars.length - (contextIndex + contextCount))
+ | (advances == null ? 0 :
+ (advances.length - (advancesIndex + count)))) < 0) {
+ throw new IndexOutOfBoundsException();
+ }
+ if (flags != DIRECTION_LTR && flags != DIRECTION_RTL) {
+ throw new IllegalArgumentException("unknown flags value: " + flags);
+ }
+
+ if (!mHasCompatScaling) {
+ return native_getTextRunAdvances(mNativePaint, chars, index, count,
+ contextIndex, contextCount, flags, advances, advancesIndex);
+ }
+
+ final float oldSize = getTextSize();
+ setTextSize(oldSize * mCompatScaling);
+ float res = native_getTextRunAdvances(mNativePaint, chars, index, count,
+ contextIndex, contextCount, flags, advances, advancesIndex);
+ setTextSize(oldSize);
+
+ if (advances != null) {
+ for (int i = advancesIndex, e = i + count; i < e; i++) {
+ advances[i] *= mInvCompatScaling;
+ }
+ }
+ return res * mInvCompatScaling; // assume errors are not significant
+ }
+
+ /**
+ * Convenience overload that takes a CharSequence instead of a
+ * String.
+ *
+ * @see #getTextRunAdvances(String, int, int, int, int, int, float[], int)
+ * @hide
+ */
+ public float getTextRunAdvances(CharSequence text, int start, int end,
+ int contextStart, int contextEnd, int flags, float[] advances,
+ int advancesIndex) {
+
+ if (text instanceof String) {
+ return getTextRunAdvances((String) text, start, end,
+ contextStart, contextEnd, flags, advances, advancesIndex);
+ }
+ if (text instanceof SpannedString ||
+ text instanceof SpannableString) {
+ return getTextRunAdvances(text.toString(), start, end,
+ contextStart, contextEnd, flags, advances, advancesIndex);
+ }
+ if (text instanceof GraphicsOperations) {
+ return ((GraphicsOperations) text).getTextRunAdvances(start, end,
+ contextStart, contextEnd, flags, advances, advancesIndex, this);
+ }
+
+ int contextLen = contextEnd - contextStart;
+ int len = end - start;
+ char[] buf = TemporaryBuffer.obtain(contextLen);
+ TextUtils.getChars(text, start, end, buf, 0);
+ float result = getTextRunAdvances(buf, start - contextStart, len,
+ 0, contextLen, flags, advances, advancesIndex);
+ TemporaryBuffer.recycle(buf);
+ return result;
+ }
+
+ /**
+ * Returns the total advance width for the characters in the run
+ * between start and end, and if advances is not null, the advance
+ * assigned to each of these characters (java chars).
+ *
+ * <p>The trailing surrogate in a valid surrogate pair is assigned
+ * an advance of 0. Thus the number of returned advances is
+ * always equal to count, not to the number of unicode codepoints
+ * represented by the run.
+ *
+ * <p>In the case of conjuncts or combining marks, the total
+ * advance is assigned to the first logical character, and the
+ * following characters are assigned an advance of 0.
+ *
+ * <p>This generates the sum of the advances of glyphs for
+ * characters in a reordered cluster as the width of the first
+ * logical character in the cluster, and 0 for the widths of all
+ * other characters in the cluster. In effect, such clusters are
+ * treated like conjuncts.
+ *
+ * <p>The shaping bounds limit the amount of context available
+ * outside start and end that can be used for shaping analysis.
+ * These bounds typically reflect changes in bidi level or font
+ * metrics across which shaping does not occur.
+ *
+ * @param text the text to measure
+ * @param start the index of the first character to measure
+ * @param end the index past the last character to measure
+ * @param contextStart the index of the first character to use for shaping context,
+ * must be <= start
+ * @param contextEnd the index past the last character to use for shaping context,
+ * must be >= end
+ * @param flags the flags to control the advances, either {@link #DIRECTION_LTR}
+ * or {@link #DIRECTION_RTL}
+ * @param advances array to receive the advances, must have room for all advances,
+ * can be null if only total advance is needed
+ * @param advancesIndex the position in advances at which to put the
+ * advance corresponding to the character at start
+ * @return the total advance
+ *
+ * @hide
+ */
+ public float getTextRunAdvances(String text, int start, int end, int contextStart,
+ int contextEnd, int flags, float[] advances, int advancesIndex) {
+
+ if ((start | end | contextStart | contextEnd | advancesIndex | (end - start)
+ | (start - contextStart) | (contextEnd - end)
+ | (text.length() - contextEnd)
+ | (advances == null ? 0 :
+ (advances.length - advancesIndex - (end - start)))) < 0) {
+ throw new IndexOutOfBoundsException();
+ }
+ if (flags != DIRECTION_LTR && flags != DIRECTION_RTL) {
+ throw new IllegalArgumentException("unknown flags value: " + flags);
+ }
+
+ if (!mHasCompatScaling) {
+ return native_getTextRunAdvances(mNativePaint, text, start, end,
+ contextStart, contextEnd, flags, advances, advancesIndex);
+ }
+
+ final float oldSize = getTextSize();
+ setTextSize(oldSize * mCompatScaling);
+ float totalAdvance = native_getTextRunAdvances(mNativePaint, text, start, end,
+ contextStart, contextEnd, flags, advances, advancesIndex);
+ setTextSize(oldSize);
+
+ if (advances != null) {
+ for (int i = advancesIndex, e = i + (end - start); i < e; i++) {
+ advances[i] *= mInvCompatScaling;
+ }
+ }
+ return totalAdvance * mInvCompatScaling; // assume errors are insignificant
+ }
+
+ /**
+ * Returns the next cursor position in the run. This avoids placing the
+ * cursor between surrogates, between characters that form conjuncts,
+ * between base characters and combining marks, or within a reordering
+ * cluster.
+ *
+ * <p>ContextStart and offset are relative to the start of text.
+ * The context is the shaping context for cursor movement, generally
+ * the bounds of the metric span enclosing the cursor in the direction of
+ * movement.
+ *
+ * <p>If cursorOpt is {@link #CURSOR_AT} and the offset is not a valid
+ * cursor position, this returns -1. Otherwise this will never return a
+ * value before contextStart or after contextStart + contextLength.
+ *
+ * @param text the text
+ * @param contextStart the start of the context
+ * @param contextLength the length of the context
+ * @param flags either {@link #DIRECTION_RTL} or {@link #DIRECTION_LTR}
+ * @param offset the cursor position to move from
+ * @param cursorOpt how to move the cursor, one of {@link #CURSOR_AFTER},
+ * {@link #CURSOR_AT_OR_AFTER}, {@link #CURSOR_BEFORE},
+ * {@link #CURSOR_AT_OR_BEFORE}, or {@link #CURSOR_AT}
+ * @return the offset of the next position, or -1
+ * @hide
+ */
+ public int getTextRunCursor(char[] text, int contextStart, int contextLength,
+ int flags, int offset, int cursorOpt) {
+ int contextEnd = contextStart + contextLength;
+ if (((contextStart | contextEnd | offset | (contextEnd - contextStart)
+ | (offset - contextStart) | (contextEnd - offset)
+ | (text.length - contextEnd) | cursorOpt) < 0)
+ || cursorOpt > CURSOR_OPT_MAX_VALUE) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ return native_getTextRunCursor(mNativePaint, text,
+ contextStart, contextLength, flags, offset, cursorOpt);
+ }
+
+ /**
+ * Returns the next cursor position in the run. This avoids placing the
+ * cursor between surrogates, between characters that form conjuncts,
+ * between base characters and combining marks, or within a reordering
+ * cluster.
+ *
+ * <p>ContextStart, contextEnd, and offset are relative to the start of
+ * text. The context is the shaping context for cursor movement, generally
+ * the bounds of the metric span enclosing the cursor in the direction of
+ * movement.
+ *
+ * <p>If cursorOpt is {@link #CURSOR_AT} and the offset is not a valid
+ * cursor position, this returns -1. Otherwise this will never return a
+ * value before contextStart or after contextEnd.
+ *
+ * @param text the text
+ * @param contextStart the start of the context
+ * @param contextEnd the end of the context
+ * @param flags either {@link #DIRECTION_RTL} or {@link #DIRECTION_LTR}
+ * @param offset the cursor position to move from
+ * @param cursorOpt how to move the cursor, one of {@link #CURSOR_AFTER},
+ * {@link #CURSOR_AT_OR_AFTER}, {@link #CURSOR_BEFORE},
+ * {@link #CURSOR_AT_OR_BEFORE}, or {@link #CURSOR_AT}
+ * @return the offset of the next position, or -1
+ * @hide
+ */
+ public int getTextRunCursor(CharSequence text, int contextStart,
+ int contextEnd, int flags, int offset, int cursorOpt) {
+
+ if (text instanceof String || text instanceof SpannedString ||
+ text instanceof SpannableString) {
+ return getTextRunCursor(text.toString(), contextStart, contextEnd,
+ flags, offset, cursorOpt);
+ }
+ if (text instanceof GraphicsOperations) {
+ return ((GraphicsOperations) text).getTextRunCursor(
+ contextStart, contextEnd, flags, offset, cursorOpt, this);
+ }
+
+ int contextLen = contextEnd - contextStart;
+ char[] buf = TemporaryBuffer.obtain(contextLen);
+ TextUtils.getChars(text, contextStart, contextEnd, buf, 0);
+ int result = getTextRunCursor(buf, 0, contextLen, flags, offset, cursorOpt);
+ TemporaryBuffer.recycle(buf);
+ return result;
+ }
+
+ /**
+ * Returns the next cursor position in the run. This avoids placing the
+ * cursor between surrogates, between characters that form conjuncts,
+ * between base characters and combining marks, or within a reordering
+ * cluster.
+ *
+ * <p>ContextStart, contextEnd, and offset are relative to the start of
+ * text. The context is the shaping context for cursor movement, generally
+ * the bounds of the metric span enclosing the cursor in the direction of
+ * movement.
+ *
+ * <p>If cursorOpt is {@link #CURSOR_AT} and the offset is not a valid
+ * cursor position, this returns -1. Otherwise this will never return a
+ * value before contextStart or after contextEnd.
+ *
+ * @param text the text
+ * @param contextStart the start of the context
+ * @param contextEnd the end of the context
+ * @param flags either {@link #DIRECTION_RTL} or {@link #DIRECTION_LTR}
+ * @param offset the cursor position to move from
+ * @param cursorOpt how to move the cursor, one of {@link #CURSOR_AFTER},
+ * {@link #CURSOR_AT_OR_AFTER}, {@link #CURSOR_BEFORE},
+ * {@link #CURSOR_AT_OR_BEFORE}, or {@link #CURSOR_AT}
+ * @return the offset of the next position, or -1
+ * @hide
+ */
+ public int getTextRunCursor(String text, int contextStart, int contextEnd,
+ int flags, int offset, int cursorOpt) {
+ if (((contextStart | contextEnd | offset | (contextEnd - contextStart)
+ | (offset - contextStart) | (contextEnd - offset)
+ | (text.length() - contextEnd) | cursorOpt) < 0)
+ || cursorOpt > CURSOR_OPT_MAX_VALUE) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ return native_getTextRunCursor(mNativePaint, text,
+ contextStart, contextEnd, flags, offset, cursorOpt);
+ }
+
+ /**
* Return the path (outline) for the specified text.
* Note: just like Canvas.drawText, this will respect the Align setting in
* the paint.
@@ -1489,6 +1819,19 @@ public class Paint {
char[] text, int index, int count, float[] widths);
private static native int native_getTextWidths(int native_object,
String text, int start, int end, float[] widths);
+
+ private static native float native_getTextRunAdvances(int native_object,
+ char[] text, int index, int count, int contextIndex, int contextCount,
+ int flags, float[] advances, int advancesIndex);
+ private static native float native_getTextRunAdvances(int native_object,
+ String text, int start, int end, int contextStart, int contextEnd,
+ int flags, float[] advances, int advancesIndex);
+
+ private native int native_getTextRunCursor(int native_object, char[] text,
+ int contextStart, int contextLength, int flags, int offset, int cursorOpt);
+ private native int native_getTextRunCursor(int native_object, String text,
+ int contextStart, int contextEnd, int flags, int offset, int cursorOpt);
+
private static native void native_getTextPath(int native_object,
char[] text, int index, int count, float x, float y, int path);
private static native void native_getTextPath(int native_object,
@@ -1499,4 +1842,3 @@ public class Paint {
char[] text, int index, int count, Rect bounds);
private static native void finalizer(int nativePaint);
}
-