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