From f7f969e67e9ab420404807b1b103dcd18d7aa7b7 Mon Sep 17 00:00:00 2001 From: Raph Levien Date: Wed, 1 Apr 2015 14:41:21 -0700 Subject: [PATCH] Add Paint.hasGlyph method This patch adds a method to determine whether a typeface has a glyph to support a particular Unicode character or sequence. The implementation is based on shaping the text and checking for the presence of .notdef glyphs, and counting the number of glyphs in the ligature case. In the case of variation selector control characters, it currently just returns false because there is no variation selector support yet (Mongolian Free Variation Selectors work because they're shaped with GSUB instead). Change-Id: I8ee4ae0d6e81a0ac57f961eb02dcc35aabd87042 --- api/current.txt | 1 + api/system-current.txt | 1 + core/jni/android/graphics/Paint.cpp | 66 +++++++++++++++++++++++ core/jni/android/graphics/Typeface.cpp | 2 +- graphics/java/android/graphics/Paint.java | 22 ++++++++ 5 files changed, 91 insertions(+), 1 deletion(-) diff --git a/api/current.txt b/api/current.txt index b3d5fd0c014a4..bdbb915f82208 100644 --- a/api/current.txt +++ b/api/current.txt @@ -11387,6 +11387,7 @@ package android.graphics { method public int getTextWidths(java.lang.String, float[]); method public android.graphics.Typeface getTypeface(); method public android.graphics.Xfermode getXfermode(); + method public boolean hasGlyph(java.lang.String); method public final boolean isAntiAlias(); method public final boolean isDither(); method public boolean isElegantTextHeight(); diff --git a/api/system-current.txt b/api/system-current.txt index 94620548bd699..090086aefed75 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -11673,6 +11673,7 @@ package android.graphics { method public int getTextWidths(java.lang.String, float[]); method public android.graphics.Typeface getTypeface(); method public android.graphics.Xfermode getXfermode(); + method public boolean hasGlyph(java.lang.String); method public final boolean isAntiAlias(); method public final boolean isDither(); method public boolean isElegantTextHeight(); diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp index 4b43de3e6d3f4..873b516117ea0 100644 --- a/core/jni/android/graphics/Paint.cpp +++ b/core/jni/android/graphics/Paint.cpp @@ -23,6 +23,7 @@ #include "GraphicsJNI.h" #include "core_jni_helpers.h" #include +#include #include "SkBlurDrawLooper.h" #include "SkColorFilter.h" @@ -41,6 +42,8 @@ #include "Paint.h" #include "TypefaceImpl.h" +#include + // temporary for debugging #include #include @@ -972,6 +975,68 @@ public: JNI_ABORT); } + static jboolean layoutContainsNotdef(const Layout& layout) { + for (size_t i = 0; i < layout.nGlyphs(); i++) { + if (layout.getGlyphId(i) == 0) { + return true; + } + } + return false; + } + + static jboolean hasGlyphVariation(const Paint* paint, TypefaceImpl* typeface, jint bidiFlags, + const jchar* chars, size_t size) { + // TODO: query font for whether character has variation selector; requires a corresponding + // function in Minikin. + return false; + } + + static jboolean hasGlyph(JNIEnv *env, jclass, jlong paintHandle, jlong typefaceHandle, + jint bidiFlags, jstring string) { + const Paint* paint = reinterpret_cast(paintHandle); + TypefaceImpl* typeface = reinterpret_cast(typefaceHandle); + ScopedStringChars str(env, string); + + /* start by rejecting variation selectors (not supported yet) */ + size_t nChars = 0; + for (size_t i = 0; i < str.size(); i++) { + jchar c = str[i]; + if (0xDC00 <= c && c <= 0xDFFF) { + // invalid UTF-16, unpaired trailing surrogate + return false; + } else if (0xD800 <= c && c <= 0xDBFF) { + if (i + 1 == str.size()) { + // invalid UTF-16, unpaired leading surrogate at end of string + return false; + } + i++; + jchar c2 = str[i]; + if (!(0xDC00 <= c2 && c2 <= 0xDFFF)) { + // invalid UTF-16, unpaired leading surrogate + return false; + } + // UTF-16 encoding of range U+E0100..U+E01EF is DB40 DD00 .. DB40 DDEF + if (c == 0xDB40 && 0xDD00 <= c2 && c2 <= 0xDDEF) { + return hasGlyphVariation(paint, typeface, bidiFlags, str.get(), str.size()); + } + } else if (0xFE00 <= c && c <= 0xFE0F) { + return hasGlyphVariation(paint, typeface, bidiFlags, str.get(), str.size()); + } + nChars++; + } + Layout layout; + MinikinUtils::doLayout(&layout, paint, bidiFlags, typeface, str.get(), 0, str.size(), + str.size()); + size_t nGlyphs = layout.nGlyphs(); + if (nGlyphs != 1 && nChars > 1) { + // multiple-character input, and was not a ligature + // TODO: handle ZWJ/ZWNJ characters specially so we can detect certain ligatures + // in joining scripts, such as Arabic and Mongolian. + return false; + } + return nGlyphs > 0 && !layoutContainsNotdef(layout); + } + }; static JNINativeMethod methods[] = { @@ -1057,6 +1122,7 @@ static JNINativeMethod methods[] = { (void*) PaintGlue::getStringBounds }, {"nativeGetCharArrayBounds", "(JJ[CIIILandroid/graphics/Rect;)V", (void*) PaintGlue::getCharArrayBounds }, + {"native_hasGlyph", "(JJILjava/lang/String;)Z", (void*) PaintGlue::hasGlyph }, {"native_setShadowLayer", "!(JFFFI)V", (void*)PaintGlue::setShadowLayer}, {"native_hasShadowLayer", "!(J)Z", (void*)PaintGlue::hasShadowLayer} diff --git a/core/jni/android/graphics/Typeface.cpp b/core/jni/android/graphics/Typeface.cpp index b092e44c73c43..e0cbc9efbd186 100644 --- a/core/jni/android/graphics/Typeface.cpp +++ b/core/jni/android/graphics/Typeface.cpp @@ -18,7 +18,7 @@ #include "core_jni_helpers.h" #include "GraphicsJNI.h" -#include +#include "ScopedPrimitiveArray.h" #include "SkTypeface.h" #include "TypefaceImpl.h" #include diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java index 1da198cfc5692..d1ff8632086ee 100644 --- a/graphics/java/android/graphics/Paint.java +++ b/graphics/java/android/graphics/Paint.java @@ -2249,6 +2249,26 @@ public class Paint { bounds); } + /** + * Determine whether the typeface set on the paint has a glyph supporting the string. The + * simplest case is when the string contains a single character, in which this method + * determines whether the font has the character. In the case of multiple characters, the + * method returns true if there is a single glyph representing the ligature. For example, if + * the input is a pair of regional indicator symbols, determine whether there is an emoji flag + * for the pair. + * + * Finally, if the string contains a variation selector, the method only returns true if + * the fonts contains a glyph specific to that variation. + * + * Checking is done on the entire fallback chain, not just the immediate font referenced. + * + * @param string the string to test whether there is glyph support + * @return true if the typeface has a glyph for the string + */ + public boolean hasGlyph(String string) { + return native_hasGlyph(mNativePaint, mNativeTypeface, mBidiFlags, string); + } + @Override protected void finalize() throws Throwable { try { @@ -2334,4 +2354,6 @@ public class Paint { String settings); private static native int native_getHyphenEdit(long native_object); 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); }