summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Raph Levien <raph@google.com> 2015-04-06 16:21:59 -0700
committer Raph Levien <raph@google.com> 2015-04-14 23:51:46 -0700
commita027ec5c2dbfbbf10cac9ea538f5e230b093be2f (patch)
treee9b6a31e92091d55a10cf17dd1b5828b111feb74
parent954850ce5db39324a9f6a4a1ac62a182d88dea83 (diff)
Add Paint methods for cursor positioning
Adds methods to Paint for finding an offset corresponding to an advance, and for finding the advance corresponding to an offset, useful for positioning and drawing a cursor. Change-Id: Id57402ddd1980650f1d0d2f8bbdb75e43612ec51
-rw-r--r--api/current.txt4
-rw-r--r--api/system-current.txt4
-rw-r--r--core/jni/android/graphics/Paint.cpp76
-rw-r--r--graphics/java/android/graphics/Paint.java168
4 files changed, 239 insertions, 13 deletions
diff --git a/api/current.txt b/api/current.txt
index d79ccbe48107..51c76dbe3f34 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -11366,8 +11366,12 @@ package android.graphics {
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 e57eee5ca95a..65dbb3d3718e 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -11656,8 +11656,12 @@ package android.graphics {
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 873b516117ea..ed418696732b 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"
@@ -1037,6 +1038,48 @@ public:
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[] = {
@@ -1093,36 +1136,43 @@ static JNINativeMethod methods[] = {
{"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 cd5f59d534be..649d996ceb2a 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -2269,6 +2269,168 @@ public class Paint {
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 @@ public class Paint {
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);
}