Move shaping to native.

Add internal API (getTextRunAdvances) to Paint, use when measuring.
Add internal API (getTextRunCursor) to Paint, use when determining
valid cursor positions.

Remove java-level shaping code.  Remove 'prep' code in TextLine
(except for replacement text) since shaping now is done on the fly as
needed in native.

Provide explicit shaping context bounds to internal text measuring,
cursor movement, and rendering APIs.

Update for to changes in external API in ushape.h.

Change-Id: I146958b624802ce8553125e5c3c6c03031bc9608
diff --git a/core/java/android/text/GraphicsOperations.java b/core/java/android/text/GraphicsOperations.java
index 05697c6..d426d124 100644
--- a/core/java/android/text/GraphicsOperations.java
+++ b/core/java/android/text/GraphicsOperations.java
@@ -37,17 +37,30 @@
      * 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 2f7720a..f533944 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -211,7 +211,7 @@
         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 @@
                 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 @@
                 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 @@
                             break;
                         }
                     }
-                    
+
                     tabStopsIsInitialized = false;
                 }
 
@@ -399,7 +399,7 @@
     /**
      * 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 @@
     }
 
     /**
-     * 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 @@
             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 @@
                 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 @@
                     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 @@
         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 @@
             }
             this.mNumStops = ns;
         }
-        
+
         float nextTab(float h) {
             int ns = this.mNumStops;
             if (ns > 0) {
@@ -1493,7 +1493,7 @@
             return ((int) ((h + inc) / inc)) * inc;
         }
     }
-    
+
     /**
      * Returns the position of the next tab stop after h on the line.
      *
@@ -1728,4 +1728,3 @@
     /* 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 e3a113d..d5699f1 100644
--- a/core/java/android/text/MeasuredText.java
+++ b/core/java/android/text/MeasuredText.java
@@ -18,8 +18,8 @@
 
 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 @@
     /* 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 @@
     }
 
     /**
-     * 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 @@
 
         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 @@
         }
 
         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 @@
             }
             mDir = AndroidBidi.bidi(bidiRequest, mChars, mLevels, len, false);
             mEasy = false;
-
-            // shape
-            if (mLen > 0) {
-                byte[] levels = mLevels;
-                char[] chars = mChars;
-                byte level = levels[0];
-                int pi = 0;
-                for (int i = 1, e = mLen;; ++i) {
-                    if (i == e || levels[i] != level) {
-                        if ((level & 0x1) != 0) {
-                            AndroidCharacter.mirror(chars, pi, i - pi);
-                            ArabicShaping.SHAPER.shape(chars, pi, i - pi);
-                        }
-                        if (i == e) {
-                            break;
-                        }
-                        pi = i;
-                        level = levels[i];
-                    }
-                }
-            }
         }
     }
 
     float addStyleRun(TextPaint paint, int len, Paint.FontMetricsInt fm) {
-        int p = mPos;
-        float[] w = mWidths, ww = mWorkWidths;
-        int count = paint.getTextWidths(mChars, p, len, ww);
-        int width = 0;
-        if (count < len) {
-            // must have surrogate pairs in here, pad out the array with zero
-            // for the trailing surrogates
-            char[] chars = mChars;
-            for (int i = 0, e = mLen; i < count; ++i) {
-                width += (w[p++] = ww[i]);
-                if (p < e && chars[p] >= '\udc00' && chars[p] < '\ue000' &&
-                        chars[p-1] >= '\ud800' && chars[p-1] < '\udc00') {
-                    w[p++] = 0;
-                }
-            }
-        } else {
-            for (int i = 0; i < len; ++i) {
-                width += (w[p++] = ww[i]);
-            }
-        }
-        mPos = p;
         if (fm != null) {
             paint.getFontMetricsInt(fm);
         }
-        return width;
+
+        int p = mPos;
+        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;
+                }
+                q = i;
+                level = mLevels[i];
+            }
+        }
+        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 7563179..56f1302 100644
--- a/core/java/android/text/SpannableStringBuilder.java
+++ b/core/java/android/text/SpannableStringBuilder.java
@@ -1055,35 +1055,27 @@
         }
     }
 
+
     /**
      * 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 @@
         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 bd410c8..e0ccbb4 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -23,7 +23,6 @@
 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 @@
     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 @@
         mDirections = directions;
         mHasTabs = hasTabs;
         mSpanned = null;
-        mPreppedIndex = 0;
-        mPreppedLimit = 0;
 
         boolean hasReplacement = false;
         if (text instanceof Spanned) {
@@ -147,6 +141,25 @@
                 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 @@
 
         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 @@
         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 @@
     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 @@
      * 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 @@
               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 @@
 
             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 @@
               // 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 @@
     /**
      * 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 @@
      *
      * @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 @@
      * @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 @@
                 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 @@
      * @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;
+        int textStart = mStart + start;
+        int textLimit = mStart + limit;
+
+        if (needWidth || (c != null && runIsRtl)) {
+            ret = replacement.getSize(wp, mText, textStart, textLimit, fmi);
         }
 
-        if (prepFlags != PREP_ONLY) {
-            int textStart = mStart + start;
-            int textLimit = mStart + limit;
-
-            if (needWidth || (c != null && runIsRtl)) {
-                ret = replacement.getSize(wp, mText, textStart, textLimit, fmi);
+        if (c != null) {
+            if (runIsRtl) {
+                x -= ret;
             }
-
-            if (c != null) {
-                if (runIsRtl) {
-                    x -= ret;
-                }
-                replacement.draw(c, mText, textStart, textLimit,
-                        x, top, y, bottom, wp);
-            }
+            replacement.draw(c, mText, textStart, textLimit,
+                    x, top, y, bottom, wp);
         }
 
         return runIsRtl ? -ret : ret;
@@ -757,10 +778,9 @@
      * 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 @@
      * @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 @@
                         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);
 
-                        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);
-                        }
-
-                        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 @@
         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 a24668a..b90ac9d 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -2782,8 +2782,11 @@
         }
 
         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 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 4c7e762..b044271 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 @@
                              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 @@
         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 @@
                     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 @@
                             }
                         }
                         ubidi_close(bidi);
-                    } 
+                    }
                 } else {
-                    shapeRtlText__(text, len, 0, len, shaped);
+                  len = shapeRtlText__(text, 0, len, len, shaped, status);
                 }
             }
         }
@@ -886,7 +914,7 @@
             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 @@
         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 @@
         }
         delete[] posPtr;
     }
- 
+
     static void drawPosText__String_FPaint(JNIEnv* env, jobject,
                                            SkCanvas* canvas, jstring text,
                                            jfloatArray pos, SkPaint* paint) {
@@ -1008,7 +1024,7 @@
         }
         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 @@
                     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 @@
                     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 @@
         (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 @@
         (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 780badc..ca9c9de 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 @@
 
 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 @@
         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 @@
     {"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 e2634d1..e556350 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -1354,18 +1354,22 @@
      * 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 @@
         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 @@
      * 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 @@
      * @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 @@
 
         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 @@
                                                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 b564929..862a2ec 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -125,13 +125,65 @@
      * @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 @@
         }
 
         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 @@
     }
 
     /**
+     * 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 @@
                             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 @@
                                 char[] text, int index, int count, Rect bounds);
     private static native void finalizer(int nativePaint);
 }
-