+ * 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.");
+ }
+ }
+ }
+
+ /**