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