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
diff --git a/core/java/android/text/FontConfig.java b/core/java/android/text/FontConfig.java
index 04596fa..9c15e00 100644
--- a/core/java/android/text/FontConfig.java
+++ b/core/java/android/text/FontConfig.java
@@ -21,6 +21,7 @@
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 @@
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 fb7c5c4..4e68602 100644
--- a/core/jni/android/graphics/FontFamily.cpp
+++ b/core/jni/android/graphics/FontFamily.cpp
@@ -34,12 +34,16 @@
#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 @@
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 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 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 @@
}
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 @@
{ "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 a2ead40..fdabb13 100644
--- a/core/tests/coretests/src/android/graphics/VariationParserTest.java
+++ b/core/tests/coretests/src/android/graphics/VariationParserTest.java
@@ -28,7 +28,7 @@
@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 @@
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 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 @@
// 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 @@
@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 16fc2b1..1b25a627 100644
--- a/graphics/java/android/graphics/FontFamily.java
+++ b/graphics/java/android/graphics/FontFamily.java
@@ -81,7 +81,8 @@
}
}
- 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 @@
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 @@
* @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 @@
@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 1b6969f..b78df34 100644
--- a/graphics/java/android/graphics/FontListParser.java
+++ b/graphics/java/android/graphics/FontListParser.java
@@ -98,15 +98,17 @@
} 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 @@
*/
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 @@
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 8511c1f..2afe375 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.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.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.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 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 @@
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 @@
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 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 @@
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 @@
};
/**
+ * 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 @@
* @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 @@
* @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 @@
* @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 @@
* 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 @@
* 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 @@
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 @@
}
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);