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.graphics.FontListParser.Family;
21import android.util.Log;
22import android.util.LongSparseArray;
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.util.ArrayList;
32import java.util.HashMap;
33import java.util.List;
34import java.util.Map;
35
36/**
37 * The Typeface class specifies the typeface and intrinsic style of a font.
38 * This is used in the paint, along with optionally Paint settings like
39 * textSize, textSkewX, textScaleX to specify
40 * how text appears when drawn (and measured).
41 */
42public class Typeface {
43
44    private static String TAG = "Typeface";
45
46    /** The default NORMAL typeface object */
47    public static final Typeface DEFAULT;
48    /**
49     * The default BOLD typeface object. Note: this may be not actually be
50     * bold, depending on what fonts are installed. Call getStyle() to know
51     * for sure.
52     */
53    public static final Typeface DEFAULT_BOLD;
54    /** The NORMAL style of the default sans serif typeface. */
55    public static final Typeface SANS_SERIF;
56    /** The NORMAL style of the default serif typeface. */
57    public static final Typeface SERIF;
58    /** The NORMAL style of the default monospace typeface. */
59    public static final Typeface MONOSPACE;
60
61    static Typeface[] sDefaults;
62    private static final LongSparseArray<SparseArray<Typeface>> sTypefaceCache =
63            new LongSparseArray<SparseArray<Typeface>>(3);
64
65    static Typeface sDefaultTypeface;
66    static Map<String, Typeface> sSystemFontMap;
67    static FontFamily[] sFallbackFonts;
68
69    static final String FONTS_CONFIG = "fonts.xml";
70
71    /**
72     * @hide
73     */
74    public long native_instance;
75
76    // Style
77    public static final int NORMAL = 0;
78    public static final int BOLD = 1;
79    public static final int ITALIC = 2;
80    public static final int BOLD_ITALIC = 3;
81
82    private int mStyle = 0;
83
84    private static void setDefault(Typeface t) {
85        sDefaultTypeface = t;
86        nativeSetDefault(t.native_instance);
87    }
88
89    /** Returns the typeface's intrinsic style attributes */
90    public int getStyle() {
91        return mStyle;
92    }
93
94    /** Returns true if getStyle() has the BOLD bit set. */
95    public final boolean isBold() {
96        return (mStyle & BOLD) != 0;
97    }
98
99    /** Returns true if getStyle() has the ITALIC bit set. */
100    public final boolean isItalic() {
101        return (mStyle & ITALIC) != 0;
102    }
103
104    /**
105     * Create a typeface object given a family name, and option style information.
106     * If null is passed for the name, then the "default" font will be chosen.
107     * The resulting typeface object can be queried (getStyle()) to discover what
108     * its "real" style characteristics are.
109     *
110     * @param familyName May be null. The name of the font family.
111     * @param style  The style (normal, bold, italic) of the typeface.
112     *               e.g. NORMAL, BOLD, ITALIC, BOLD_ITALIC
113     * @return The best matching typeface.
114     */
115    public static Typeface create(String familyName, int style) {
116        if (sSystemFontMap != null) {
117            return create(sSystemFontMap.get(familyName), style);
118        }
119        return null;
120    }
121
122    /**
123     * Create a typeface object that best matches the specified existing
124     * typeface and the specified Style. Use this call if you want to pick a new
125     * style from the same family of an existing typeface object. If family is
126     * null, this selects from the default font's family.
127     *
128     * @param family May be null. The name of the existing type face.
129     * @param style  The style (normal, bold, italic) of the typeface.
130     *               e.g. NORMAL, BOLD, ITALIC, BOLD_ITALIC
131     * @return The best matching typeface.
132     */
133    public static Typeface create(Typeface family, int style) {
134        if (style < 0 || style > 3) {
135            style = 0;
136        }
137        long ni = 0;
138        if (family != null) {
139            // Return early if we're asked for the same face/style
140            if (family.mStyle == style) {
141                return family;
142            }
143
144            ni = family.native_instance;
145        }
146
147        Typeface typeface;
148        SparseArray<Typeface> styles = sTypefaceCache.get(ni);
149
150        if (styles != null) {
151            typeface = styles.get(style);
152            if (typeface != null) {
153                return typeface;
154            }
155        }
156
157        typeface = new Typeface(nativeCreateFromTypeface(ni, style));
158        if (styles == null) {
159            styles = new SparseArray<Typeface>(4);
160            sTypefaceCache.put(ni, styles);
161        }
162        styles.put(style, typeface);
163
164        return typeface;
165    }
166
167    /**
168     * Returns one of the default typeface objects, based on the specified style
169     *
170     * @return the default typeface that corresponds to the style
171     */
172    public static Typeface defaultFromStyle(int style) {
173        return sDefaults[style];
174    }
175
176    /**
177     * Create a new typeface from the specified font data.
178     * @param mgr The application's asset manager
179     * @param path  The file name of the font data in the assets directory
180     * @return The new typeface.
181     */
182    public static Typeface createFromAsset(AssetManager mgr, String path) {
183        if (sFallbackFonts != null) {
184            FontFamily fontFamily = new FontFamily();
185            if (fontFamily.addFontFromAsset(mgr, path)) {
186                FontFamily[] families = { fontFamily };
187                return createFromFamiliesWithDefault(families);
188            }
189        }
190        throw new RuntimeException("Font asset not found " + path);
191    }
192
193    /**
194     * Create a new typeface from the specified font file.
195     *
196     * @param path The path to the font data.
197     * @return The new typeface.
198     */
199    public static Typeface createFromFile(File path) {
200        return createFromFile(path.getAbsolutePath());
201    }
202
203    /**
204     * Create a new typeface from the specified font file.
205     *
206     * @param path The full path to the font data.
207     * @return The new typeface.
208     */
209    public static Typeface createFromFile(String path) {
210        if (sFallbackFonts != null) {
211            FontFamily fontFamily = new FontFamily();
212            if (fontFamily.addFont(path)) {
213                FontFamily[] families = { fontFamily };
214                return createFromFamiliesWithDefault(families);
215            }
216        }
217        throw new RuntimeException("Font not found " + path);
218    }
219
220    /**
221     * Create a new typeface from an array of font families.
222     *
223     * @param families array of font families
224     * @hide
225     */
226    public static Typeface createFromFamilies(FontFamily[] families) {
227        long[] ptrArray = new long[families.length];
228        for (int i = 0; i < families.length; i++) {
229            ptrArray[i] = families[i].mNativePtr;
230        }
231        return new Typeface(nativeCreateFromArray(ptrArray));
232    }
233
234    /**
235     * Create a new typeface from an array of font families, including
236     * also the font families in the fallback list.
237     *
238     * @param families array of font families
239     * @hide
240     */
241    public static Typeface createFromFamiliesWithDefault(FontFamily[] families) {
242        long[] ptrArray = new long[families.length + sFallbackFonts.length];
243        for (int i = 0; i < families.length; i++) {
244            ptrArray[i] = families[i].mNativePtr;
245        }
246        for (int i = 0; i < sFallbackFonts.length; i++) {
247            ptrArray[i + families.length] = sFallbackFonts[i].mNativePtr;
248        }
249        return new Typeface(nativeCreateFromArray(ptrArray));
250    }
251
252    // don't allow clients to call this directly
253    private Typeface(long ni) {
254        if (ni == 0) {
255            throw new RuntimeException("native typeface cannot be made");
256        }
257
258        native_instance = ni;
259        mStyle = nativeGetStyle(ni);
260    }
261
262    private static FontFamily makeFamilyFromParsed(FontListParser.Family family) {
263        FontFamily fontFamily = new FontFamily(family.lang, family.variant);
264        for (FontListParser.Font font : family.fonts) {
265            fontFamily.addFontWeightStyle(font.fontName, 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                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                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);
328        } catch (IOException e) {
329            Log.e(TAG, "Error reading " + configFilename);
330        } catch (XmlPullParserException e) {
331            Log.e(TAG, "XML parse exception for " + configFilename);
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        } finally {
362            super.finalize();
363        }
364    }
365
366    @Override
367    public boolean equals(Object o) {
368        if (this == o) return true;
369        if (o == null || getClass() != o.getClass()) return false;
370
371        Typeface typeface = (Typeface) o;
372
373        return mStyle == typeface.mStyle && native_instance == typeface.native_instance;
374    }
375
376    @Override
377    public int hashCode() {
378        /*
379         * Modified method for hashCode with long native_instance derived from
380         * http://developer.android.com/reference/java/lang/Object.html
381         */
382        int result = 17;
383        result = 31 * result + (int) (native_instance ^ (native_instance >>> 32));
384        result = 31 * result + mStyle;
385        return result;
386    }
387
388    private static native long nativeCreateFromTypeface(long native_instance, int style);
389    private static native long nativeCreateWeightAlias(long native_instance, int weight);
390    private static native void nativeUnref(long native_instance);
391    private static native int  nativeGetStyle(long native_instance);
392    private static native long nativeCreateFromArray(long[] familyArray);
393    private static native void nativeSetDefault(long native_instance);
394}
395