Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a027ec5c authored by Raph Levien's avatar Raph Levien
Browse files

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
parent 954850ce
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -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();
+4 −0
Original line number Diff line number Diff line
@@ -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();
+63 −13
Original line number Diff line number Diff line
@@ -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,21 +1136,25 @@ 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},
    {"native_getTextRunAdvances","(JJLjava/lang/String;IIIIZ[FI)F",
@@ -1123,6 +1170,9 @@ static JNINativeMethod methods[] = {
    {"nativeGetCharArrayBounds", "(JJ[CIIILandroid/graphics/Rect;)V",
            (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}
+168 −0
Original line number Diff line number Diff line
@@ -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);
}