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