Merge "Add Paint methods for cursor positioning"
diff --git a/api/current.txt b/api/current.txt
index bd9fd3e..9a4e905 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -11524,8 +11524,12 @@
     method public int getHinting();
     method public float getLetterSpacing();
     method public android.graphics.MaskFilter getMaskFilter();
+    method public int getOffsetForAdvance(char[], int, int, int, int, boolean, float);
+    method public int getOffsetForAdvance(java.lang.CharSequence, int, int, int, int, boolean, float);
     method public android.graphics.PathEffect getPathEffect();
     method public deprecated android.graphics.Rasterizer getRasterizer();
+    method public float getRunAdvance(char[], int, int, int, int, boolean, int);
+    method public float getRunAdvance(java.lang.CharSequence, int, int, int, int, boolean, int);
     method public android.graphics.Shader getShader();
     method public android.graphics.Paint.Cap getStrokeCap();
     method public android.graphics.Paint.Join getStrokeJoin();
diff --git a/api/system-current.txt b/api/system-current.txt
index 836f3a0..8cdf938 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -11818,8 +11818,12 @@
     method public int getHinting();
     method public float getLetterSpacing();
     method public android.graphics.MaskFilter getMaskFilter();
+    method public int getOffsetForAdvance(char[], int, int, int, int, boolean, float);
+    method public int getOffsetForAdvance(java.lang.CharSequence, int, int, int, int, boolean, float);
     method public android.graphics.PathEffect getPathEffect();
     method public deprecated android.graphics.Rasterizer getRasterizer();
+    method public float getRunAdvance(char[], int, int, int, int, boolean, int);
+    method public float getRunAdvance(java.lang.CharSequence, int, int, int, int, boolean, int);
     method public android.graphics.Shader getShader();
     method public android.graphics.Paint.Cap getStrokeCap();
     method public android.graphics.Paint.Join getStrokeJoin();
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index 4906f59..63395ed 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -22,8 +22,8 @@
 #include "jni.h"
 #include "GraphicsJNI.h"
 #include "core_jni_helpers.h"
-#include <ScopedUtfChars.h>
 #include <ScopedStringChars.h>
+#include <ScopedUtfChars.h>
 
 #include "SkBlurDrawLooper.h"
 #include "SkColorFilter.h"
@@ -37,6 +37,7 @@
 #include "utils/Blur.h"
 
 #include <minikin/GraphemeBreak.h>
+#include <minikin/Measurement.h>
 #include "MinikinSkia.h"
 #include "MinikinUtils.h"
 #include "Paint.h"
@@ -1038,6 +1039,48 @@
         return nGlyphs > 0 && !layoutContainsNotdef(layout);
     }
 
+    static jfloat doRunAdvance(const Paint* paint, TypefaceImpl* typeface, const jchar buf[],
+            jint start, jint count, jint bufSize, jboolean isRtl, jint offset) {
+        Layout layout;
+        int bidiFlags = isRtl ? kBidi_Force_RTL : kBidi_Force_LTR;
+        MinikinUtils::doLayout(&layout, paint, bidiFlags, typeface, buf, start, count, bufSize);
+        return getRunAdvance(layout, buf, start, count, offset);
+    }
+
+    static jfloat getRunAdvance___CIIIIZI_F(JNIEnv *env, jclass, jlong paintHandle,
+            jlong typefaceHandle, jcharArray text, jint start, jint end, jint contextStart,
+            jint contextEnd, jboolean isRtl, jint offset) {
+        const Paint* paint = reinterpret_cast<Paint*>(paintHandle);
+        TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
+        // TODO performance: optimize JNI array access
+        jchar* textArray = env->GetCharArrayElements(text, NULL);
+        jfloat result = doRunAdvance(paint, typeface, textArray + contextStart,
+                start - contextStart, end - start, contextEnd - contextStart, isRtl, offset);
+        env->ReleaseCharArrayElements(text, textArray, JNI_ABORT);
+        return result;
+    }
+
+    static jint doOffsetForAdvance(const Paint* paint, TypefaceImpl* typeface, const jchar buf[],
+            jint start, jint count, jint bufSize, jboolean isRtl, jfloat advance) {
+        Layout layout;
+        int bidiFlags = isRtl ? kBidi_Force_RTL : kBidi_Force_LTR;
+        MinikinUtils::doLayout(&layout, paint, bidiFlags, typeface, buf, start, count, bufSize);
+        return getOffsetForAdvance(layout, buf, start, count, advance);
+    }
+    static jint getOffsetForAdvance___CIIIIZF_I(JNIEnv *env, jclass, jlong paintHandle,
+            jlong typefaceHandle, jcharArray text, jint start, jint end, jint contextStart,
+            jint contextEnd, jboolean isRtl, jfloat advance) {
+        const Paint* paint = reinterpret_cast<Paint*>(paintHandle);
+        TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
+        // TODO performance: optimize JNI array access
+        jchar* textArray = env->GetCharArrayElements(text, NULL);
+        jint result = doOffsetForAdvance(paint, typeface, textArray + contextStart,
+                start - contextStart, end - start, contextEnd - contextStart, isRtl, advance);
+        result += contextStart;
+        env->ReleaseCharArrayElements(text, textArray, JNI_ABORT);
+        return result;
+    }
+
 };
 
 static JNINativeMethod methods[] = {
@@ -1094,36 +1137,43 @@
     {"setTextSkewX","!(F)V", (void*) PaintGlue::setTextSkewX},
     {"native_getLetterSpacing","!(J)F", (void*) PaintGlue::getLetterSpacing},
     {"native_setLetterSpacing","!(JF)V", (void*) PaintGlue::setLetterSpacing},
-    {"native_setFontFeatureSettings","(JLjava/lang/String;)V", (void*) PaintGlue::setFontFeatureSettings},
+    {"native_setFontFeatureSettings","(JLjava/lang/String;)V",
+            (void*) PaintGlue::setFontFeatureSettings},
     {"native_getHyphenEdit", "!(J)I", (void*) PaintGlue::getHyphenEdit},
     {"native_setHyphenEdit", "!(JI)V", (void*) PaintGlue::setHyphenEdit},
     {"ascent","!()F", (void*) PaintGlue::ascent},
     {"descent","!()F", (void*) PaintGlue::descent},
 
-    {"getFontMetrics", "(Landroid/graphics/Paint$FontMetrics;)F", (void*)PaintGlue::getFontMetrics},
-    {"getFontMetricsInt", "(Landroid/graphics/Paint$FontMetricsInt;)I", (void*)PaintGlue::getFontMetricsInt},
+    {"getFontMetrics", "(Landroid/graphics/Paint$FontMetrics;)F",
+            (void*)PaintGlue::getFontMetrics},
+    {"getFontMetricsInt", "(Landroid/graphics/Paint$FontMetricsInt;)I",
+            (void*)PaintGlue::getFontMetricsInt},
     {"native_measureText","([CIII)F", (void*) PaintGlue::measureText_CIII},
     {"native_measureText","(Ljava/lang/String;I)F", (void*) PaintGlue::measureText_StringI},
     {"native_measureText","(Ljava/lang/String;III)F", (void*) PaintGlue::measureText_StringIII},
     {"native_breakText","(JJ[CIIFI[F)I", (void*) PaintGlue::breakTextC},
     {"native_breakText","(JJLjava/lang/String;ZFI[F)I", (void*) PaintGlue::breakTextS},
     {"native_getTextWidths","(JJ[CIII[F)I", (void*) PaintGlue::getTextWidths___CIII_F},
-    {"native_getTextWidths","(JJLjava/lang/String;III[F)I", (void*) PaintGlue::getTextWidths__StringIII_F},
+    {"native_getTextWidths","(JJLjava/lang/String;III[F)I",
+            (void*) PaintGlue::getTextWidths__StringIII_F},
     {"native_getTextRunAdvances","(JJ[CIIIIZ[FI)F",
-        (void*) PaintGlue::getTextRunAdvances___CIIIIZ_FI},
+            (void*) PaintGlue::getTextRunAdvances___CIIIIZ_FI},
     {"native_getTextRunAdvances","(JJLjava/lang/String;IIIIZ[FI)F",
-        (void*) PaintGlue::getTextRunAdvances__StringIIIIZ_FI},
+            (void*) PaintGlue::getTextRunAdvances__StringIIIIZ_FI},
 
     {"native_getTextRunCursor", "(J[CIIIII)I", (void*) PaintGlue::getTextRunCursor___C},
     {"native_getTextRunCursor", "(JLjava/lang/String;IIIII)I",
-        (void*) PaintGlue::getTextRunCursor__String},
-    {"native_getTextPath","(JJI[CIIFFJ)V", (void*) PaintGlue::getTextPath___C},
-    {"native_getTextPath","(JJILjava/lang/String;IIFFJ)V", (void*) PaintGlue::getTextPath__String},
+            (void*) PaintGlue::getTextRunCursor__String},
+    {"native_getTextPath", "(JJI[CIIFFJ)V", (void*) PaintGlue::getTextPath___C},
+    {"native_getTextPath", "(JJILjava/lang/String;IIFFJ)V", (void*) PaintGlue::getTextPath__String},
     {"nativeGetStringBounds", "(JJLjava/lang/String;IIILandroid/graphics/Rect;)V",
-                                        (void*) PaintGlue::getStringBounds },
+            (void*) PaintGlue::getStringBounds },
     {"nativeGetCharArrayBounds", "(JJ[CIIILandroid/graphics/Rect;)V",
-                                    (void*) PaintGlue::getCharArrayBounds },
-    {"native_hasGlyph",           "(JJILjava/lang/String;)Z", (void*) PaintGlue::hasGlyph },
+            (void*) PaintGlue::getCharArrayBounds },
+    {"native_hasGlyph", "(JJILjava/lang/String;)Z", (void*) PaintGlue::hasGlyph },
+    {"native_getRunAdvance", "(JJ[CIIIIZI)F", (void*) PaintGlue::getRunAdvance___CIIIIZI_F},
+    {"native_getOffsetForAdvance", "(JJ[CIIIIZF)I",
+            (void*) PaintGlue::getOffsetForAdvance___CIIIIZF_I},
 
     {"native_setShadowLayer", "!(JFFFI)V", (void*)PaintGlue::setShadowLayer},
     {"native_hasShadowLayer", "!(J)Z", (void*)PaintGlue::hasShadowLayer}
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index cd5f59d..649d996 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -2269,6 +2269,168 @@
         return native_hasGlyph(mNativePaint, mNativeTypeface, mBidiFlags, string);
     }
 
+    /**
+     * Measure cursor position within a run of text.
+     *
+     * <p>The run of text includes the characters from {@code start} to {@code end} in the text. In
+     * addition, the range {@code contextStart} to {@code contextEnd} is used as context for the
+     * purpose of complex text shaping, such as Arabic text potentially shaped differently based on
+     * the text next to it.
+     *
+     * All text outside the range {@code contextStart..contextEnd} is ignored. The text between
+     * {@code start} and {@code end} will be laid out to be measured.
+     *
+     * The returned width measurement is the advance from {@code start} to {@code offset}. It is
+     * generally a positive value, no matter the direction of the run. If {@code offset == end},
+     * the return value is simply the width of the whole run from {@code start} to {@code end}.
+     *
+     * Ligatures are formed for characters in the range {@code start..end} (but not for
+     * {@code start..contextStart} or {@code end..contextEnd}). If {@code offset} points to a
+     * character in the middle of such a formed ligature, but at a grapheme cluster boundary, the
+     * return value will also reflect an advance in the middle of the ligature. See
+     * {@link #getOffsetForAdvance} for more discussion of grapheme cluster boundaries.
+     *
+     * The direction of the run is explicitly specified by {@code isRtl}. Thus, this method is
+     * suitable only for runs of a single direction.
+     *
+     * <p>All indices are relative to the start of {@code text}. Further, {@code 0 <= contextStart
+     * <= start <= offset <= end <= contextEnd <= text.length} must hold on entry.
+     *
+     * @param text the text to measure. Cannot be null.
+     * @param start the index of the start of the range to measure
+     * @param end the index + 1 of the end of the range to measure
+     * @param contextStart the index of the start of the shaping context
+     * @param contextEnd the index + 1 of the end of the range to measure
+     * @param isRtl whether the run is in RTL direction
+     * @param offset index of caret position
+     * @return width measurement between start and offset
+     */
+    public float getRunAdvance(char[] text, int start, int end, int contextStart, int contextEnd,
+            boolean isRtl, int offset) {
+        if (text == null) {
+            throw new IllegalArgumentException("text cannot be null");
+        }
+        if ((contextStart | start | offset | end | contextEnd
+                | start - contextStart | offset - start | end - offset
+                | contextEnd - end | text.length - contextEnd) < 0) {
+            throw new IndexOutOfBoundsException();
+        }
+        if (end == start) {
+            return 0.0f;
+        }
+        // TODO: take mCompatScaling into account (or eliminate compat scaling)?
+        return native_getRunAdvance(mNativePaint, mNativeTypeface, text, start, end,
+                contextStart, contextEnd, isRtl, offset);
+    }
+
+    /**
+     * @see #getRunAdvance(char[], int, int, int, int, boolean, int)
+     *
+     * @param text the text to measure. Cannot be null.
+     * @param start the index of the start of the range to measure
+     * @param end the index + 1 of the end of the range to measure
+     * @param contextStart the index of the start of the shaping context
+     * @param contextEnd the index + 1 of the end of the range to measure
+     * @param isRtl whether the run is in RTL direction
+     * @param offset index of caret position
+     * @return width measurement between start and offset
+     */
+    public float getRunAdvance(CharSequence text, int start, int end, int contextStart,
+            int contextEnd, boolean isRtl, int offset) {
+        if (text == null) {
+            throw new IllegalArgumentException("text cannot be null");
+        }
+        if ((contextStart | start | offset | end | contextEnd
+                | start - contextStart | offset - start | end - offset
+                | contextEnd - end | text.length() - contextEnd) < 0) {
+            throw new IndexOutOfBoundsException();
+        }
+        if (end == start) {
+            return 0.0f;
+        }
+        // TODO performance: specialized alternatives to avoid buffer copy, if win is significant
+        char[] buf = TemporaryBuffer.obtain(contextEnd - contextStart);
+        TextUtils.getChars(text, contextStart, contextEnd, buf, 0);
+        float result = getRunAdvance(buf, start - contextStart, end - contextStart, 0,
+                contextEnd - contextStart, isRtl, offset - contextStart);
+        TemporaryBuffer.recycle(buf);
+        return result;
+    }
+
+    /**
+     * Get the character offset within the string whose position is closest to the specified
+     * horizontal position.
+     *
+     * <p>The returned value is generally the value of {@code offset} for which
+     * {@link #getRunAdvance} yields a result most closely approximating {@code advance},
+     * and which is also on a grapheme cluster boundary. As such, it is the preferred method
+     * for positioning a cursor in response to a touch or pointer event. The grapheme cluster
+     * boundaries are based on
+     * <a href="http://unicode.org/reports/tr29/">Unicode Standard Annex #29</a> but with some
+     * tailoring for better user experience.
+     *
+     * <p>Note that {@code advance} is a (generally positive) width measurement relative to the start
+     * of the run. Thus, for RTL runs it the distance from the point to the right edge.
+     *
+     * <p>All indices are relative to the start of {@code text}. Further, {@code 0 <= contextStart
+     * <= start <= end <= contextEnd <= text.length} must hold on entry, and {@code start <= result
+     * <= end} will hold on return.
+     *
+     * @param text the text to measure. Cannot be null.
+     * @param start the index of the start of the range to measure
+     * @param end the index + 1 of the end of the range to measure
+     * @param contextStart the index of the start of the shaping context
+     * @param contextEnd the index + 1 of the end of the range to measure
+     * @param isRtl whether the run is in RTL direction
+     * @param advance width relative to start of run
+     * @return index of offset
+     */
+    public int getOffsetForAdvance(char[] text, int start, int end, int contextStart,
+            int contextEnd, boolean isRtl, float advance) {
+        if (text == null) {
+            throw new IllegalArgumentException("text cannot be null");
+        }
+        if ((contextStart | start | end | contextEnd
+                | start - contextStart | end - start | contextEnd - end
+                | text.length - contextEnd) < 0) {
+            throw new IndexOutOfBoundsException();
+        }
+        // TODO: take mCompatScaling into account (or eliminate compat scaling)?
+        return native_getOffsetForAdvance(mNativePaint, mNativeTypeface, text, start, end,
+                contextStart, contextEnd, isRtl, advance);
+    }
+
+    /**
+     * @see #getOffsetForAdvance(char[], int, int, int, int, boolean, float)
+     *
+     * @param text the text to measure. Cannot be null.
+     * @param start the index of the start of the range to measure
+     * @param end the index + 1 of the end of the range to measure
+     * @param contextStart the index of the start of the shaping context
+     * @param contextEnd the index + 1 of the end of the range to measure
+     * @param isRtl whether the run is in RTL direction
+     * @param advance width relative to start of run
+     * @return index of offset
+     */
+    public int getOffsetForAdvance(CharSequence text, int start, int end, int contextStart,
+            int contextEnd, boolean isRtl, float advance) {
+        if (text == null) {
+            throw new IllegalArgumentException("text cannot be null");
+        }
+        if ((contextStart | start | end | contextEnd
+                | start - contextStart | end - start | contextEnd - end
+                | text.length() - contextEnd) < 0) {
+            throw new IndexOutOfBoundsException();
+        }
+        // TODO performance: specialized alternatives to avoid buffer copy, if win is significant
+        char[] buf = TemporaryBuffer.obtain(contextEnd - contextStart);
+        TextUtils.getChars(text, contextStart, contextEnd, buf, 0);
+        int result = getOffsetForAdvance(buf, start - contextStart, end - contextStart, 0,
+                contextEnd - contextStart, isRtl, advance) + contextStart;
+        TemporaryBuffer.recycle(buf);
+        return result;
+    }
+
     @Override
     protected void finalize() throws Throwable {
         try {
@@ -2356,4 +2518,10 @@
     private static native void native_setHyphenEdit(long native_object, int hyphen);
     private static native boolean native_hasGlyph(long native_object, long native_typeface,
             int bidiFlags, String string);
+    private static native float native_getRunAdvance(long native_object, long native_typeface,
+            char[] text, int start, int end, int contextStart, int contextEnd, boolean isRtl,
+            int offset);
+    private static native int native_getOffsetForAdvance(long native_object,
+            long native_typeface, char[] text, int start, int end, int contextStart, int contextEnd,
+            boolean isRtl, float advance);
 }