diff --git a/api/current.txt b/api/current.txt index 1e57d19edc557..380fcb4b49278 100644 --- a/api/current.txt +++ b/api/current.txt @@ -11388,6 +11388,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 544cbb3069bb8..40accaaa50d83 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -11677,6 +11677,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 d3cb9b151915e..cd5f59d534be2 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); }