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