From 20e5d91739fb88a02afb4888bf9f938308bc9b7b Mon Sep 17 00:00:00 2001 From: Seigo Nonaka Date: Wed, 18 Jan 2017 20:31:27 +0900 Subject: [PATCH] Expose Typeface creation APIs with ttc and font variation. Introduce Builder class for creating Typeface from various sources with optional TTC index and font variation settings. Bug: 33062398 Test: Manually verified new Builder create Typeface. Change-Id: Ia23ee6a73516707d854c7387fe75fbb22f80673d --- core/java/android/text/FontConfig.java | 14 +- core/jni/android/graphics/FontFamily.cpp | 135 +++--- .../src/android/graphics/VariationParserTest.java | 16 +- graphics/java/android/graphics/FontFamily.java | 42 +- graphics/java/android/graphics/FontListParser.java | 21 +- graphics/java/android/graphics/Typeface.java | 458 ++++++++++++++++++--- 6 files changed, 531 insertions(+), 155 deletions(-) diff --git a/core/java/android/text/FontConfig.java b/core/java/android/text/FontConfig.java index 04596fa5aa3d..9c15e00dc682 100644 --- a/core/java/android/text/FontConfig.java +++ b/core/java/android/text/FontConfig.java @@ -21,6 +21,7 @@ import static java.lang.annotation.RetentionPolicy.SOURCE; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.graphics.FontListParser; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.Parcelable; @@ -106,8 +107,17 @@ public final class FontConfig implements Parcelable { private final float mStyleValue; public Axis(int tag, float styleValue) { - this.mTag = tag; - this.mStyleValue = styleValue; + mTag = tag; + mStyleValue = styleValue; + } + + /** @hide */ + public Axis(@NonNull String tagString, float styleValue) { + if (!FontListParser.isValidTag(tagString)) { + throw new IllegalArgumentException("Invalid tag pattern: " + tagString); + } + mTag = FontListParser.makeTag(tagString); + mStyleValue = styleValue; } /** diff --git a/core/jni/android/graphics/FontFamily.cpp b/core/jni/android/graphics/FontFamily.cpp index fb7c5c46843a..4e68602b7712 100644 --- a/core/jni/android/graphics/FontFamily.cpp +++ b/core/jni/android/graphics/FontFamily.cpp @@ -34,12 +34,16 @@ #include #include +#include #include #include namespace android { +// Must be same with Java constant in Typeface.Builder. See Typeface.java +constexpr jint RESOLVE_BY_FONT_TABLE = -1; + struct NativeFamilyBuilder { uint32_t langId; int variant; @@ -81,24 +85,53 @@ static void FontFamily_unref(jlong familyPtr) { delete family; } -static void addSkTypeface(jlong builderPtr, sk_sp face, const void* fontData, - size_t fontSize, int ttcIndex, jint givenWeight, jboolean givenItalic) { +static bool addSkTypeface(NativeFamilyBuilder* builder, sk_sp&& data, int ttcIndex, + jint givenWeight, jint givenItalic) { + uirenderer::FatVector skiaAxes; + for (const auto& axis : builder->axes) { + skiaAxes.emplace_back(SkFontMgr::FontParameters::Axis{axis.axisTag, axis.value}); + } + + const size_t fontSize = data->size(); + const void* fontPtr = data->data(); + std::unique_ptr fontData(new SkMemoryStream(std::move(data))); + + SkFontMgr::FontParameters params; + params.setCollectionIndex(ttcIndex); + params.setAxes(skiaAxes.data(), skiaAxes.size()); + + sk_sp fm(SkFontMgr::RefDefault()); + sk_sp face(fm->createFromStream(fontData.release(), params)); + if (face == NULL) { + ALOGE("addFont failed to create font, invalid request"); + builder->axes.clear(); + return false; + } std::shared_ptr minikinFont = - std::make_shared(std::move(face), fontData, fontSize, ttcIndex, - std::vector()); - NativeFamilyBuilder* builder = reinterpret_cast(builderPtr); + std::make_shared(std::move(face), fontPtr, fontSize, ttcIndex, + builder->axes); + int weight = givenWeight / 100; - bool italic = givenItalic; - if (weight == 0) { - if (!minikin::FontFamily::analyzeStyle(minikinFont, &weight, &italic)) { + bool italic = givenItalic == 1; + if (givenWeight == RESOLVE_BY_FONT_TABLE || givenItalic == RESOLVE_BY_FONT_TABLE) { + int os2Weight; + bool os2Italic; + if (!minikin::FontFamily::analyzeStyle(minikinFont, &os2Weight, &os2Italic)) { ALOGE("analyzeStyle failed. Using default style"); - weight = 4; - italic = false; + os2Weight = 4; + os2Italic = false; + } + if (givenWeight == RESOLVE_BY_FONT_TABLE) { + weight = os2Weight; + } + if (givenItalic == RESOLVE_BY_FONT_TABLE) { + italic = os2Italic; } } - builder->fonts.push_back(minikin::Font( - std::move(minikinFont), minikin::FontStyle(weight, italic))); + builder->fonts.push_back(minikin::Font(minikinFont, minikin::FontStyle(weight, italic))); + builder->axes.clear(); + return true; } static void release_global_ref(const void* /*data*/, void* context) { @@ -125,80 +158,47 @@ static void release_global_ref(const void* /*data*/, void* context) { } static jboolean FontFamily_addFont(JNIEnv* env, jobject clazz, jlong builderPtr, jobject bytebuf, - jint ttcIndex) { + jint ttcIndex, jint weight, jint isItalic) { NPE_CHECK_RETURN_ZERO(env, bytebuf); + NativeFamilyBuilder* builder = reinterpret_cast(builderPtr); const void* fontPtr = env->GetDirectBufferAddress(bytebuf); if (fontPtr == NULL) { ALOGE("addFont failed to create font, buffer invalid"); + builder->axes.clear(); return false; } jlong fontSize = env->GetDirectBufferCapacity(bytebuf); if (fontSize < 0) { ALOGE("addFont failed to create font, buffer size invalid"); + builder->axes.clear(); return false; } jobject fontRef = MakeGlobalRefOrDie(env, bytebuf); sk_sp data(SkData::MakeWithProc(fontPtr, fontSize, release_global_ref, reinterpret_cast(fontRef))); - std::unique_ptr fontData(new SkMemoryStream(std::move(data))); - - SkFontMgr::FontParameters params; - params.setCollectionIndex(ttcIndex); - - sk_sp fm(SkFontMgr::RefDefault()); - sk_sp face(fm->createFromStream(fontData.release(), params)); - if (face == NULL) { - ALOGE("addFont failed to create font"); - return false; - } - addSkTypeface(builderPtr, std::move(face), fontPtr, (size_t)fontSize, ttcIndex, 0, false); - return true; + return addSkTypeface(builder, std::move(data), ttcIndex, weight, isItalic); } static jboolean FontFamily_addFontWeightStyle(JNIEnv* env, jobject clazz, jlong builderPtr, - jobject font, jint ttcIndex, jint weight, jboolean isItalic) { + jobject font, jint ttcIndex, jint weight, jint isItalic) { NPE_CHECK_RETURN_ZERO(env, font); - NativeFamilyBuilder* builder = reinterpret_cast(builderPtr); - - // Declare axis native type. - std::vector skiaAxes; - skiaAxes.reserve(builder->axes.size()); - for (const minikin::FontVariation& minikinAxis : builder->axes) { - skiaAxes.push_back({minikinAxis.axisTag, minikinAxis.value}); - } - const void* fontPtr = env->GetDirectBufferAddress(font); if (fontPtr == NULL) { ALOGE("addFont failed to create font, buffer invalid"); + builder->axes.clear(); return false; } jlong fontSize = env->GetDirectBufferCapacity(font); if (fontSize < 0) { ALOGE("addFont failed to create font, buffer size invalid"); + builder->axes.clear(); return false; } jobject fontRef = MakeGlobalRefOrDie(env, font); sk_sp data(SkData::MakeWithProc(fontPtr, fontSize, release_global_ref, reinterpret_cast(fontRef))); - std::unique_ptr fontData(new SkMemoryStream(std::move(data))); - - SkFontMgr::FontParameters params; - params.setCollectionIndex(ttcIndex); - params.setAxes(skiaAxes.data(), skiaAxes.size()); - - sk_sp fm(SkFontMgr::RefDefault()); - sk_sp face(fm->createFromStream(fontData.release(), params)); - if (face == NULL) { - ALOGE("addFont failed to create font, invalid request"); - return false; - } - std::shared_ptr minikinFont = - std::make_shared(std::move(face), fontPtr, fontSize, ttcIndex, - std::vector()); - builder->fonts.push_back(minikin::Font(std::move(minikinFont), - minikin::FontStyle(weight / 100, isItalic))); - return true; + return addSkTypeface(builder, std::move(data), ttcIndex, weight, isItalic); } static void releaseAsset(const void* ptr, void* context) { @@ -206,18 +206,21 @@ static void releaseAsset(const void* ptr, void* context) { } static jboolean FontFamily_addFontFromAssetManager(JNIEnv* env, jobject, jlong builderPtr, - jobject jassetMgr, jstring jpath, jint cookie, jboolean isAsset, jint weight, - jboolean isItalic) { + jobject jassetMgr, jstring jpath, jint cookie, jboolean isAsset, jint ttcIndex, + jint weight, jint isItalic) { NPE_CHECK_RETURN_ZERO(env, jassetMgr); NPE_CHECK_RETURN_ZERO(env, jpath); + NativeFamilyBuilder* builder = reinterpret_cast(builderPtr); AssetManager* mgr = assetManagerForJavaObject(env, jassetMgr); if (NULL == mgr) { + builder->axes.clear(); return false; } ScopedUtfChars str(env, jpath); if (str.c_str() == nullptr) { + builder->axes.clear(); return false; } @@ -230,27 +233,19 @@ static jboolean FontFamily_addFontFromAssetManager(JNIEnv* env, jobject, jlong b } if (NULL == asset) { + builder->axes.clear(); return false; } const void* buf = asset->getBuffer(false); if (NULL == buf) { delete asset; + builder->axes.clear(); return false; } - size_t bufSize = asset->getLength(); sk_sp data(SkData::MakeWithProc(buf, asset->getLength(), releaseAsset, asset)); - std::unique_ptr fontData(new SkMemoryStream(std::move(data))); - - sk_sp fm(SkFontMgr::RefDefault()); - sk_sp face(fm->createFromStream(fontData.release(), SkFontMgr::FontParameters())); - if (face == NULL) { - ALOGE("addFontFromAsset failed to create font %s", str.c_str()); - return false; - } - - addSkTypeface(builderPtr, std::move(face), buf, bufSize, 0 /* ttc index */, weight, isItalic); + addSkTypeface(builder, std::move(data), ttcIndex, weight, isItalic); return true; } @@ -266,10 +261,10 @@ static const JNINativeMethod gFontFamilyMethods[] = { { "nCreateFamily", "(J)J", (void*)FontFamily_create }, { "nAbort", "(J)V", (void*)FontFamily_abort }, { "nUnrefFamily", "(J)V", (void*)FontFamily_unref }, - { "nAddFont", "(JLjava/nio/ByteBuffer;I)Z", (void*)FontFamily_addFont }, - { "nAddFontWeightStyle", "(JLjava/nio/ByteBuffer;IIZ)Z", + { "nAddFont", "(JLjava/nio/ByteBuffer;III)Z", (void*)FontFamily_addFont }, + { "nAddFontWeightStyle", "(JLjava/nio/ByteBuffer;III)Z", (void*)FontFamily_addFontWeightStyle }, - { "nAddFontFromAssetManager", "(JLandroid/content/res/AssetManager;Ljava/lang/String;IZIZ)Z", + { "nAddFontFromAssetManager", "(JLandroid/content/res/AssetManager;Ljava/lang/String;IZIII)Z", (void*)FontFamily_addFontFromAssetManager }, { "nAddAxisValue", "(JIF)V", (void*)FontFamily_addAxisValue }, }; diff --git a/core/tests/coretests/src/android/graphics/VariationParserTest.java b/core/tests/coretests/src/android/graphics/VariationParserTest.java index a2ead405b1bc..fdabb130115c 100644 --- a/core/tests/coretests/src/android/graphics/VariationParserTest.java +++ b/core/tests/coretests/src/android/graphics/VariationParserTest.java @@ -28,7 +28,7 @@ public class VariationParserTest extends TestCase { @SmallTest public void testParseFontVariationSetting() { - int tag = FontListParser.makeTag('w', 'd', 't', 'h'); + int tag = FontListParser.makeTag("wdth"); List axes = FontListParser.parseFontVariationSettings("'wdth' 1"); assertEquals(tag, axes.get(0).getTag()); assertEquals(1.0f, axes.get(0).getStyleValue()); @@ -45,7 +45,7 @@ public class VariationParserTest extends TestCase { assertEquals(tag, axes.get(0).getTag()); assertEquals(0.5f, axes.get(0).getStyleValue()); - tag = FontListParser.makeTag('A', 'X', ' ', ' '); + tag = FontListParser.makeTag("AX "); axes = FontListParser.parseFontVariationSettings("'AX ' 1"); assertEquals(tag, axes.get(0).getTag()); assertEquals(1.0f, axes.get(0).getStyleValue()); @@ -93,8 +93,8 @@ public class VariationParserTest extends TestCase { public void testParseFontVariationStyleSettings() { List axes = FontListParser.parseFontVariationSettings("'wdth' 10,'AX '\r1"); - int tag1 = FontListParser.makeTag('w', 'd', 't', 'h'); - int tag2 = FontListParser.makeTag('A', 'X', ' ', ' '); + int tag1 = FontListParser.makeTag("wdth"); + int tag2 = FontListParser.makeTag("AX "); assertEquals(tag1, axes.get(0).getTag()); assertEquals(10.0f, axes.get(0).getStyleValue()); assertEquals(tag2, axes.get(1).getTag()); @@ -102,7 +102,7 @@ public class VariationParserTest extends TestCase { // Test only spacers are allowed before tag axes = FontListParser.parseFontVariationSettings(" 'wdth' 10,ab'wdth' 1"); - tag1 = FontListParser.makeTag('w', 'd', 't', 'h'); + tag1 = FontListParser.makeTag("wdth"); assertEquals(tag1, axes.get(0).getTag()); assertEquals(10.0f, axes.get(0).getStyleValue()); assertEquals(1, axes.size()); @@ -119,8 +119,8 @@ public class VariationParserTest extends TestCase { @SmallTest public void testMakeTag() { - assertEquals(0x77647468, FontListParser.makeTag('w', 'd', 't', 'h')); - assertEquals(0x41582020, FontListParser.makeTag('A', 'X', ' ', ' ')); - assertEquals(0x20202020, FontListParser.makeTag(' ', ' ', ' ', ' ')); + assertEquals(0x77647468, FontListParser.makeTag("wdth")); + assertEquals(0x41582020, FontListParser.makeTag("AX ")); + assertEquals(0x20202020, FontListParser.makeTag(" ")); } } diff --git a/graphics/java/android/graphics/FontFamily.java b/graphics/java/android/graphics/FontFamily.java index 16fc2b143964..1b25a6275d7e 100644 --- a/graphics/java/android/graphics/FontFamily.java +++ b/graphics/java/android/graphics/FontFamily.java @@ -81,7 +81,8 @@ public class FontFamily { } } - public boolean addFont(String path, int ttcIndex) { + public boolean addFont(String path, int ttcIndex, FontConfig.Axis[] axes, int weight, + int italic) { if (mBuilderPtr == 0) { throw new IllegalStateException("Unable to call addFont after freezing."); } @@ -89,22 +90,29 @@ public class FontFamily { FileChannel fileChannel = file.getChannel(); long fontSize = fileChannel.size(); ByteBuffer fontBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fontSize); - return nAddFont(mBuilderPtr, fontBuffer, ttcIndex); + if (axes != null) { + for (FontConfig.Axis axis : axes) { + nAddAxisValue(mBuilderPtr, axis.getTag(), axis.getStyleValue()); + } + } + return nAddFont(mBuilderPtr, fontBuffer, ttcIndex, weight, italic); } catch (IOException e) { Log.e(TAG, "Error mapping font file " + path); return false; } } - public boolean addFontWeightStyle(ByteBuffer font, int ttcIndex, FontConfig.Axis[] axes, - int weight, boolean style) { + public boolean addFontFromBuffer(ByteBuffer font, int ttcIndex, FontConfig.Axis[] axes, + int weight, int italic) { if (mBuilderPtr == 0) { throw new IllegalStateException("Unable to call addFontWeightStyle after freezing."); } - for (FontConfig.Axis axis : axes) { - nAddAxisValue(mBuilderPtr, axis.getTag(), axis.getStyleValue()); + if (axes != null) { + for (FontConfig.Axis axis : axes) { + nAddAxisValue(mBuilderPtr, axis.getTag(), axis.getStyleValue()); + } } - return nAddFontWeightStyle(mBuilderPtr, font, ttcIndex, weight, style); + return nAddFontWeightStyle(mBuilderPtr, font, ttcIndex, weight, italic); } /** @@ -120,11 +128,18 @@ public class FontFamily { * @return */ public boolean addFontFromAssetManager(AssetManager mgr, String path, int cookie, - boolean isAsset, int weight, boolean isItalic) { + boolean isAsset, int ttcIndex, int weight, int isItalic, + FontConfig.Axis[] axes) { if (mBuilderPtr == 0) { throw new IllegalStateException("Unable to call addFontFromAsset after freezing."); } - return nAddFontFromAssetManager(mBuilderPtr, mgr, path, cookie, isAsset, weight, isItalic); + if (axes != null) { + for (FontConfig.Axis axis : axes) { + nAddAxisValue(mBuilderPtr, axis.getTag(), axis.getStyleValue()); + } + } + return nAddFontFromAssetManager(mBuilderPtr, mgr, path, cookie, isAsset, ttcIndex, weight, + isItalic); } private static native long nInitBuilder(String lang, int variant); @@ -137,11 +152,14 @@ public class FontFamily { @CriticalNative private static native void nUnrefFamily(long nativePtr); - private static native boolean nAddFont(long builderPtr, ByteBuffer font, int ttcIndex); + // By passing -1 to weigth argument, the weight value is resolved by OS/2 table in the font. + // By passing -1 to italic argument, the italic value is resolved by OS/2 table in the font. + private static native boolean nAddFont(long builderPtr, ByteBuffer font, int ttcIndex, + int weight, int isItalic); private static native boolean nAddFontWeightStyle(long builderPtr, ByteBuffer font, - int ttcIndex, int weight, boolean isItalic); + int ttcIndex, int weight, int isItalic); private static native boolean nAddFontFromAssetManager(long builderPtr, AssetManager mgr, - String path, int cookie, boolean isAsset, int weight, boolean isItalic); + String path, int cookie, boolean isAsset, int ttcIndex, int weight, int isItalic); // The added axis values are only valid for the next nAddFont* method call. @CriticalNative diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java index 1b6969f96f82..b78df34f5c3e 100644 --- a/graphics/java/android/graphics/FontListParser.java +++ b/graphics/java/android/graphics/FontListParser.java @@ -98,15 +98,17 @@ public class FontListParser { } catch (NumberFormatException e) { continue; // ignoreing invalid number format } - int tag = makeTag(tagString.charAt(0), tagString.charAt(1), tagString.charAt(2), - tagString.charAt(3)); + int tag = makeTag(tagString); axisList.add(new FontConfig.Axis(tag, styleValue)); } return axisList; } - @VisibleForTesting - public static int makeTag(char c1, char c2, char c3, char c4) { + public static int makeTag(String tagString) { + char c1 = tagString.charAt(0); + char c2 = tagString.charAt(1); + char c3 = tagString.charAt(2); + char c4 = tagString.charAt(3); return (c1 << 24) | (c2 << 16) | (c3 << 8) | c4; } @@ -198,6 +200,13 @@ public class FontListParser { */ private static final Pattern TAG_PATTERN = Pattern.compile("[\\x20-\\x7E]{4}"); + public static boolean isValidTag(String tagString) { + if (tagString == null || tagString.length() != 4) { + return false; + } + return TAG_PATTERN.matcher(tagString).matches(); + } + /** The 'styleValue' attribute has an optional leading '-', followed by '', * '.', or '.' where '' is one or more of [0-9]. */ @@ -208,8 +217,8 @@ public class FontListParser { throws XmlPullParserException, IOException { int tag = 0; String tagStr = parser.getAttributeValue(null, "tag"); - if (tagStr != null && TAG_PATTERN.matcher(tagStr).matches()) { - tag = makeTag(tagStr.charAt(0), tagStr.charAt(1), tagStr.charAt(2), tagStr.charAt(3)); + if (isValidTag(tagStr)) { + tag = makeTag(tagStr); } else { throw new XmlPullParserException("Invalid tag attribute value.", parser, null); } diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index 8511c1f4dc94..2afe3756a355 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -21,11 +21,15 @@ import static android.content.res.FontResourcesParser.FontFileResourceEntry; import static android.content.res.FontResourcesParser.FontFamilyFilesResourceEntry; import static android.content.res.FontResourcesParser.FamilyResourceEntry; +import static java.lang.annotation.RetentionPolicy.SOURCE; + import android.annotation.IntDef; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.content.res.AssetManager; +import android.graphics.FontListParser; import android.graphics.fonts.FontRequest; import android.graphics.fonts.FontResult; import android.os.Bundle; @@ -40,12 +44,14 @@ import android.util.LruCache; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.Preconditions; import libcore.io.IoUtils; import org.xmlpull.v1.XmlPullParserException; import java.io.File; +import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; @@ -55,9 +61,12 @@ import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.util.Arrays; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; /** * The Typeface class specifies the typeface and intrinsic style of a font. @@ -148,13 +157,16 @@ public class Typeface { public static Typeface createFromResources(AssetManager mgr, String path, int cookie) { if (sFallbackFonts != null) { synchronized (sDynamicTypefaceCache) { - final String key = createAssetUid(mgr, path); + final String key = Builder.createAssetUid( + mgr, path, 0 /* ttcIndex */, null /* axes */); Typeface typeface = sDynamicTypefaceCache.get(key); if (typeface != null) return typeface; FontFamily fontFamily = new FontFamily(); + // TODO: introduce ttc index and variation settings to resource type font. if (fontFamily.addFontFromAssetManager(mgr, path, cookie, false /* isAsset */, - 0 /* use OS/2 table to determine weight and italic */, false)) { + 0 /* ttcIndex */, Builder.RESOLVE_BY_FONT_TABLE /* weight */, + Builder.RESOLVE_BY_FONT_TABLE /* italic */, null /* axes */)) { fontFamily.freeze(); FontFamily[] families = {fontFamily}; typeface = createFromFamiliesWithDefault(families); @@ -198,8 +210,10 @@ public class Typeface { FontFamily fontFamily = new FontFamily(); for (final FontFileResourceEntry fontFile : filesEntry.getEntries()) { if (!fontFamily.addFontFromAssetManager(mgr, fontFile.getFileName(), - 0 /* resourceCookie */, false /* isAsset */, fontFile.getWeight(), - fontFile.isItalic())) { + 0 /* resourceCookie */, false /* isAsset */, 0 /* ttcIndex */, + fontFile.getWeight(), + fontFile.isItalic() ? Builder.ITALIC : Builder.NORMAL, + null /* axes */)) { return null; } } @@ -207,7 +221,8 @@ public class Typeface { FontFamily[] familyChain = { fontFamily }; typeface = createFromFamiliesWithDefault(familyChain); synchronized (sDynamicTypefaceCache) { - final String key = createAssetUid(mgr, path); + final String key = Builder.createAssetUid(mgr, path, 0 /* ttcIndex */, + null /* axes */); sDynamicTypefaceCache.put(key, typeface); } return typeface; @@ -221,7 +236,7 @@ public class Typeface { */ public static Typeface findFromCache(AssetManager mgr, String path) { synchronized (sDynamicTypefaceCache) { - final String key = createAssetUid(mgr, path); + final String key = Builder.createAssetUid(mgr, path, 0 /* ttcIndex */, null /* axes */); Typeface typeface = sDynamicTypefaceCache.get(key); if (typeface != null) { return typeface; @@ -332,8 +347,9 @@ public class Typeface { int weight = (style & BOLD) != 0 ? 700 : 400; // TODO: this method should be // create(fd, ttcIndex, fontVariationSettings, style). - if (!fontFamily.addFontWeightStyle(fontBuffer, result.getTtcIndex(), - null, weight, (style & ITALIC) != 0)) { + if (!fontFamily.addFontFromBuffer(fontBuffer, result.getTtcIndex(), + null, weight, + (style & ITALIC) == 0 ? Builder.NORMAL : Builder.ITALIC)) { Log.e(TAG, "Error creating font " + request.getQuery()); callback.onTypefaceRequestFailed( FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR); @@ -432,6 +448,347 @@ public class Typeface { }; /** + * A builder class for creating new Typeface instance. + * + * Examples, + * 1) Create Typeface from ttf file. + *
+     * 
+     * Typeface.Builder buidler = new Typeface.Builder.obtain();
+     * builder.setSourceFromFilePath("your_font_file.ttf");
+     * Typeface typeface = builder.build();
+     * builder.recycle();
+     * 
+     * 
+ * + * 2) Create Typeface from ttc file in assets directory. + *
+     * 
+     * Typeface.Builder buidler = new Typeface.Builder.obtain();
+     * builder.setSourceFromAsset(getAssets(), "your_font_file.ttc");
+     * builder.setTtcIndex(2);  // set index of font collection.
+     * Typeface typeface = builder.build();
+     * builder.recycle();
+     * 
+     * 
+ * + * 3) Create Typeface from existing Typeface with variation settings. + *
+     *
+     * 

Note that only one source can be specified for the single Typeface.

+ */ + /** @hide TODO: Make this API public. */ + public static final class Builder { + /** + * Value for weight and italic. + * + * Indicates the value is resolved by font metadata. + */ + // Must be same with C++ constant in core/jni/android/graphics/FontFamily.cpp + public static final int RESOLVE_BY_FONT_TABLE = -1; + + /** + * Value for italic. + * + * Indicates the font style is not italic. + */ + public static final int NORMAL = 0; + + /** + * Value for italic. + * + * Indicates the font style is italic. + */ + public static final int ITALIC = 1; + + private int mTtcIndex; + private FontConfig.Axis[] mAxes; + + private AssetManager mAssetManager; + private String mPath; + private FileDescriptor mFd; + private @IntRange(from = -1) int mWeight = RESOLVE_BY_FONT_TABLE; + + /** @hide */ + @Retention(SOURCE) + @IntDef({RESOLVE_BY_FONT_TABLE, NORMAL, ITALIC}) + public @interface Italic {} + private @Italic int mItalic = RESOLVE_BY_FONT_TABLE; + + private boolean mHasSourceSet = false; + private boolean mRecycled = false; + + /** Use Builder.obtain() instead */ + private void Builder() {} + + private static AtomicReference mCache = new AtomicReference<>(); + + /** + * Returns Typeface.Builder from pool. + */ + public static Builder obtain() { + final Builder builder = mCache.getAndSet(null); + if (builder != null) { + builder.mRecycled = false; + return builder; + } + return new Builder(); + } + + /** + * Resets the internal states. + */ + public void reset() { + checkNotRecycled(); + mTtcIndex = 0; + mAxes = null; + + mAssetManager = null; + mPath = null; + mFd = null; + + mWeight = RESOLVE_BY_FONT_TABLE; + mItalic = RESOLVE_BY_FONT_TABLE; + + mHasSourceSet = false; + } + + /** + * Returns the instance to the pool. + */ + public void recycle() { + reset(); + mRecycled = true; + + mCache.compareAndSet(null, this); + } + + private void checkNotRecycled() { + if (mRecycled) { + throw new IllegalStateException("Don't use Builder after calling recycle()"); + } + } + + private void checkSingleFontSource() { + if (mHasSourceSet) { + throw new IllegalStateException("Typeface can only built with single font source."); + } + } + + /** + * Sets a font file as a source of Typeface. + * + * @param path The file object refers to the font file. + */ + public Builder setSourceFromFile(@NonNull File path) { + return setSourceFromFilePath(path.getAbsolutePath()); + } + + /** + * Sets a font file as a source of Typeface. + * + * @param fd The file descriptor. The passed fd must be mmap-able. + */ + public Builder setSourceFromFile(@NonNull FileDescriptor fd) { + checkNotRecycled(); + checkSingleFontSource(); + mFd = fd; + mHasSourceSet = true; + return this; + } + + /** + * Sets a font file as a source of Typeface. + * + * @param path The full path to the font file. + */ + public Builder setSourceFromFilePath(@NonNull String path) { + checkNotRecycled(); + checkSingleFontSource(); + mPath = path; + mHasSourceSet = true; + return this; + } + + /** + * Sets an asset entry as a source of Typeface. + * + * @param assetManager The application's asset manager + * @param path The file name of the font data in the asset directory + */ + public Builder setSourceFromAsset(@NonNull AssetManager assetManager, + @NonNull String path) { + checkNotRecycled(); + checkSingleFontSource(); + mAssetManager = Preconditions.checkNotNull(assetManager); + mPath = Preconditions.checkStringNotEmpty(path); + mHasSourceSet = true; + return this; + } + + /** + * Sets weight of the font. + * + * By passing {@link #RESOLVE_BY_FONT_TABLE}, weight value is resolved by OS/2 table in + * font file if possible. + * @param weight a weight value or {@link #RESOLVE_BY_FONT_TABLE} + */ + public Builder setWeight(@IntRange(from = -1) int weight) { + checkNotRecycled(); + mWeight = weight; + return this; + } + + /** + * Sets italic information of the font. + * + * By passing {@link #RESOLVE_BY_FONT_TABLE}, italic or normal is determined by OS/2 table + * in font file if possible. + * @param italic One of {@link #NORMAL}, {@link #ITALIC}, {@link #RESOLVE_BY_FONT_TABLE}. + * will be used. + */ + public Builder setItalic(@Italic int italic) { + checkNotRecycled(); + mItalic = italic; + return this; + } + + /** + * Sets an idex of the font collection. + * + * Can not be used for Typeface source. build() method will return null for invalid index. + * @param ttcIndex An index of the font collection. If the font source is not font + * collection, do not call this method or specify 0. + */ + public Builder setTtcIndex(@IntRange(from = 0) int ttcIndex) { + checkNotRecycled(); + mTtcIndex = ttcIndex; + return this; + } + + /** + * Sets a font variation settings. + * + * @param variationSettings See {@link android.widget.TextView#setFontVariationSettings}. + */ + public Builder setFontVariationSettings(@Nullable String variationSettings) { + checkNotRecycled(); + if (mAxes != null) { + throw new IllegalStateException("Font variation settings are already set."); + } + final List axesList = FontListParser.parseFontVariationSettings( + variationSettings); + mAxes = axesList.toArray(new FontConfig.Axis[axesList.size()]); + return this; + } + + /** + * Sets a font variation settings. + * + * @param axes An array of font variation axis tag-value pairs. + */ + public Builder setFontVariationSettings(@Nullable FontConfig.Axis[] axes) { + checkNotRecycled(); + if (mAxes != null) { + throw new IllegalStateException("Font variation settings are already set."); + } + mAxes = axes; + return this; + } + + /** + * Creates a unique id for a given AssetManager and asset path. + * + * @param mgr AssetManager instance + * @param path The path for the asset. + * @param ttcIndex The TTC index for the font. + * @param axes The font variation settings. + * @return Unique id for a given AssetManager and asset path. + */ + private static String createAssetUid(final AssetManager mgr, String path, int ttcIndex, + @Nullable FontConfig.Axis[] axes) { + final SparseArray pkgs = mgr.getAssignedPackageIdentifiers(); + final StringBuilder builder = new StringBuilder(); + final int size = pkgs.size(); + for (int i = 0; i < size; i++) { + builder.append(pkgs.valueAt(i)); + builder.append("-"); + } + builder.append(path); + builder.append("-"); + builder.append(Integer.toString(ttcIndex)); + builder.append("-"); + if (axes != null) { + for (FontConfig.Axis axis : axes) { + builder.append(Integer.toHexString(axis.getTag())); + builder.append("-"); + builder.append(Float.toString(axis.getStyleValue())); + } + } + return builder.toString(); + } + + /** + * Generates new Typeface from specified configuration. + * + * @return Newly created Typeface. May return null if some parameters are invalid. + */ + public Typeface build() { + checkNotRecycled(); + if (!mHasSourceSet) { + return null; + } + + if (mFd != null) { // set source by setSourceFromFile(FileDescriptor) + try (FileInputStream fis = new FileInputStream(mFd)) { + FileChannel channel = fis.getChannel(); + long size = channel.size(); + ByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, size); + + final FontFamily fontFamily = new FontFamily(); + if (!fontFamily.addFontFromBuffer(buffer, mTtcIndex, mAxes, mWeight, mItalic)) { + fontFamily.abortCreation(); + return null; + } + fontFamily.freeze(); + FontFamily[] families = { fontFamily }; + return createFromFamiliesWithDefault(families); + } catch (IOException e) { + return null; + } + } else if (mAssetManager != null) { // set source by setSourceFromAsset() + final String key = createAssetUid(mAssetManager, mPath, mTtcIndex, mAxes); + synchronized (sDynamicTypefaceCache) { + Typeface typeface = sDynamicTypefaceCache.get(key); + if (typeface != null) return typeface; + final FontFamily fontFamily = new FontFamily(); + if (!fontFamily.addFontFromAssetManager(mAssetManager, mPath, mTtcIndex, + true /* isAsset */, mTtcIndex, mWeight, mItalic, mAxes)) { + fontFamily.abortCreation(); + return null; + } + fontFamily.freeze(); + FontFamily[] families = { fontFamily }; + typeface = createFromFamiliesWithDefault(families); + sDynamicTypefaceCache.put(key, typeface); + return typeface; + } + } else if (mPath != null) { // set source by setSourceFromFile(File) + final FontFamily fontFamily = new FontFamily(); + if (!fontFamily.addFont(mPath, mTtcIndex, mAxes, mWeight, mItalic)) { + fontFamily.abortCreation(); + return null; + } + fontFamily.freeze(); + FontFamily[] families = { fontFamily }; + return createFromFamiliesWithDefault(families); + } else { + throw new IllegalArgumentException("No source was set."); + } + } + } + + /** * Create a typeface object given a family name, and option style information. * If null is passed for the name, then the "default" font will be chosen. * The resulting typeface object can be queried (getStyle()) to discover what @@ -518,49 +875,26 @@ public class Typeface { * @return The new typeface. */ public static Typeface createFromAsset(AssetManager mgr, String path) { + if (path == null) { + throw new NullPointerException(); // for backward compatibility + } if (sFallbackFonts != null) { - synchronized (sDynamicTypefaceCache) { - final String key = createAssetUid(mgr, path); - Typeface typeface = sDynamicTypefaceCache.get(key); - if (typeface != null) return typeface; - - FontFamily fontFamily = new FontFamily(); - if (fontFamily.addFontFromAssetManager(mgr, path, 0, true /* isAsset */, - 0 /* use OS/2 table to determine weight and italic */, false)) { - fontFamily.freeze(); - FontFamily[] families = { fontFamily }; - typeface = createFromFamiliesWithDefault(families); - sDynamicTypefaceCache.put(key, typeface); + final Builder builder = Builder.obtain(); + try { + builder.setSourceFromAsset(mgr, path); + Typeface typeface = builder.build(); + if (typeface != null) { return typeface; - } else { - fontFamily.abortCreation(); } + } finally { + builder.recycle(); } } + // For the compatibility reasons, throw runtime exception if failed to create Typeface. throw new RuntimeException("Font asset not found " + path); } /** - * Creates a unique id for a given AssetManager and asset path. - * - * @param mgr AssetManager instance - * @param path The path for the asset. - * @return Unique id for a given AssetManager and asset path. - */ - private static String createAssetUid(final AssetManager mgr, String path) { - final SparseArray pkgs = mgr.getAssignedPackageIdentifiers(); - final StringBuilder builder = new StringBuilder(); - builder.append("asset:"); - final int size = pkgs.size(); - for (int i = 0; i < size; i++) { - builder.append(pkgs.valueAt(i)); - builder.append("-"); - } - builder.append(path); - return builder.toString(); - } - - /** * Creates a unique id for a given font provider and query. */ private static String createProviderUid(String authority, String query) { @@ -578,7 +912,9 @@ public class Typeface { * @param path The path to the font data. * @return The new typeface. */ - public static Typeface createFromFile(File path) { + public static Typeface createFromFile(@Nullable File path) { + // For the compatibility reasons, leaving possible NPE here. + // See android.graphics.cts.TypefaceTest#testCreateFromFileByFileReferenceNull return createFromFile(path.getAbsolutePath()); } @@ -588,15 +924,24 @@ public class Typeface { * @param path The full path to the font data. * @return The new typeface. */ - public static Typeface createFromFile(String path) { + public static Typeface createFromFile(@Nullable String path) { + if (path == null) { + // For the compatibility reasons, need to throw NPE if the argument is null. + // See android.graphics.cts.TypefaceTest#testCreateFromFileByFileNameNull + throw new NullPointerException(); + } if (sFallbackFonts != null) { - FontFamily fontFamily = new FontFamily(); - if (fontFamily.addFont(path, 0 /* ttcIndex */)) { - fontFamily.freeze(); - FontFamily[] families = { fontFamily }; - return createFromFamiliesWithDefault(families); - } else { - fontFamily.abortCreation(); + final Builder builder = Builder.obtain(); + try { + builder.setSourceFromFilePath(path); + Typeface typeface = builder.build(); + if (typeface != null) { + // For the compatibility reasons, throw runtime exception if failed to create + // Typeface. + return typeface; + } + } finally { + builder.recycle(); } } throw new RuntimeException("Font not found " + path); @@ -606,9 +951,8 @@ public class Typeface { * Create a new typeface from an array of font families. * * @param families array of font families - * @hide */ - public static Typeface createFromFamilies(FontFamily[] families) { + private static Typeface createFromFamilies(FontFamily[] families) { long[] ptrArray = new long[families.length]; for (int i = 0; i < families.length; i++) { ptrArray[i] = families[i].mNativePtr; @@ -621,9 +965,8 @@ public class Typeface { * also the font families in the fallback list. * * @param families array of font families - * @hide */ - public static Typeface createFromFamiliesWithDefault(FontFamily[] families) { + private static Typeface createFromFamiliesWithDefault(FontFamily[] families) { long[] ptrArray = new long[families.length + sFallbackFonts.length]; for (int i = 0; i < families.length; i++) { ptrArray[i] = families[i].mNativePtr; @@ -660,8 +1003,8 @@ public class Typeface { continue; } } - if (!fontFamily.addFontWeightStyle(fontBuffer, font.getTtcIndex(), font.getAxes(), - font.getWeight(), font.isItalic())) { + if (!fontFamily.addFontFromBuffer(fontBuffer, font.getTtcIndex(), font.getAxes(), + font.getWeight(), font.isItalic() ? Builder.ITALIC : Builder.NORMAL)) { Log.e(TAG, "Error creating font " + font.getFontName() + "#" + font.getTtcIndex()); } } @@ -806,6 +1149,7 @@ public class Typeface { } private static native long nativeCreateFromTypeface(long native_instance, int style); + // TODO: clean up: change List to FontConfig.Axis[] private static native long nativeCreateFromTypefaceWithVariation( long native_instance, List axes); private static native long nativeCreateWeightAlias(long native_instance, int weight); -- 2.11.0