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