Typeface.java revision a0a4828614edfd633ab86f04408e7f4e55b491dd
1/*
2 * Copyright (C) 2006 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.content.res.AssetManager;
20import android.util.Log;
21import android.util.LongSparseArray;
22import android.util.LruCache;
23import android.util.SparseArray;
24
25import org.xmlpull.v1.XmlPullParserException;
26
27import java.io.File;
28import java.io.FileInputStream;
29import java.io.FileNotFoundException;
30import java.io.IOException;
31import java.nio.ByteBuffer;
32import java.nio.channels.FileChannel;
33import java.util.ArrayList;
34import java.util.HashMap;
35import java.util.List;
36import java.util.Map;
37
38/**
39 * The Typeface class specifies the typeface and intrinsic style of a font.
40 * This is used in the paint, along with optionally Paint settings like
41 * textSize, textSkewX, textScaleX to specify
42 * how text appears when drawn (and measured).
43 */
44public class Typeface {
45
46    private static String TAG = "Typeface";
47
48    /** The default NORMAL typeface object */
49    public static final Typeface DEFAULT;
50    /**
51     * The default BOLD typeface object. Note: this may be not actually be
52     * bold, depending on what fonts are installed. Call getStyle() to know
53     * for sure.
54     */
55    public static final Typeface DEFAULT_BOLD;
56    /** The NORMAL style of the default sans serif typeface. */
57    public static final Typeface SANS_SERIF;
58    /** The NORMAL style of the default serif typeface. */
59    public static final Typeface SERIF;
60    /** The NORMAL style of the default monospace typeface. */
61    public static final Typeface MONOSPACE;
62
63    static Typeface[] sDefaults;
64    private static final LongSparseArray<SparseArray<Typeface>> sTypefaceCache =
65            new LongSparseArray<SparseArray<Typeface>>(3);
66
67    /**
68     * Cache for Typeface objects dynamically loaded from assets. Currently max size is 16.
69     */
70    private static final LruCache<String, Typeface> sDynamicTypefaceCache = new LruCache<>(16);
71
72    static Typeface sDefaultTypeface;
73    static Map<String, Typeface> sSystemFontMap;
74    static FontFamily[] sFallbackFonts;
75
76    static final String FONTS_CONFIG = "fonts.xml";
77
78    /**
79     * @hide
80     */
81    public long native_instance;
82
83    // Style
84    public static final int NORMAL = 0;
85    public static final int BOLD = 1;
86    public static final int ITALIC = 2;
87    public static final int BOLD_ITALIC = 3;
88
89    private int mStyle = 0;
90
91    private static void setDefault(Typeface t) {
92        sDefaultTypeface = t;
93        nativeSetDefault(t.native_instance);
94    }
95
96    /** Returns the typeface's intrinsic style attributes */
97    public int getStyle() {
98        return mStyle;
99    }
100
101    /** Returns true if getStyle() has the BOLD bit set. */
102    public final boolean isBold() {
103        return (mStyle & BOLD) != 0;
104    }
105
106    /** Returns true if getStyle() has the ITALIC bit set. */
107    public final boolean isItalic() {
108        return (mStyle & ITALIC) != 0;
109    }
110
111    /**
112     * Create a typeface object given a family name, and option style information.
113     * If null is passed for the name, then the "default" font will be chosen.
114     * The resulting typeface object can be queried (getStyle()) to discover what
115     * its "real" style characteristics are.
116     *
117     * @param familyName May be null. The name of the font family.
118     * @param style  The style (normal, bold, italic) of the typeface.
119     *               e.g. NORMAL, BOLD, ITALIC, BOLD_ITALIC
120     * @return The best matching typeface.
121     */
122    public static Typeface create(String familyName, int style) {
123        if (sSystemFontMap != null) {
124            return create(sSystemFontMap.get(familyName), style);
125        }
126        return null;
127    }
128
129    /**
130     * Create a typeface object that best matches the specified existing
131     * typeface and the specified Style. Use this call if you want to pick a new
132     * style from the same family of an existing typeface object. If family is
133     * null, this selects from the default font's family.
134     *
135     * @param family May be null. The name of the existing type face.
136     * @param style  The style (normal, bold, italic) of the typeface.
137     *               e.g. NORMAL, BOLD, ITALIC, BOLD_ITALIC
138     * @return The best matching typeface.
139     */
140    public static Typeface create(Typeface family, int style) {
141        if (style < 0 || style > 3) {
142            style = 0;
143        }
144        long ni = 0;
145        if (family != null) {
146            // Return early if we're asked for the same face/style
147            if (family.mStyle == style) {
148                return family;
149            }
150
151            ni = family.native_instance;
152        }
153
154        Typeface typeface;
155        SparseArray<Typeface> styles = sTypefaceCache.get(ni);
156
157        if (styles != null) {
158            typeface = styles.get(style);
159            if (typeface != null) {
160                return typeface;
161            }
162        }
163
164        typeface = new Typeface(nativeCreateFromTypeface(ni, style));
165        if (styles == null) {
166            styles = new SparseArray<Typeface>(4);
167            sTypefaceCache.put(ni, styles);
168        }
169        styles.put(style, typeface);
170
171        return typeface;
172    }
173
174    /**
175     * Returns one of the default typeface objects, based on the specified style
176     *
177     * @return the default typeface that corresponds to the style
178     */
179    public static Typeface defaultFromStyle(int style) {
180        return sDefaults[style];
181    }
182
183    /**
184     * Create a new typeface from the specified font data.
185     *
186     * @param mgr  The application's asset manager
187     * @param path The file name of the font data in the assets directory
188     * @return The new typeface.
189     */
190    public static Typeface createFromAsset(AssetManager mgr, String path) {
191        if (sFallbackFonts != null) {
192            synchronized (sDynamicTypefaceCache) {
193                final String key = createAssetUid(mgr, path);
194                Typeface typeface = sDynamicTypefaceCache.get(key);
195                if (typeface != null) return typeface;
196
197                FontFamily fontFamily = new FontFamily();
198                if (fontFamily.addFontFromAsset(mgr, path)) {
199                    fontFamily.freeze();
200                    FontFamily[] families = { fontFamily };
201                    typeface = createFromFamiliesWithDefault(families);
202                    sDynamicTypefaceCache.put(key, typeface);
203                    return typeface;
204                }
205            }
206        }
207        throw new RuntimeException("Font asset not found " + path);
208    }
209
210    /**
211     * Creates a unique id for a given AssetManager and asset path.
212     *
213     * @param mgr  AssetManager instance
214     * @param path The path for the asset.
215     * @return Unique id for a given AssetManager and asset path.
216     */
217    private static String createAssetUid(final AssetManager mgr, String path) {
218        final SparseArray<String> pkgs = mgr.getAssignedPackageIdentifiers();
219        final StringBuilder builder = new StringBuilder();
220        final int size = pkgs.size();
221        for (int i = 0; i < size; i++) {
222            builder.append(pkgs.valueAt(i));
223            builder.append("-");
224        }
225        builder.append(path);
226        return builder.toString();
227    }
228
229    /**
230     * Create a new typeface from the specified font file.
231     *
232     * @param path The path to the font data.
233     * @return The new typeface.
234     */
235    public static Typeface createFromFile(File path) {
236        return createFromFile(path.getAbsolutePath());
237    }
238
239    /**
240     * Create a new typeface from the specified font file.
241     *
242     * @param path The full path to the font data.
243     * @return The new typeface.
244     */
245    public static Typeface createFromFile(String path) {
246        if (sFallbackFonts != null) {
247            FontFamily fontFamily = new FontFamily();
248            if (fontFamily.addFont(path, 0 /* ttcIndex */)) {
249                fontFamily.freeze();
250                FontFamily[] families = { fontFamily };
251                return createFromFamiliesWithDefault(families);
252            }
253        }
254        throw new RuntimeException("Font not found " + path);
255    }
256
257    /**
258     * Create a new typeface from an array of font families.
259     *
260     * @param families array of font families
261     * @hide
262     */
263    public static Typeface createFromFamilies(FontFamily[] families) {
264        long[] ptrArray = new long[families.length];
265        for (int i = 0; i < families.length; i++) {
266            ptrArray[i] = families[i].mNativePtr;
267        }
268        return new Typeface(nativeCreateFromArray(ptrArray));
269    }
270
271    /**
272     * Create a new typeface from an array of font families, including
273     * also the font families in the fallback list.
274     *
275     * @param families array of font families
276     * @hide
277     */
278    public static Typeface createFromFamiliesWithDefault(FontFamily[] families) {
279        long[] ptrArray = new long[families.length + sFallbackFonts.length];
280        for (int i = 0; i < families.length; i++) {
281            ptrArray[i] = families[i].mNativePtr;
282        }
283        for (int i = 0; i < sFallbackFonts.length; i++) {
284            ptrArray[i + families.length] = sFallbackFonts[i].mNativePtr;
285        }
286        return new Typeface(nativeCreateFromArray(ptrArray));
287    }
288
289    // don't allow clients to call this directly
290    private Typeface(long ni) {
291        if (ni == 0) {
292            throw new RuntimeException("native typeface cannot be made");
293        }
294
295        native_instance = ni;
296        mStyle = nativeGetStyle(ni);
297    }
298
299    private static FontFamily makeFamilyFromParsed(FontListParser.Family family,
300            Map<String, ByteBuffer> bufferForPath) {
301        FontFamily fontFamily = new FontFamily(family.lang, family.variant);
302        for (FontListParser.Font font : family.fonts) {
303            ByteBuffer fontBuffer = bufferForPath.get(font.fontName);
304            if (fontBuffer == null) {
305                try (FileInputStream file = new FileInputStream(font.fontName)) {
306                    FileChannel fileChannel = file.getChannel();
307                    long fontSize = fileChannel.size();
308                    fontBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fontSize);
309                    bufferForPath.put(font.fontName, fontBuffer);
310                } catch (IOException e) {
311                    Log.e(TAG, "Error mapping font file " + font.fontName);
312                    continue;
313                }
314            }
315            if (!fontFamily.addFontWeightStyle(fontBuffer, font.ttcIndex, font.axes,
316                    font.weight, font.isItalic)) {
317                Log.e(TAG, "Error creating font " + font.fontName + "#" + font.ttcIndex);
318            }
319        }
320        fontFamily.freeze();
321        return fontFamily;
322    }
323
324    /*
325     * (non-Javadoc)
326     *
327     * This should only be called once, from the static class initializer block.
328     */
329    private static void init() {
330        // Load font config and initialize Minikin state
331        File systemFontConfigLocation = getSystemFontConfigLocation();
332        File configFilename = new File(systemFontConfigLocation, FONTS_CONFIG);
333        try {
334            FileInputStream fontsIn = new FileInputStream(configFilename);
335            FontListParser.Config fontConfig = FontListParser.parse(fontsIn);
336
337            Map<String, ByteBuffer> bufferForPath = new HashMap<String, ByteBuffer>();
338
339            List<FontFamily> familyList = new ArrayList<FontFamily>();
340            // Note that the default typeface is always present in the fallback list;
341            // this is an enhancement from pre-Minikin behavior.
342            for (int i = 0; i < fontConfig.families.size(); i++) {
343                FontListParser.Family f = fontConfig.families.get(i);
344                if (i == 0 || f.name == null) {
345                    familyList.add(makeFamilyFromParsed(f, bufferForPath));
346                }
347            }
348            sFallbackFonts = familyList.toArray(new FontFamily[familyList.size()]);
349            setDefault(Typeface.createFromFamilies(sFallbackFonts));
350
351            Map<String, Typeface> systemFonts = new HashMap<String, Typeface>();
352            for (int i = 0; i < fontConfig.families.size(); i++) {
353                Typeface typeface;
354                FontListParser.Family f = fontConfig.families.get(i);
355                if (f.name != null) {
356                    if (i == 0) {
357                        // The first entry is the default typeface; no sense in
358                        // duplicating the corresponding FontFamily.
359                        typeface = sDefaultTypeface;
360                    } else {
361                        FontFamily fontFamily = makeFamilyFromParsed(f, bufferForPath);
362                        FontFamily[] families = { fontFamily };
363                        typeface = Typeface.createFromFamiliesWithDefault(families);
364                    }
365                    systemFonts.put(f.name, typeface);
366                }
367            }
368            for (FontListParser.Alias alias : fontConfig.aliases) {
369                Typeface base = systemFonts.get(alias.toName);
370                Typeface newFace = base;
371                int weight = alias.weight;
372                if (weight != 400) {
373                    newFace = new Typeface(nativeCreateWeightAlias(base.native_instance, weight));
374                }
375                systemFonts.put(alias.name, newFace);
376            }
377            sSystemFontMap = systemFonts;
378
379        } catch (RuntimeException e) {
380            Log.w(TAG, "Didn't create default family (most likely, non-Minikin build)", e);
381            // TODO: normal in non-Minikin case, remove or make error when Minikin-only
382        } catch (FileNotFoundException e) {
383            Log.e(TAG, "Error opening " + configFilename, e);
384        } catch (IOException e) {
385            Log.e(TAG, "Error reading " + configFilename, e);
386        } catch (XmlPullParserException e) {
387            Log.e(TAG, "XML parse exception for " + configFilename, e);
388        }
389    }
390
391    static {
392        init();
393        // Set up defaults and typefaces exposed in public API
394        DEFAULT         = create((String) null, 0);
395        DEFAULT_BOLD    = create((String) null, Typeface.BOLD);
396        SANS_SERIF      = create("sans-serif", 0);
397        SERIF           = create("serif", 0);
398        MONOSPACE       = create("monospace", 0);
399
400        sDefaults = new Typeface[] {
401            DEFAULT,
402            DEFAULT_BOLD,
403            create((String) null, Typeface.ITALIC),
404            create((String) null, Typeface.BOLD_ITALIC),
405        };
406
407    }
408
409    private static File getSystemFontConfigLocation() {
410        return new File("/system/etc/");
411    }
412
413    @Override
414    protected void finalize() throws Throwable {
415        try {
416            nativeUnref(native_instance);
417            native_instance = 0;  // Other finalizers can still call us.
418        } finally {
419            super.finalize();
420        }
421    }
422
423    @Override
424    public boolean equals(Object o) {
425        if (this == o) return true;
426        if (o == null || getClass() != o.getClass()) return false;
427
428        Typeface typeface = (Typeface) o;
429
430        return mStyle == typeface.mStyle && native_instance == typeface.native_instance;
431    }
432
433    @Override
434    public int hashCode() {
435        /*
436         * Modified method for hashCode with long native_instance derived from
437         * http://developer.android.com/reference/java/lang/Object.html
438         */
439        int result = 17;
440        result = 31 * result + (int) (native_instance ^ (native_instance >>> 32));
441        result = 31 * result + mStyle;
442        return result;
443    }
444
445    private static native long nativeCreateFromTypeface(long native_instance, int style);
446    private static native long nativeCreateWeightAlias(long native_instance, int weight);
447    private static native void nativeUnref(long native_instance);
448    private static native int  nativeGetStyle(long native_instance);
449    private static native long nativeCreateFromArray(long[] familyArray);
450    private static native void nativeSetDefault(long native_instance);
451}
452