Typeface.java revision ac873c9f25d2a687c9195226b9d680f51c91fa30
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 static android.content.res.FontResourcesParser.ProviderResourceEntry; 20import static android.content.res.FontResourcesParser.FontFileResourceEntry; 21import static android.content.res.FontResourcesParser.FontFamilyFilesResourceEntry; 22import static android.content.res.FontResourcesParser.FamilyResourceEntry; 23 24import android.annotation.IntDef; 25import android.annotation.NonNull; 26import android.annotation.Nullable; 27import android.content.res.AssetManager; 28import android.graphics.fonts.FontRequest; 29import android.graphics.fonts.FontResult; 30import android.os.Bundle; 31import android.os.Handler; 32import android.os.ParcelFileDescriptor; 33import android.os.ResultReceiver; 34import android.provider.FontsContract; 35import android.text.FontConfig; 36import android.util.Log; 37import android.util.LongSparseArray; 38import android.util.LruCache; 39import android.util.SparseArray; 40 41import com.android.internal.annotations.GuardedBy; 42 43import libcore.io.IoUtils; 44 45import org.xmlpull.v1.XmlPullParserException; 46 47import java.io.File; 48import java.io.FileInputStream; 49import java.io.FileNotFoundException; 50import java.io.IOException; 51import java.lang.annotation.Retention; 52import java.lang.annotation.RetentionPolicy; 53import java.nio.ByteBuffer; 54import java.nio.channels.FileChannel; 55import java.util.ArrayList; 56import java.util.HashMap; 57import java.util.List; 58import java.util.Map; 59 60/** 61 * The Typeface class specifies the typeface and intrinsic style of a font. 62 * This is used in the paint, along with optionally Paint settings like 63 * textSize, textSkewX, textScaleX to specify 64 * how text appears when drawn (and measured). 65 */ 66public class Typeface { 67 68 private static String TAG = "Typeface"; 69 70 /** The default NORMAL typeface object */ 71 public static final Typeface DEFAULT; 72 /** 73 * The default BOLD typeface object. Note: this may be not actually be 74 * bold, depending on what fonts are installed. Call getStyle() to know 75 * for sure. 76 */ 77 public static final Typeface DEFAULT_BOLD; 78 /** The NORMAL style of the default sans serif typeface. */ 79 public static final Typeface SANS_SERIF; 80 /** The NORMAL style of the default serif typeface. */ 81 public static final Typeface SERIF; 82 /** The NORMAL style of the default monospace typeface. */ 83 public static final Typeface MONOSPACE; 84 85 static Typeface[] sDefaults; 86 private static final LongSparseArray<SparseArray<Typeface>> sTypefaceCache = 87 new LongSparseArray<>(3); 88 @GuardedBy("sLock") 89 private static FontsContract sFontsContract; 90 @GuardedBy("sLock") 91 private static Handler mHandler; 92 93 /** 94 * Cache for Typeface objects dynamically loaded from assets. Currently max size is 16. 95 */ 96 private static final LruCache<String, Typeface> sDynamicTypefaceCache = new LruCache<>(16); 97 98 static Typeface sDefaultTypeface; 99 static Map<String, Typeface> sSystemFontMap; 100 static FontFamily[] sFallbackFonts; 101 private static final Object sLock = new Object(); 102 103 static final String FONTS_CONFIG = "fonts.xml"; 104 105 /** 106 * @hide 107 */ 108 public long native_instance; 109 110 // Style 111 public static final int NORMAL = 0; 112 public static final int BOLD = 1; 113 public static final int ITALIC = 2; 114 public static final int BOLD_ITALIC = 3; 115 116 private int mStyle = 0; 117 118 private static void setDefault(Typeface t) { 119 sDefaultTypeface = t; 120 nativeSetDefault(t.native_instance); 121 } 122 123 /** Returns the typeface's intrinsic style attributes */ 124 public int getStyle() { 125 return mStyle; 126 } 127 128 /** Returns true if getStyle() has the BOLD bit set. */ 129 public final boolean isBold() { 130 return (mStyle & BOLD) != 0; 131 } 132 133 /** Returns true if getStyle() has the ITALIC bit set. */ 134 public final boolean isItalic() { 135 return (mStyle & ITALIC) != 0; 136 } 137 138 /** 139 * @hide 140 * Used by Resources to load a font resource of type font file. 141 */ 142 @Nullable 143 public static Typeface createFromResources(AssetManager mgr, String path, int cookie) { 144 if (sFallbackFonts != null) { 145 synchronized (sDynamicTypefaceCache) { 146 final String key = createAssetUid(mgr, path); 147 Typeface typeface = sDynamicTypefaceCache.get(key); 148 if (typeface != null) return typeface; 149 150 FontFamily fontFamily = new FontFamily(); 151 if (fontFamily.addFontFromAssetManager(mgr, path, cookie, false /* isAsset */, 152 0 /* use OS/2 table to determine weight and italic */, false)) { 153 fontFamily.freeze(); 154 FontFamily[] families = {fontFamily}; 155 typeface = createFromFamiliesWithDefault(families); 156 sDynamicTypefaceCache.put(key, typeface); 157 return typeface; 158 } 159 } 160 } 161 return null; 162 } 163 164 /** 165 * @hide 166 * Used by Resources to load a font resource of type xml. 167 */ 168 @Nullable 169 public static Typeface createFromResources( 170 FamilyResourceEntry entry, AssetManager mgr, String path) { 171 if (sFallbackFonts != null) { 172 Typeface typeface = findFromCache(mgr, path); 173 if (typeface != null) return typeface; 174 175 if (entry instanceof ProviderResourceEntry) { 176 final ProviderResourceEntry providerEntry = (ProviderResourceEntry) entry; 177 // Downloadable font 178 typeface = findFromCache(providerEntry.getAuthority(), providerEntry.getQuery()); 179 if (typeface != null) { 180 return typeface; 181 } 182 // Downloaded font and it wasn't cached, request it again and return a 183 // default font instead (nothing we can do now). 184 create(new FontRequest(providerEntry.getAuthority(), providerEntry.getPackage(), 185 providerEntry.getQuery()), NO_OP_REQUEST_CALLBACK); 186 return DEFAULT; 187 } 188 189 // family is FontFamilyFilesResourceEntry 190 final FontFamilyFilesResourceEntry filesEntry = 191 (FontFamilyFilesResourceEntry) entry; 192 193 FontFamily fontFamily = new FontFamily(); 194 for (final FontFileResourceEntry fontFile : filesEntry.getEntries()) { 195 if (!fontFamily.addFontFromAssetManager(mgr, fontFile.getFileName(), 196 0 /* resourceCookie */, false /* isAsset */, fontFile.getWeight(), 197 fontFile.isItalic())) { 198 return null; 199 } 200 } 201 fontFamily.freeze(); 202 FontFamily[] familyChain = { fontFamily }; 203 typeface = createFromFamiliesWithDefault(familyChain); 204 synchronized (sDynamicTypefaceCache) { 205 final String key = createAssetUid(mgr, path); 206 sDynamicTypefaceCache.put(key, typeface); 207 } 208 return typeface; 209 } 210 return null; 211 } 212 213 /** 214 * Used by resources for cached loading if the font is available. 215 * @hide 216 */ 217 public static Typeface findFromCache(AssetManager mgr, String path) { 218 synchronized (sDynamicTypefaceCache) { 219 final String key = createAssetUid(mgr, path); 220 Typeface typeface = sDynamicTypefaceCache.get(key); 221 if (typeface != null) { 222 return typeface; 223 } 224 } 225 return null; 226 } 227 228 /** 229 * Create a typeface object given a font request. The font will be asynchronously fetched, 230 * therefore the result is delivered to the given callback. See {@link FontRequest}. 231 * Only one of the methods in callback will be invoked, depending on whether the request 232 * succeeds or fails. These calls will happen on the main thread. 233 * @param request A {@link FontRequest} object that identifies the provider and query for the 234 * request. May not be null. 235 * @param callback A callback that will be triggered when results are obtained. May not be null. 236 */ 237 public static void create(@NonNull FontRequest request, @NonNull FontRequestCallback callback) { 238 // Check the cache first 239 // TODO: would the developer want to avoid a cache hit and always ask for the freshest 240 // result? 241 Typeface cachedTypeface = findFromCache( 242 request.getProviderAuthority(), request.getQuery()); 243 if (cachedTypeface != null) { 244 mHandler.post(() -> callback.onTypefaceRetrieved(cachedTypeface)); 245 return; 246 } 247 synchronized (sLock) { 248 if (sFontsContract == null) { 249 sFontsContract = new FontsContract(); 250 mHandler = new Handler(); 251 } 252 final ResultReceiver receiver = new ResultReceiver(null) { 253 @Override 254 public void onReceiveResult(int resultCode, Bundle resultData) { 255 mHandler.post(() -> receiveResult(request, callback, resultCode, resultData)); 256 } 257 }; 258 sFontsContract.getFont(request, receiver); 259 } 260 } 261 262 private static Typeface findFromCache(String providerAuthority, String query) { 263 synchronized (sDynamicTypefaceCache) { 264 final String key = createProviderUid(providerAuthority, query); 265 Typeface typeface = sDynamicTypefaceCache.get(key); 266 if (typeface != null) { 267 return typeface; 268 } 269 } 270 return null; 271 } 272 273 private static void receiveResult(FontRequest request, FontRequestCallback callback, 274 int resultCode, Bundle resultData) { 275 Typeface cachedTypeface = findFromCache( 276 request.getProviderAuthority(), request.getQuery()); 277 if (cachedTypeface != null) { 278 // We already know the result. 279 // Probably the requester requests the same font again in a short interval. 280 callback.onTypefaceRetrieved(cachedTypeface); 281 return; 282 } 283 if (resultCode != FontsContract.Columns.RESULT_CODE_OK) { 284 callback.onTypefaceRequestFailed(resultCode); 285 return; 286 } 287 if (resultData == null) { 288 callback.onTypefaceRequestFailed( 289 FontRequestCallback.FAIL_REASON_FONT_NOT_FOUND); 290 return; 291 } 292 List<FontResult> resultList = 293 resultData.getParcelableArrayList(FontsContract.PARCEL_FONT_RESULTS); 294 if (resultList == null || resultList.isEmpty()) { 295 callback.onTypefaceRequestFailed( 296 FontRequestCallback.FAIL_REASON_FONT_NOT_FOUND); 297 return; 298 } 299 FontFamily fontFamily = new FontFamily(); 300 for (int i = 0; i < resultList.size(); ++i) { 301 FontResult result = resultList.get(i); 302 ParcelFileDescriptor fd = result.getFileDescriptor(); 303 if (fd == null) { 304 callback.onTypefaceRequestFailed( 305 FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR); 306 return; 307 } 308 try (FileInputStream is = new FileInputStream(fd.getFileDescriptor())) { 309 FileChannel fileChannel = is.getChannel(); 310 long fontSize = fileChannel.size(); 311 ByteBuffer fontBuffer = fileChannel.map( 312 FileChannel.MapMode.READ_ONLY, 0, fontSize); 313 int style = result.getStyle(); 314 int weight = (style & BOLD) != 0 ? 700 : 400; 315 // TODO: this method should be 316 // create(fd, ttcIndex, fontVariationSettings, style). 317 if (!fontFamily.addFontWeightStyle(fontBuffer, result.getTtcIndex(), 318 null, weight, (style & ITALIC) != 0)) { 319 Log.e(TAG, "Error creating font " + request.getQuery()); 320 callback.onTypefaceRequestFailed( 321 FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR); 322 return; 323 } 324 } catch (IOException e) { 325 Log.e(TAG, "Error reading font " + request.getQuery(), e); 326 callback.onTypefaceRequestFailed( 327 FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR); 328 return; 329 } finally { 330 IoUtils.closeQuietly(fd); 331 } 332 } 333 fontFamily.freeze(); 334 Typeface typeface = Typeface.createFromFamiliesWithDefault(new FontFamily[] { fontFamily }); 335 synchronized (sDynamicTypefaceCache) { 336 String key = createProviderUid(request.getProviderAuthority(), request.getQuery()); 337 sDynamicTypefaceCache.put(key, typeface); 338 } 339 callback.onTypefaceRetrieved(typeface); 340 } 341 342 /** 343 * Interface used to receive asynchronously fetched typefaces. 344 */ 345 public interface FontRequestCallback { 346 /** 347 * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the given 348 * provider was not found on the device. 349 */ 350 int FAIL_REASON_PROVIDER_NOT_FOUND = FontsContract.RESULT_CODE_PROVIDER_NOT_FOUND; 351 /** 352 * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the given 353 * provider must be authenticated and the given certificates do not match its signature. 354 */ 355 int FAIL_REASON_WRONG_CERTIFICATES = FontsContract.RESULT_CODE_WRONG_CERTIFICATES; 356 /** 357 * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the font 358 * returned by the provider was not loaded properly. 359 */ 360 int FAIL_REASON_FONT_LOAD_ERROR = -3; 361 /** 362 * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the font 363 * provider did not return any results for the given query. 364 */ 365 int FAIL_REASON_FONT_NOT_FOUND = FontsContract.Columns.RESULT_CODE_FONT_NOT_FOUND; 366 /** 367 * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the font 368 * provider found the queried font, but it is currently unavailable. 369 */ 370 int FAIL_REASON_FONT_UNAVAILABLE = FontsContract.Columns.RESULT_CODE_FONT_UNAVAILABLE; 371 /** 372 * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the given 373 * query was not supported by the provider. 374 */ 375 int FAIL_REASON_MALFORMED_QUERY = FontsContract.Columns.RESULT_CODE_MALFORMED_QUERY; 376 377 /** @hide */ 378 @IntDef({ FAIL_REASON_PROVIDER_NOT_FOUND, FAIL_REASON_FONT_LOAD_ERROR, 379 FAIL_REASON_FONT_NOT_FOUND, FAIL_REASON_FONT_UNAVAILABLE, 380 FAIL_REASON_MALFORMED_QUERY }) 381 @Retention(RetentionPolicy.SOURCE) 382 @interface FontRequestFailReason {} 383 384 /** 385 * Called then a Typeface request done via {@link Typeface#create(FontRequest, 386 * FontRequestCallback)} is complete. Note that this method will not be called if 387 * {@link #onTypefaceRequestFailed(int)} is called instead. 388 * @param typeface The Typeface object retrieved. 389 */ 390 void onTypefaceRetrieved(Typeface typeface); 391 392 /** 393 * Called when a Typeface request done via {@link Typeface#create(FontRequest, 394 * FontRequestCallback)} fails. 395 * @param reason One of {@link #FAIL_REASON_PROVIDER_NOT_FOUND}, 396 * {@link #FAIL_REASON_FONT_NOT_FOUND}, 397 * {@link #FAIL_REASON_FONT_LOAD_ERROR}, 398 * {@link #FAIL_REASON_FONT_UNAVAILABLE} or 399 * {@link #FAIL_REASON_MALFORMED_QUERY}. 400 */ 401 void onTypefaceRequestFailed(@FontRequestFailReason int reason); 402 } 403 404 private static final FontRequestCallback NO_OP_REQUEST_CALLBACK = new FontRequestCallback() { 405 @Override 406 public void onTypefaceRetrieved(Typeface typeface) { 407 // Do nothing. 408 } 409 410 @Override 411 public void onTypefaceRequestFailed(@FontRequestFailReason int reason) { 412 // Do nothing. 413 } 414 }; 415 416 /** 417 * Create a typeface object given a family name, and option style information. 418 * If null is passed for the name, then the "default" font will be chosen. 419 * The resulting typeface object can be queried (getStyle()) to discover what 420 * its "real" style characteristics are. 421 * 422 * @param familyName May be null. The name of the font family. 423 * @param style The style (normal, bold, italic) of the typeface. 424 * e.g. NORMAL, BOLD, ITALIC, BOLD_ITALIC 425 * @return The best matching typeface. 426 */ 427 public static Typeface create(String familyName, int style) { 428 if (sSystemFontMap != null) { 429 return create(sSystemFontMap.get(familyName), style); 430 } 431 return null; 432 } 433 434 /** 435 * Create a typeface object that best matches the specified existing 436 * typeface and the specified Style. Use this call if you want to pick a new 437 * style from the same family of an existing typeface object. If family is 438 * null, this selects from the default font's family. 439 * 440 * @param family May be null. The name of the existing type face. 441 * @param style The style (normal, bold, italic) of the typeface. 442 * e.g. NORMAL, BOLD, ITALIC, BOLD_ITALIC 443 * @return The best matching typeface. 444 */ 445 public static Typeface create(Typeface family, int style) { 446 if (style < 0 || style > 3) { 447 style = 0; 448 } 449 long ni = 0; 450 if (family != null) { 451 // Return early if we're asked for the same face/style 452 if (family.mStyle == style) { 453 return family; 454 } 455 456 ni = family.native_instance; 457 } 458 459 Typeface typeface; 460 SparseArray<Typeface> styles = sTypefaceCache.get(ni); 461 462 if (styles != null) { 463 typeface = styles.get(style); 464 if (typeface != null) { 465 return typeface; 466 } 467 } 468 469 typeface = new Typeface(nativeCreateFromTypeface(ni, style)); 470 if (styles == null) { 471 styles = new SparseArray<Typeface>(4); 472 sTypefaceCache.put(ni, styles); 473 } 474 styles.put(style, typeface); 475 476 return typeface; 477 } 478 479 /** @hide */ 480 public static Typeface createFromTypefaceWithVariation(Typeface family, 481 String fontVariationSettings) { 482 final long ni = family == null ? 0 : family.native_instance; 483 ArrayList<FontConfig.Axis> axes = 484 FontListParser.parseFontVariationSettings(fontVariationSettings); 485 return new Typeface(nativeCreateFromTypefaceWithVariation(ni, axes)); 486 } 487 488 /** 489 * Returns one of the default typeface objects, based on the specified style 490 * 491 * @return the default typeface that corresponds to the style 492 */ 493 public static Typeface defaultFromStyle(int style) { 494 return sDefaults[style]; 495 } 496 497 /** 498 * Create a new typeface from the specified font data. 499 * 500 * @param mgr The application's asset manager 501 * @param path The file name of the font data in the assets directory 502 * @return The new typeface. 503 */ 504 public static Typeface createFromAsset(AssetManager mgr, String path) { 505 if (sFallbackFonts != null) { 506 synchronized (sDynamicTypefaceCache) { 507 final String key = createAssetUid(mgr, path); 508 Typeface typeface = sDynamicTypefaceCache.get(key); 509 if (typeface != null) return typeface; 510 511 FontFamily fontFamily = new FontFamily(); 512 if (fontFamily.addFontFromAssetManager(mgr, path, 0, true /* isAsset */, 513 0 /* use OS/2 table to determine weight and italic */, false)) { 514 fontFamily.freeze(); 515 FontFamily[] families = { fontFamily }; 516 typeface = createFromFamiliesWithDefault(families); 517 sDynamicTypefaceCache.put(key, typeface); 518 return typeface; 519 } else { 520 fontFamily.abortCreation(); 521 } 522 } 523 } 524 throw new RuntimeException("Font asset not found " + path); 525 } 526 527 /** 528 * Creates a unique id for a given AssetManager and asset path. 529 * 530 * @param mgr AssetManager instance 531 * @param path The path for the asset. 532 * @return Unique id for a given AssetManager and asset path. 533 */ 534 private static String createAssetUid(final AssetManager mgr, String path) { 535 final SparseArray<String> pkgs = mgr.getAssignedPackageIdentifiers(); 536 final StringBuilder builder = new StringBuilder(); 537 builder.append("asset:"); 538 final int size = pkgs.size(); 539 for (int i = 0; i < size; i++) { 540 builder.append(pkgs.valueAt(i)); 541 builder.append("-"); 542 } 543 builder.append(path); 544 return builder.toString(); 545 } 546 547 /** 548 * Creates a unique id for a given font provider and query. 549 */ 550 private static String createProviderUid(String authority, String query) { 551 final StringBuilder builder = new StringBuilder(); 552 builder.append("provider:"); 553 builder.append(authority); 554 builder.append("-"); 555 builder.append(query); 556 return builder.toString(); 557 } 558 559 /** 560 * Create a new typeface from the specified font file. 561 * 562 * @param path The path to the font data. 563 * @return The new typeface. 564 */ 565 public static Typeface createFromFile(File path) { 566 return createFromFile(path.getAbsolutePath()); 567 } 568 569 /** 570 * Create a new typeface from the specified font file. 571 * 572 * @param path The full path to the font data. 573 * @return The new typeface. 574 */ 575 public static Typeface createFromFile(String path) { 576 if (sFallbackFonts != null) { 577 FontFamily fontFamily = new FontFamily(); 578 if (fontFamily.addFont(path, 0 /* ttcIndex */)) { 579 fontFamily.freeze(); 580 FontFamily[] families = { fontFamily }; 581 return createFromFamiliesWithDefault(families); 582 } else { 583 fontFamily.abortCreation(); 584 } 585 } 586 throw new RuntimeException("Font not found " + path); 587 } 588 589 /** 590 * Create a new typeface from an array of font families. 591 * 592 * @param families array of font families 593 * @hide 594 */ 595 public static Typeface createFromFamilies(FontFamily[] families) { 596 long[] ptrArray = new long[families.length]; 597 for (int i = 0; i < families.length; i++) { 598 ptrArray[i] = families[i].mNativePtr; 599 } 600 return new Typeface(nativeCreateFromArray(ptrArray)); 601 } 602 603 /** 604 * Create a new typeface from an array of font families, including 605 * also the font families in the fallback list. 606 * 607 * @param families array of font families 608 * @hide 609 */ 610 public static Typeface createFromFamiliesWithDefault(FontFamily[] families) { 611 long[] ptrArray = new long[families.length + sFallbackFonts.length]; 612 for (int i = 0; i < families.length; i++) { 613 ptrArray[i] = families[i].mNativePtr; 614 } 615 for (int i = 0; i < sFallbackFonts.length; i++) { 616 ptrArray[i + families.length] = sFallbackFonts[i].mNativePtr; 617 } 618 return new Typeface(nativeCreateFromArray(ptrArray)); 619 } 620 621 // don't allow clients to call this directly 622 private Typeface(long ni) { 623 if (ni == 0) { 624 throw new RuntimeException("native typeface cannot be made"); 625 } 626 627 native_instance = ni; 628 mStyle = nativeGetStyle(ni); 629 } 630 631 private static FontFamily makeFamilyFromParsed(FontConfig.Family family, 632 Map<String, ByteBuffer> bufferForPath) { 633 FontFamily fontFamily = new FontFamily(family.getLanguage(), family.getVariant()); 634 for (FontConfig.Font font : family.getFonts()) { 635 ByteBuffer fontBuffer = bufferForPath.get(font.getFontName()); 636 if (fontBuffer == null) { 637 try (FileInputStream file = new FileInputStream(font.getFontName())) { 638 FileChannel fileChannel = file.getChannel(); 639 long fontSize = fileChannel.size(); 640 fontBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fontSize); 641 bufferForPath.put(font.getFontName(), fontBuffer); 642 } catch (IOException e) { 643 Log.e(TAG, "Error mapping font file " + font.getFontName()); 644 continue; 645 } 646 } 647 if (!fontFamily.addFontWeightStyle(fontBuffer, font.getTtcIndex(), font.getAxes(), 648 font.getWeight(), font.isItalic())) { 649 Log.e(TAG, "Error creating font " + font.getFontName() + "#" + font.getTtcIndex()); 650 } 651 } 652 fontFamily.freeze(); 653 return fontFamily; 654 } 655 656 /* 657 * (non-Javadoc) 658 * 659 * This should only be called once, from the static class initializer block. 660 */ 661 private static void init() { 662 // Load font config and initialize Minikin state 663 File systemFontConfigLocation = getSystemFontConfigLocation(); 664 File configFilename = new File(systemFontConfigLocation, FONTS_CONFIG); 665 try { 666 FileInputStream fontsIn = new FileInputStream(configFilename); 667 FontConfig fontConfig = FontListParser.parse(fontsIn); 668 669 Map<String, ByteBuffer> bufferForPath = new HashMap<String, ByteBuffer>(); 670 671 List<FontFamily> familyList = new ArrayList<FontFamily>(); 672 // Note that the default typeface is always present in the fallback list; 673 // this is an enhancement from pre-Minikin behavior. 674 for (int i = 0; i < fontConfig.getFamilies().length; i++) { 675 FontConfig.Family f = fontConfig.getFamilies()[i]; 676 if (i == 0 || f.getName() == null) { 677 familyList.add(makeFamilyFromParsed(f, bufferForPath)); 678 } 679 } 680 sFallbackFonts = familyList.toArray(new FontFamily[familyList.size()]); 681 setDefault(Typeface.createFromFamilies(sFallbackFonts)); 682 683 Map<String, Typeface> systemFonts = new HashMap<String, Typeface>(); 684 for (int i = 0; i < fontConfig.getFamilies().length; i++) { 685 Typeface typeface; 686 FontConfig.Family f = fontConfig.getFamilies()[i]; 687 if (f.getName() != null) { 688 if (i == 0) { 689 // The first entry is the default typeface; no sense in 690 // duplicating the corresponding FontFamily. 691 typeface = sDefaultTypeface; 692 } else { 693 FontFamily fontFamily = makeFamilyFromParsed(f, bufferForPath); 694 FontFamily[] families = { fontFamily }; 695 typeface = Typeface.createFromFamiliesWithDefault(families); 696 } 697 systemFonts.put(f.getName(), typeface); 698 } 699 } 700 for (FontConfig.Alias alias : fontConfig.getAliases()) { 701 Typeface base = systemFonts.get(alias.getToName()); 702 Typeface newFace = base; 703 int weight = alias.getWeight(); 704 if (weight != 400) { 705 newFace = new Typeface(nativeCreateWeightAlias(base.native_instance, weight)); 706 } 707 systemFonts.put(alias.getName(), newFace); 708 } 709 sSystemFontMap = systemFonts; 710 711 } catch (RuntimeException e) { 712 Log.w(TAG, "Didn't create default family (most likely, non-Minikin build)", e); 713 // TODO: normal in non-Minikin case, remove or make error when Minikin-only 714 } catch (FileNotFoundException e) { 715 Log.e(TAG, "Error opening " + configFilename, e); 716 } catch (IOException e) { 717 Log.e(TAG, "Error reading " + configFilename, e); 718 } catch (XmlPullParserException e) { 719 Log.e(TAG, "XML parse exception for " + configFilename, e); 720 } 721 } 722 723 static { 724 init(); 725 // Set up defaults and typefaces exposed in public API 726 DEFAULT = create((String) null, 0); 727 DEFAULT_BOLD = create((String) null, Typeface.BOLD); 728 SANS_SERIF = create("sans-serif", 0); 729 SERIF = create("serif", 0); 730 MONOSPACE = create("monospace", 0); 731 732 sDefaults = new Typeface[] { 733 DEFAULT, 734 DEFAULT_BOLD, 735 create((String) null, Typeface.ITALIC), 736 create((String) null, Typeface.BOLD_ITALIC), 737 }; 738 739 } 740 741 private static File getSystemFontConfigLocation() { 742 return new File("/system/etc/"); 743 } 744 745 @Override 746 protected void finalize() throws Throwable { 747 try { 748 nativeUnref(native_instance); 749 native_instance = 0; // Other finalizers can still call us. 750 } finally { 751 super.finalize(); 752 } 753 } 754 755 @Override 756 public boolean equals(Object o) { 757 if (this == o) return true; 758 if (o == null || getClass() != o.getClass()) return false; 759 760 Typeface typeface = (Typeface) o; 761 762 return mStyle == typeface.mStyle && native_instance == typeface.native_instance; 763 } 764 765 @Override 766 public int hashCode() { 767 /* 768 * Modified method for hashCode with long native_instance derived from 769 * http://developer.android.com/reference/java/lang/Object.html 770 */ 771 int result = 17; 772 result = 31 * result + (int) (native_instance ^ (native_instance >>> 32)); 773 result = 31 * result + mStyle; 774 return result; 775 } 776 777 private static native long nativeCreateFromTypeface(long native_instance, int style); 778 private static native long nativeCreateFromTypefaceWithVariation( 779 long native_instance, List<FontConfig.Axis> axes); 780 private static native long nativeCreateWeightAlias(long native_instance, int weight); 781 private static native void nativeUnref(long native_instance); 782 private static native int nativeGetStyle(long native_instance); 783 private static native long nativeCreateFromArray(long[] familyArray); 784 private static native void nativeSetDefault(long native_instance); 785} 786