OSDN Git Service

Expose Typeface creation APIs with ttc and font variation.
authorSeigo Nonaka <nona@google.com>
Wed, 18 Jan 2017 11:31:27 +0000 (20:31 +0900)
committerSeigo Nonaka <nona@google.com>
Tue, 21 Mar 2017 20:27:33 +0000 (13:27 -0700)
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
core/jni/android/graphics/FontFamily.cpp
core/tests/coretests/src/android/graphics/VariationParserTest.java
graphics/java/android/graphics/FontFamily.java
graphics/java/android/graphics/FontListParser.java
graphics/java/android/graphics/Typeface.java

index 04596fa..9c15e00 100644 (file)
@@ -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;
         }
 
         /**
index fb7c5c4..4e68602 100644 (file)
 
 #include <hwui/MinikinSkia.h>
 #include <hwui/Typeface.h>
+#include <utils/FatVector.h>
 #include <minikin/FontFamily.h>
 
 #include <memory>
 
 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<SkTypeface> face, const void* fontData,
-        size_t fontSize, int ttcIndex, jint givenWeight, jboolean givenItalic) {
+static bool addSkTypeface(NativeFamilyBuilder* builder, sk_sp<SkData>&& data, int ttcIndex,
+        jint givenWeight, jint givenItalic) {
+    uirenderer::FatVector<SkFontMgr::FontParameters::Axis, 2> 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<SkStreamAsset> fontData(new SkMemoryStream(std::move(data)));
+
+    SkFontMgr::FontParameters params;
+    params.setCollectionIndex(ttcIndex);
+    params.setAxes(skiaAxes.data(), skiaAxes.size());
+
+    sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault());
+    sk_sp<SkTypeface> 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<minikin::MinikinFont> minikinFont =
-            std::make_shared<MinikinFontSkia>(std::move(face), fontData, fontSize, ttcIndex,
-                    std::vector<minikin::FontVariation>());
-    NativeFamilyBuilder* builder = reinterpret_cast<NativeFamilyBuilder*>(builderPtr);
+            std::make_shared<MinikinFontSkia>(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<NativeFamilyBuilder*>(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<SkData> data(SkData::MakeWithProc(fontPtr, fontSize,
             release_global_ref, reinterpret_cast<void*>(fontRef)));
-    std::unique_ptr<SkStreamAsset> fontData(new SkMemoryStream(std::move(data)));
-
-    SkFontMgr::FontParameters params;
-    params.setCollectionIndex(ttcIndex);
-
-    sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault());
-    sk_sp<SkTypeface> 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<NativeFamilyBuilder*>(builderPtr);
-
-    // Declare axis native type.
-    std::vector<SkFontMgr::FontParameters::Axis> 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<SkData> data(SkData::MakeWithProc(fontPtr, fontSize,
             release_global_ref, reinterpret_cast<void*>(fontRef)));
-    std::unique_ptr<SkStreamAsset> fontData(new SkMemoryStream(std::move(data)));
-
-    SkFontMgr::FontParameters params;
-    params.setCollectionIndex(ttcIndex);
-    params.setAxes(skiaAxes.data(), skiaAxes.size());
-
-    sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault());
-    sk_sp<SkTypeface> face(fm->createFromStream(fontData.release(), params));
-    if (face == NULL) {
-        ALOGE("addFont failed to create font, invalid request");
-        return false;
-    }
-    std::shared_ptr<minikin::MinikinFont> minikinFont =
-            std::make_shared<MinikinFontSkia>(std::move(face), fontPtr, fontSize, ttcIndex,
-                    std::vector<minikin::FontVariation>());
-    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<NativeFamilyBuilder*>(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<SkData> data(SkData::MakeWithProc(buf, asset->getLength(), releaseAsset, asset));
-    std::unique_ptr<SkStreamAsset> fontData(new SkMemoryStream(std::move(data)));
-
-    sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault());
-    sk_sp<SkTypeface> 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 },
 };
index a2ead40..fdabb13 100644 (file)
@@ -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<FontConfig.Axis> 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<FontConfig.Axis> 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("    "));
     }
 }
index 16fc2b1..1b25a62 100644 (file)
@@ -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
index 1b6969f..b78df34 100644 (file)
@@ -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 '<digits>',
      *  '<digits>.<digits>', or '.<digits>' where '<digits>' 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);
         }
index 8511c1f..2afe375 100644 (file)
@@ -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.
+     * <pre>
+     * <code>
+     * Typeface.Builder buidler = new Typeface.Builder.obtain();
+     * builder.setSourceFromFilePath("your_font_file.ttf");
+     * Typeface typeface = builder.build();
+     * builder.recycle();
+     * </code>
+     * </pre>
+     *
+     * 2) Create Typeface from ttc file in assets directory.
+     * <pre>
+     * <code>
+     * 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();
+     * </code>
+     * </pre>
+     *
+     * 3) Create Typeface from existing Typeface with variation settings.
+     * <pre>
+     *
+     * <p>Note that only one source can be specified for the single Typeface.</p>
+     */
+    /** @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<Builder> 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<FontConfig.Axis> 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<String> 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<String> 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<FontConfig.Axis> to FontConfig.Axis[]
     private static native long nativeCreateFromTypefaceWithVariation(
             long native_instance, List<FontConfig.Axis> axes);
     private static native long nativeCreateWeightAlias(long native_instance, int weight);