diff options
| author | 2010-06-10 14:25:39 -0700 | |
|---|---|---|
| committer | 2010-06-10 14:25:39 -0700 | |
| commit | 92a4d078e54aa9da1d48941496bb7eb835a79ce3 (patch) | |
| tree | cc8e6e9d0218480ad51cb5cf72d09805b7b3a130 | |
| parent | aefed3626efeccf87692aba5b7a1aafe07a25308 (diff) | |
| parent | 0c702b88c5d0d4380930b920f5be6e66dd95a0d8 (diff) | |
Merge "Move shaping to native."
| -rw-r--r-- | core/java/android/text/GraphicsOperations.java | 19 | ||||
| -rw-r--r-- | core/java/android/text/Layout.java | 39 | ||||
| -rw-r--r-- | core/java/android/text/MeasuredText.java | 75 | ||||
| -rw-r--r-- | core/java/android/text/SpannableStringBuilder.java | 86 | ||||
| -rw-r--r-- | core/java/android/text/TextLine.java | 354 | ||||
| -rw-r--r-- | core/java/android/widget/TextView.java | 24 | ||||
| -rw-r--r-- | core/jni/android/graphics/Canvas.cpp | 139 | ||||
| -rw-r--r-- | core/jni/android/graphics/Paint.cpp | 193 | ||||
| -rw-r--r-- | graphics/java/android/graphics/Canvas.java | 58 | ||||
| -rw-r--r-- | graphics/java/android/graphics/Paint.java | 354 |
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); } - |