• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package android.graphics;
18 
19 import android.content.res.AssetManager;
20 import android.util.Log;
21 import android.util.LongSparseArray;
22 import android.util.LruCache;
23 import android.util.SparseArray;
24 
25 import org.xmlpull.v1.XmlPullParserException;
26 
27 import java.io.File;
28 import java.io.FileInputStream;
29 import java.io.FileNotFoundException;
30 import java.io.IOException;
31 import java.nio.ByteBuffer;
32 import java.nio.channels.FileChannel;
33 import java.util.ArrayList;
34 import java.util.HashMap;
35 import java.util.List;
36 import 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  */
44 public 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 
setDefault(Typeface t)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 */
getStyle()97     public int getStyle() {
98         return mStyle;
99     }
100 
101     /** Returns true if getStyle() has the BOLD bit set. */
isBold()102     public final boolean isBold() {
103         return (mStyle & BOLD) != 0;
104     }
105 
106     /** Returns true if getStyle() has the ITALIC bit set. */
isItalic()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      */
create(String familyName, int style)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      */
create(Typeface family, int style)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      */
defaultFromStyle(int style)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      */
createFromAsset(AssetManager mgr, String path)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[] families = { fontFamily };
200                     typeface = createFromFamiliesWithDefault(families);
201                     sDynamicTypefaceCache.put(key, typeface);
202                     return typeface;
203                 }
204             }
205         }
206         throw new RuntimeException("Font asset not found " + path);
207     }
208 
209     /**
210      * Creates a unique id for a given AssetManager and asset path.
211      *
212      * @param mgr  AssetManager instance
213      * @param path The path for the asset.
214      * @return Unique id for a given AssetManager and asset path.
215      */
createAssetUid(final AssetManager mgr, String path)216     private static String createAssetUid(final AssetManager mgr, String path) {
217         final SparseArray<String> pkgs = mgr.getAssignedPackageIdentifiers();
218         final StringBuilder builder = new StringBuilder();
219         final int size = pkgs.size();
220         for (int i = 0; i < size; i++) {
221             builder.append(pkgs.valueAt(i));
222             builder.append("-");
223         }
224         builder.append(path);
225         return builder.toString();
226     }
227 
228     /**
229      * Create a new typeface from the specified font file.
230      *
231      * @param path The path to the font data.
232      * @return The new typeface.
233      */
createFromFile(File path)234     public static Typeface createFromFile(File path) {
235         return createFromFile(path.getAbsolutePath());
236     }
237 
238     /**
239      * Create a new typeface from the specified font file.
240      *
241      * @param path The full path to the font data.
242      * @return The new typeface.
243      */
createFromFile(String path)244     public static Typeface createFromFile(String path) {
245         if (sFallbackFonts != null) {
246             FontFamily fontFamily = new FontFamily();
247             if (fontFamily.addFont(path, 0 /* ttcIndex */)) {
248                 FontFamily[] families = { fontFamily };
249                 return createFromFamiliesWithDefault(families);
250             }
251         }
252         throw new RuntimeException("Font not found " + path);
253     }
254 
255     /**
256      * Create a new typeface from an array of font families.
257      *
258      * @param families array of font families
259      * @hide
260      */
createFromFamilies(FontFamily[] families)261     public static Typeface createFromFamilies(FontFamily[] families) {
262         long[] ptrArray = new long[families.length];
263         for (int i = 0; i < families.length; i++) {
264             ptrArray[i] = families[i].mNativePtr;
265         }
266         return new Typeface(nativeCreateFromArray(ptrArray));
267     }
268 
269     /**
270      * Create a new typeface from an array of font families, including
271      * also the font families in the fallback list.
272      *
273      * @param families array of font families
274      * @hide
275      */
createFromFamiliesWithDefault(FontFamily[] families)276     public static Typeface createFromFamiliesWithDefault(FontFamily[] families) {
277         long[] ptrArray = new long[families.length + sFallbackFonts.length];
278         for (int i = 0; i < families.length; i++) {
279             ptrArray[i] = families[i].mNativePtr;
280         }
281         for (int i = 0; i < sFallbackFonts.length; i++) {
282             ptrArray[i + families.length] = sFallbackFonts[i].mNativePtr;
283         }
284         return new Typeface(nativeCreateFromArray(ptrArray));
285     }
286 
287     // don't allow clients to call this directly
Typeface(long ni)288     private Typeface(long ni) {
289         if (ni == 0) {
290             throw new RuntimeException("native typeface cannot be made");
291         }
292 
293         native_instance = ni;
294         mStyle = nativeGetStyle(ni);
295     }
296 
makeFamilyFromParsed(FontListParser.Family family, Map<String, ByteBuffer> bufferForPath)297     private static FontFamily makeFamilyFromParsed(FontListParser.Family family,
298             Map<String, ByteBuffer> bufferForPath) {
299         FontFamily fontFamily = new FontFamily(family.lang, family.variant);
300         for (FontListParser.Font font : family.fonts) {
301             ByteBuffer fontBuffer = bufferForPath.get(font.fontName);
302             if (fontBuffer == null) {
303                 try (FileInputStream file = new FileInputStream(font.fontName)) {
304                     FileChannel fileChannel = file.getChannel();
305                     long fontSize = fileChannel.size();
306                     fontBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fontSize);
307                     bufferForPath.put(font.fontName, fontBuffer);
308                 } catch (IOException e) {
309                     Log.e(TAG, "Error mapping font file " + font.fontName);
310                     continue;
311                 }
312             }
313             if (!fontFamily.addFontWeightStyle(fontBuffer, font.ttcIndex, font.axes,
314                     font.weight, font.isItalic)) {
315                 Log.e(TAG, "Error creating font " + font.fontName + "#" + font.ttcIndex);
316             }
317         }
318         return fontFamily;
319     }
320 
321     /*
322      * (non-Javadoc)
323      *
324      * This should only be called once, from the static class initializer block.
325      */
init()326     private static void init() {
327         // Load font config and initialize Minikin state
328         File systemFontConfigLocation = getSystemFontConfigLocation();
329         File configFilename = new File(systemFontConfigLocation, FONTS_CONFIG);
330         try {
331             FileInputStream fontsIn = new FileInputStream(configFilename);
332             FontListParser.Config fontConfig = FontListParser.parse(fontsIn);
333 
334             Map<String, ByteBuffer> bufferForPath = new HashMap<String, ByteBuffer>();
335 
336             List<FontFamily> familyList = new ArrayList<FontFamily>();
337             // Note that the default typeface is always present in the fallback list;
338             // this is an enhancement from pre-Minikin behavior.
339             for (int i = 0; i < fontConfig.families.size(); i++) {
340                 FontListParser.Family f = fontConfig.families.get(i);
341                 if (i == 0 || f.name == null) {
342                     familyList.add(makeFamilyFromParsed(f, bufferForPath));
343                 }
344             }
345             sFallbackFonts = familyList.toArray(new FontFamily[familyList.size()]);
346             setDefault(Typeface.createFromFamilies(sFallbackFonts));
347 
348             Map<String, Typeface> systemFonts = new HashMap<String, Typeface>();
349             for (int i = 0; i < fontConfig.families.size(); i++) {
350                 Typeface typeface;
351                 FontListParser.Family f = fontConfig.families.get(i);
352                 if (f.name != null) {
353                     if (i == 0) {
354                         // The first entry is the default typeface; no sense in
355                         // duplicating the corresponding FontFamily.
356                         typeface = sDefaultTypeface;
357                     } else {
358                         FontFamily fontFamily = makeFamilyFromParsed(f, bufferForPath);
359                         FontFamily[] families = { fontFamily };
360                         typeface = Typeface.createFromFamiliesWithDefault(families);
361                     }
362                     systemFonts.put(f.name, typeface);
363                 }
364             }
365             for (FontListParser.Alias alias : fontConfig.aliases) {
366                 Typeface base = systemFonts.get(alias.toName);
367                 Typeface newFace = base;
368                 int weight = alias.weight;
369                 if (weight != 400) {
370                     newFace = new Typeface(nativeCreateWeightAlias(base.native_instance, weight));
371                 }
372                 systemFonts.put(alias.name, newFace);
373             }
374             sSystemFontMap = systemFonts;
375 
376         } catch (RuntimeException e) {
377             Log.w(TAG, "Didn't create default family (most likely, non-Minikin build)", e);
378             // TODO: normal in non-Minikin case, remove or make error when Minikin-only
379         } catch (FileNotFoundException e) {
380             Log.e(TAG, "Error opening " + configFilename, e);
381         } catch (IOException e) {
382             Log.e(TAG, "Error reading " + configFilename, e);
383         } catch (XmlPullParserException e) {
384             Log.e(TAG, "XML parse exception for " + configFilename, e);
385         }
386     }
387 
388     static {
init()389         init();
390         // Set up defaults and typefaces exposed in public API
391         DEFAULT         = create((String) null, 0);
392         DEFAULT_BOLD    = create((String) null, Typeface.BOLD);
393         SANS_SERIF      = create("sans-serif", 0);
394         SERIF           = create("serif", 0);
395         MONOSPACE       = create("monospace", 0);
396 
397         sDefaults = new Typeface[] {
398             DEFAULT,
399             DEFAULT_BOLD,
400             create((String) null, Typeface.ITALIC),
401             create((String) null, Typeface.BOLD_ITALIC),
402         };
403 
404     }
405 
getSystemFontConfigLocation()406     private static File getSystemFontConfigLocation() {
407         return new File("/system/etc/");
408     }
409 
410     @Override
finalize()411     protected void finalize() throws Throwable {
412         try {
413             nativeUnref(native_instance);
414             native_instance = 0;  // Other finalizers can still call us.
415         } finally {
416             super.finalize();
417         }
418     }
419 
420     @Override
equals(Object o)421     public boolean equals(Object o) {
422         if (this == o) return true;
423         if (o == null || getClass() != o.getClass()) return false;
424 
425         Typeface typeface = (Typeface) o;
426 
427         return mStyle == typeface.mStyle && native_instance == typeface.native_instance;
428     }
429 
430     @Override
hashCode()431     public int hashCode() {
432         /*
433          * Modified method for hashCode with long native_instance derived from
434          * http://developer.android.com/reference/java/lang/Object.html
435          */
436         int result = 17;
437         result = 31 * result + (int) (native_instance ^ (native_instance >>> 32));
438         result = 31 * result + mStyle;
439         return result;
440     }
441 
nativeCreateFromTypeface(long native_instance, int style)442     private static native long nativeCreateFromTypeface(long native_instance, int style);
nativeCreateWeightAlias(long native_instance, int weight)443     private static native long nativeCreateWeightAlias(long native_instance, int weight);
nativeUnref(long native_instance)444     private static native void nativeUnref(long native_instance);
nativeGetStyle(long native_instance)445     private static native int  nativeGetStyle(long native_instance);
nativeCreateFromArray(long[] familyArray)446     private static native long nativeCreateFromArray(long[] familyArray);
nativeSetDefault(long native_instance)447     private static native void nativeSetDefault(long native_instance);
448 }
449