1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.graphics;
18
19import android.annotation.Nullable;
20import android.content.res.AssetManager;
21import android.graphics.fonts.FontVariationAxis;
22import android.text.TextUtils;
23import android.util.Log;
24
25import dalvik.annotation.optimization.CriticalNative;
26
27import libcore.util.NativeAllocationRegistry;
28
29import java.io.FileInputStream;
30import java.io.IOException;
31import java.nio.ByteBuffer;
32import java.nio.channels.FileChannel;
33
34/**
35 * A family of typefaces with different styles.
36 *
37 * @hide
38 */
39public class FontFamily {
40
41    private static String TAG = "FontFamily";
42
43    private static final NativeAllocationRegistry sBuilderRegistry = new NativeAllocationRegistry(
44            FontFamily.class.getClassLoader(), nGetBuilderReleaseFunc(), 64);
45
46    private @Nullable Runnable mNativeBuilderCleaner;
47
48    private static final NativeAllocationRegistry sFamilyRegistry = new NativeAllocationRegistry(
49            FontFamily.class.getClassLoader(), nGetFamilyReleaseFunc(), 64);
50
51    /**
52     * @hide
53     */
54    public long mNativePtr;
55
56    // Points native font family builder. Must be zero after freezing this family.
57    private long mBuilderPtr;
58
59    public FontFamily() {
60        mBuilderPtr = nInitBuilder(null, 0);
61        mNativeBuilderCleaner = sBuilderRegistry.registerNativeAllocation(this, mBuilderPtr);
62    }
63
64    public FontFamily(@Nullable String[] langs, int variant) {
65        final String langsString;
66        if (langs == null || langs.length == 0) {
67            langsString = null;
68        } else if (langs.length == 1) {
69            langsString = langs[0];
70        } else {
71            langsString = TextUtils.join(",", langs);
72        }
73        mBuilderPtr = nInitBuilder(langsString, variant);
74        mNativeBuilderCleaner = sBuilderRegistry.registerNativeAllocation(this, mBuilderPtr);
75    }
76
77    /**
78     * Finalize the FontFamily creation.
79     *
80     * @return boolean returns false if some error happens in native code, e.g. broken font file is
81     *                 passed, etc.
82     */
83    public boolean freeze() {
84        if (mBuilderPtr == 0) {
85            throw new IllegalStateException("This FontFamily is already frozen");
86        }
87        mNativePtr = nCreateFamily(mBuilderPtr);
88        mNativeBuilderCleaner.run();
89        mBuilderPtr = 0;
90        if (mNativePtr != 0) {
91            sFamilyRegistry.registerNativeAllocation(this, mNativePtr);
92        }
93        return mNativePtr != 0;
94    }
95
96    public void abortCreation() {
97        if (mBuilderPtr == 0) {
98            throw new IllegalStateException("This FontFamily is already frozen or abandoned");
99        }
100        mNativeBuilderCleaner.run();
101        mBuilderPtr = 0;
102    }
103
104    public boolean addFont(String path, int ttcIndex, FontVariationAxis[] axes, int weight,
105            int italic) {
106        if (mBuilderPtr == 0) {
107            throw new IllegalStateException("Unable to call addFont after freezing.");
108        }
109        try (FileInputStream file = new FileInputStream(path)) {
110            FileChannel fileChannel = file.getChannel();
111            long fontSize = fileChannel.size();
112            ByteBuffer fontBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fontSize);
113            if (axes != null) {
114                for (FontVariationAxis axis : axes) {
115                    nAddAxisValue(mBuilderPtr, axis.getOpenTypeTagValue(), axis.getStyleValue());
116                }
117            }
118            return nAddFont(mBuilderPtr, fontBuffer, ttcIndex, weight, italic);
119        } catch (IOException e) {
120            Log.e(TAG, "Error mapping font file " + path);
121            return false;
122        }
123    }
124
125    public boolean addFontFromBuffer(ByteBuffer font, int ttcIndex, FontVariationAxis[] axes,
126            int weight, int italic) {
127        if (mBuilderPtr == 0) {
128            throw new IllegalStateException("Unable to call addFontWeightStyle after freezing.");
129        }
130        if (axes != null) {
131            for (FontVariationAxis axis : axes) {
132                nAddAxisValue(mBuilderPtr, axis.getOpenTypeTagValue(), axis.getStyleValue());
133            }
134        }
135        return nAddFontWeightStyle(mBuilderPtr, font, ttcIndex, weight, italic);
136    }
137
138    /**
139     * @param mgr The AssetManager to use for this context.
140     * @param path The path to the font file to load.
141     * @param cookie If available, the resource cookie given by Resources.
142     * @param isAsset {@code true} if this is from the assets/ folder, {@code false} if from
143     *            resources
144     * @param weight The weight of the font. If 0 is given, the weight and italic will be resolved
145     *            using the OS/2 table in the font.
146     * @param isItalic Whether this font is italic. If the weight is set to 0, this will be resolved
147     *            using the OS/2 table in the font.
148     * @return
149     */
150    public boolean addFontFromAssetManager(AssetManager mgr, String path, int cookie,
151            boolean isAsset, int ttcIndex, int weight, int isItalic,
152            FontVariationAxis[] axes) {
153        if (mBuilderPtr == 0) {
154            throw new IllegalStateException("Unable to call addFontFromAsset after freezing.");
155        }
156        if (axes != null) {
157            for (FontVariationAxis axis : axes) {
158                nAddAxisValue(mBuilderPtr, axis.getOpenTypeTagValue(), axis.getStyleValue());
159            }
160        }
161        return nAddFontFromAssetManager(mBuilderPtr, mgr, path, cookie, isAsset, ttcIndex, weight,
162                isItalic);
163    }
164
165    // TODO: Remove once internal user stop using private API.
166    private static boolean nAddFont(long builderPtr, ByteBuffer font, int ttcIndex) {
167        return nAddFont(builderPtr, font, ttcIndex, -1, -1);
168    }
169
170    private static native long nInitBuilder(String langs, int variant);
171
172    @CriticalNative
173    private static native long nCreateFamily(long mBuilderPtr);
174
175    @CriticalNative
176    private static native long nGetBuilderReleaseFunc();
177
178    @CriticalNative
179    private static native long nGetFamilyReleaseFunc();
180    // By passing -1 to weigth argument, the weight value is resolved by OS/2 table in the font.
181    // By passing -1 to italic argument, the italic value is resolved by OS/2 table in the font.
182    private static native boolean nAddFont(long builderPtr, ByteBuffer font, int ttcIndex,
183            int weight, int isItalic);
184    private static native boolean nAddFontWeightStyle(long builderPtr, ByteBuffer font,
185            int ttcIndex, int weight, int isItalic);
186    private static native boolean nAddFontFromAssetManager(long builderPtr, AssetManager mgr,
187            String path, int cookie, boolean isAsset, int ttcIndex, int weight, int isItalic);
188
189    // The added axis values are only valid for the next nAddFont* method call.
190    @CriticalNative
191    private static native void nAddAxisValue(long builderPtr, int tag, float value);
192}
193