Typeface.java revision 5421e810233ecadf0ebc7d7760dc9ac07a141e6c
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 static java.lang.annotation.RetentionPolicy.SOURCE; 25 26import android.annotation.IntDef; 27import android.annotation.IntRange; 28import android.annotation.NonNull; 29import android.annotation.Nullable; 30import android.content.Context; 31import android.content.res.AssetManager; 32import android.graphics.FontListParser; 33import android.graphics.fonts.FontRequest; 34import android.graphics.fonts.FontResult; 35import android.graphics.fonts.FontVariationAxis; 36import android.graphics.fonts.FontVariationAxis.InvalidFormatException; 37import android.os.Bundle; 38import android.os.Handler; 39import android.os.ParcelFileDescriptor; 40import android.os.ResultReceiver; 41import android.provider.FontsContract; 42import android.text.FontConfig; 43import android.util.Base64; 44import android.util.Log; 45import android.util.LongSparseArray; 46import android.util.LruCache; 47import android.util.SparseArray; 48 49import com.android.internal.annotations.GuardedBy; 50import com.android.internal.util.Preconditions; 51 52import libcore.io.IoUtils; 53 54import org.xmlpull.v1.XmlPullParserException; 55 56import java.io.File; 57import java.io.FileDescriptor; 58import java.io.FileInputStream; 59import java.io.FileNotFoundException; 60import java.io.IOException; 61import java.lang.annotation.Retention; 62import java.lang.annotation.RetentionPolicy; 63import java.nio.ByteBuffer; 64import java.nio.channels.FileChannel; 65import java.util.Arrays; 66import java.util.ArrayList; 67import java.util.Arrays; 68import java.util.Collections; 69import java.util.HashMap; 70import java.util.List; 71import java.util.Map; 72import java.util.concurrent.atomic.AtomicReference; 73 74/** 75 * The Typeface class specifies the typeface and intrinsic style of a font. 76 * This is used in the paint, along with optionally Paint settings like 77 * textSize, textSkewX, textScaleX to specify 78 * how text appears when drawn (and measured). 79 */ 80public class Typeface { 81 82 private static String TAG = "Typeface"; 83 84 /** The default NORMAL typeface object */ 85 public static final Typeface DEFAULT; 86 /** 87 * The default BOLD typeface object. Note: this may be not actually be 88 * bold, depending on what fonts are installed. Call getStyle() to know 89 * for sure. 90 */ 91 public static final Typeface DEFAULT_BOLD; 92 /** The NORMAL style of the default sans serif typeface. */ 93 public static final Typeface SANS_SERIF; 94 /** The NORMAL style of the default serif typeface. */ 95 public static final Typeface SERIF; 96 /** The NORMAL style of the default monospace typeface. */ 97 public static final Typeface MONOSPACE; 98 99 static Typeface[] sDefaults; 100 private static final LongSparseArray<SparseArray<Typeface>> sTypefaceCache = 101 new LongSparseArray<>(3); 102 @GuardedBy("sLock") 103 private static FontsContract sFontsContract; 104 @GuardedBy("sLock") 105 private static Handler sHandler; 106 107 /** 108 * Cache for Typeface objects dynamically loaded from assets. Currently max size is 16. 109 */ 110 private static final LruCache<String, Typeface> sDynamicTypefaceCache = new LruCache<>(16); 111 112 static Typeface sDefaultTypeface; 113 static Map<String, Typeface> sSystemFontMap; 114 static FontFamily[] sFallbackFonts; 115 private static final Object sLock = new Object(); 116 117 static final String FONTS_CONFIG = "fonts.xml"; 118 119 /** 120 * @hide 121 */ 122 public long native_instance; 123 124 // Style 125 public static final int NORMAL = 0; 126 public static final int BOLD = 1; 127 public static final int ITALIC = 2; 128 public static final int BOLD_ITALIC = 3; 129 130 private int mStyle = 0; 131 132 // Value for weight and italic. Indicates the value is resolved by font metadata. 133 // Must be the same as the C++ constant in core/jni/android/graphics/FontFamily.cpp 134 /** @hide */ 135 public static final int RESOLVE_BY_FONT_TABLE = -1; 136 137 // Style value for building typeface. 138 private static final int STYLE_NORMAL = 0; 139 private static final int STYLE_ITALIC = 1; 140 141 private int[] mSupportedAxes; 142 private static final int[] EMPTY_AXES = {}; 143 144 private static void setDefault(Typeface t) { 145 sDefaultTypeface = t; 146 nativeSetDefault(t.native_instance); 147 } 148 149 /** Returns the typeface's intrinsic style attributes */ 150 public int getStyle() { 151 return mStyle; 152 } 153 154 /** Returns true if getStyle() has the BOLD bit set. */ 155 public final boolean isBold() { 156 return (mStyle & BOLD) != 0; 157 } 158 159 /** Returns true if getStyle() has the ITALIC bit set. */ 160 public final boolean isItalic() { 161 return (mStyle & ITALIC) != 0; 162 } 163 164 /** 165 * @hide 166 * Used by Resources to load a font resource of type font file. 167 */ 168 @Nullable 169 public static Typeface createFromResources(AssetManager mgr, String path, int cookie) { 170 if (sFallbackFonts != null) { 171 synchronized (sDynamicTypefaceCache) { 172 final String key = Builder.createAssetUid( 173 mgr, path, 0 /* ttcIndex */, null /* axes */); 174 Typeface typeface = sDynamicTypefaceCache.get(key); 175 if (typeface != null) return typeface; 176 177 FontFamily fontFamily = new FontFamily(); 178 // TODO: introduce ttc index and variation settings to resource type font. 179 if (fontFamily.addFontFromAssetManager(mgr, path, cookie, false /* isAsset */, 180 0 /* ttcIndex */, RESOLVE_BY_FONT_TABLE /* weight */, 181 RESOLVE_BY_FONT_TABLE /* italic */, null /* axes */)) { 182 fontFamily.freeze(); 183 FontFamily[] families = {fontFamily}; 184 typeface = createFromFamiliesWithDefault(families); 185 sDynamicTypefaceCache.put(key, typeface); 186 return typeface; 187 } 188 } 189 } 190 return null; 191 } 192 193 /** 194 * @hide 195 * Used by Resources to load a font resource of type xml. 196 */ 197 @Nullable 198 public static Typeface createFromResources( 199 FamilyResourceEntry entry, AssetManager mgr, String path) { 200 if (sFallbackFonts != null) { 201 Typeface typeface = findFromCache(mgr, path); 202 if (typeface != null) return typeface; 203 204 if (entry instanceof ProviderResourceEntry) { 205 final ProviderResourceEntry providerEntry = (ProviderResourceEntry) entry; 206 // Downloadable font 207 typeface = findFromCache(providerEntry.getAuthority(), providerEntry.getQuery()); 208 if (typeface != null) { 209 return typeface; 210 } 211 List<List<String>> givenCerts = providerEntry.getCerts(); 212 List<List<byte[]>> certs = new ArrayList<>(); 213 if (givenCerts != null) { 214 for (int i = 0; i < givenCerts.size(); i++) { 215 List<String> certSet = givenCerts.get(i); 216 List<byte[]> byteArraySet = new ArrayList<>(); 217 for (int j = 0; j < certSet.size(); j++) { 218 byteArraySet.add(Base64.decode(certSet.get(j), Base64.DEFAULT)); 219 } 220 certs.add(byteArraySet); 221 } 222 } 223 // Downloaded font and it wasn't cached, request it again and return a 224 // default font instead (nothing we can do now). 225 create(new FontRequest(providerEntry.getAuthority(), providerEntry.getPackage(), 226 providerEntry.getQuery(), certs), NO_OP_REQUEST_CALLBACK); 227 return DEFAULT; 228 } 229 230 // family is FontFamilyFilesResourceEntry 231 final FontFamilyFilesResourceEntry filesEntry = 232 (FontFamilyFilesResourceEntry) entry; 233 234 FontFamily fontFamily = new FontFamily(); 235 for (final FontFileResourceEntry fontFile : filesEntry.getEntries()) { 236 if (!fontFamily.addFontFromAssetManager(mgr, fontFile.getFileName(), 237 0 /* resourceCookie */, false /* isAsset */, 0 /* ttcIndex */, 238 fontFile.getWeight(), fontFile.isItalic() ? STYLE_ITALIC : STYLE_NORMAL, 239 null /* axes */)) { 240 return null; 241 } 242 } 243 fontFamily.freeze(); 244 FontFamily[] familyChain = { fontFamily }; 245 typeface = createFromFamiliesWithDefault(familyChain); 246 synchronized (sDynamicTypefaceCache) { 247 final String key = Builder.createAssetUid(mgr, path, 0 /* ttcIndex */, 248 null /* axes */); 249 sDynamicTypefaceCache.put(key, typeface); 250 } 251 return typeface; 252 } 253 return null; 254 } 255 256 /** 257 * Used by resources for cached loading if the font is available. 258 * @hide 259 */ 260 public static Typeface findFromCache(AssetManager mgr, String path) { 261 synchronized (sDynamicTypefaceCache) { 262 final String key = Builder.createAssetUid(mgr, path, 0 /* ttcIndex */, null /* axes */); 263 Typeface typeface = sDynamicTypefaceCache.get(key); 264 if (typeface != null) { 265 return typeface; 266 } 267 } 268 return null; 269 } 270 271 /** 272 * Set the application context so we can generate font requests from the provider. This should 273 * be called from ActivityThread when the application binds, as we preload fonts. 274 * @hide 275 */ 276 public static void setApplicationContext(Context context) { 277 synchronized (sLock) { 278 if (sFontsContract == null) { 279 sFontsContract = new FontsContract(context); 280 sHandler = new Handler(); 281 } 282 } 283 } 284 285 /** 286 * Create a typeface object given a font request. The font will be asynchronously fetched, 287 * therefore the result is delivered to the given callback. See {@link FontRequest}. 288 * Only one of the methods in callback will be invoked, depending on whether the request 289 * succeeds or fails. These calls will happen on the main thread. 290 * @param request A {@link FontRequest} object that identifies the provider and query for the 291 * request. May not be null. 292 * @param callback A callback that will be triggered when results are obtained. May not be null. 293 */ 294 public static void create(@NonNull FontRequest request, @NonNull FontRequestCallback callback) { 295 // Check the cache first 296 // TODO: would the developer want to avoid a cache hit and always ask for the freshest 297 // result? 298 Typeface cachedTypeface = findFromCache( 299 request.getProviderAuthority(), request.getQuery()); 300 if (cachedTypeface != null) { 301 sHandler.post(() -> callback.onTypefaceRetrieved(cachedTypeface)); 302 return; 303 } 304 synchronized (sLock) { 305 if (sFontsContract == null) { 306 throw new RuntimeException("Context not initialized, can't query provider"); 307 } 308 final ResultReceiver receiver = new ResultReceiver(null) { 309 @Override 310 public void onReceiveResult(int resultCode, Bundle resultData) { 311 sHandler.post(() -> receiveResult(request, callback, resultCode, resultData)); 312 } 313 }; 314 sFontsContract.getFont(request, receiver); 315 } 316 } 317 318 private static Typeface findFromCache(String providerAuthority, String query) { 319 synchronized (sDynamicTypefaceCache) { 320 final String key = createProviderUid(providerAuthority, query); 321 Typeface typeface = sDynamicTypefaceCache.get(key); 322 if (typeface != null) { 323 return typeface; 324 } 325 } 326 return null; 327 } 328 329 private static void receiveResult(FontRequest request, FontRequestCallback callback, 330 int resultCode, Bundle resultData) { 331 Typeface cachedTypeface = findFromCache( 332 request.getProviderAuthority(), request.getQuery()); 333 if (cachedTypeface != null) { 334 // We already know the result. 335 // Probably the requester requests the same font again in a short interval. 336 callback.onTypefaceRetrieved(cachedTypeface); 337 return; 338 } 339 if (resultCode != FontsContract.Columns.RESULT_CODE_OK) { 340 callback.onTypefaceRequestFailed(resultCode); 341 return; 342 } 343 if (resultData == null) { 344 callback.onTypefaceRequestFailed( 345 FontRequestCallback.FAIL_REASON_FONT_NOT_FOUND); 346 return; 347 } 348 List<FontResult> resultList = 349 resultData.getParcelableArrayList(FontsContract.PARCEL_FONT_RESULTS); 350 if (resultList == null || resultList.isEmpty()) { 351 callback.onTypefaceRequestFailed( 352 FontRequestCallback.FAIL_REASON_FONT_NOT_FOUND); 353 return; 354 } 355 FontFamily fontFamily = new FontFamily(); 356 for (int i = 0; i < resultList.size(); ++i) { 357 FontResult result = resultList.get(i); 358 ParcelFileDescriptor fd = result.getFileDescriptor(); 359 if (fd == null) { 360 callback.onTypefaceRequestFailed( 361 FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR); 362 return; 363 } 364 try (FileInputStream is = new FileInputStream(fd.getFileDescriptor())) { 365 FileChannel fileChannel = is.getChannel(); 366 long fontSize = fileChannel.size(); 367 ByteBuffer fontBuffer = fileChannel.map( 368 FileChannel.MapMode.READ_ONLY, 0, fontSize); 369 int weight = result.getWeight(); 370 int italic = result.getItalic() ? STYLE_ITALIC : STYLE_NORMAL; 371 FontVariationAxis[] axes = null; 372 try { 373 axes = FontVariationAxis.fromFontVariationSettings( 374 result.getFontVariationSettings()); 375 } catch (FontVariationAxis.InvalidFormatException e) { 376 // TODO: Nice to pass FontVariationAxis[] directly instead of string. 377 } 378 if (!fontFamily.addFontFromBuffer(fontBuffer, result.getTtcIndex(), 379 axes, weight, italic)) { 380 Log.e(TAG, "Error creating font " + request.getQuery()); 381 callback.onTypefaceRequestFailed( 382 FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR); 383 return; 384 } 385 } catch (IOException e) { 386 Log.e(TAG, "Error reading font " + request.getQuery(), e); 387 callback.onTypefaceRequestFailed( 388 FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR); 389 return; 390 } finally { 391 IoUtils.closeQuietly(fd); 392 } 393 } 394 fontFamily.freeze(); 395 Typeface typeface = Typeface.createFromFamiliesWithDefault(new FontFamily[] { fontFamily }); 396 synchronized (sDynamicTypefaceCache) { 397 String key = createProviderUid(request.getProviderAuthority(), request.getQuery()); 398 sDynamicTypefaceCache.put(key, typeface); 399 } 400 callback.onTypefaceRetrieved(typeface); 401 } 402 403 /** 404 * Interface used to receive asynchronously fetched typefaces. 405 */ 406 public interface FontRequestCallback { 407 /** 408 * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the given 409 * provider was not found on the device. 410 */ 411 int FAIL_REASON_PROVIDER_NOT_FOUND = FontsContract.RESULT_CODE_PROVIDER_NOT_FOUND; 412 /** 413 * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the given 414 * provider must be authenticated and the given certificates do not match its signature. 415 */ 416 int FAIL_REASON_WRONG_CERTIFICATES = FontsContract.RESULT_CODE_WRONG_CERTIFICATES; 417 /** 418 * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the font 419 * returned by the provider was not loaded properly. 420 */ 421 int FAIL_REASON_FONT_LOAD_ERROR = -3; 422 /** 423 * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the font 424 * provider did not return any results for the given query. 425 */ 426 int FAIL_REASON_FONT_NOT_FOUND = FontsContract.Columns.RESULT_CODE_FONT_NOT_FOUND; 427 /** 428 * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the font 429 * provider found the queried font, but it is currently unavailable. 430 */ 431 int FAIL_REASON_FONT_UNAVAILABLE = FontsContract.Columns.RESULT_CODE_FONT_UNAVAILABLE; 432 /** 433 * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the given 434 * query was not supported by the provider. 435 */ 436 int FAIL_REASON_MALFORMED_QUERY = FontsContract.Columns.RESULT_CODE_MALFORMED_QUERY; 437 438 /** @hide */ 439 @IntDef({ FAIL_REASON_PROVIDER_NOT_FOUND, FAIL_REASON_FONT_LOAD_ERROR, 440 FAIL_REASON_FONT_NOT_FOUND, FAIL_REASON_FONT_UNAVAILABLE, 441 FAIL_REASON_MALFORMED_QUERY }) 442 @Retention(RetentionPolicy.SOURCE) 443 @interface FontRequestFailReason {} 444 445 /** 446 * Called then a Typeface request done via {@link Typeface#create(FontRequest, 447 * FontRequestCallback)} is complete. Note that this method will not be called if 448 * {@link #onTypefaceRequestFailed(int)} is called instead. 449 * @param typeface The Typeface object retrieved. 450 */ 451 void onTypefaceRetrieved(Typeface typeface); 452 453 /** 454 * Called when a Typeface request done via {@link Typeface#create(FontRequest, 455 * FontRequestCallback)} fails. 456 * @param reason One of {@link #FAIL_REASON_PROVIDER_NOT_FOUND}, 457 * {@link #FAIL_REASON_FONT_NOT_FOUND}, 458 * {@link #FAIL_REASON_FONT_LOAD_ERROR}, 459 * {@link #FAIL_REASON_FONT_UNAVAILABLE} or 460 * {@link #FAIL_REASON_MALFORMED_QUERY}. 461 */ 462 void onTypefaceRequestFailed(@FontRequestFailReason int reason); 463 } 464 465 private static final FontRequestCallback NO_OP_REQUEST_CALLBACK = new FontRequestCallback() { 466 @Override 467 public void onTypefaceRetrieved(Typeface typeface) { 468 // Do nothing. 469 } 470 471 @Override 472 public void onTypefaceRequestFailed(@FontRequestFailReason int reason) { 473 // Do nothing. 474 } 475 }; 476 477 /** 478 * A builder class for creating new Typeface instance. 479 * 480 * <p> 481 * Examples, 482 * 1) Create Typeface from ttf file. 483 * <pre> 484 * <code> 485 * Typeface.Builder buidler = new Typeface.Builder("your_font_file.ttf"); 486 * Typeface typeface = builder.build(); 487 * </code> 488 * </pre> 489 * 490 * 2) Create Typeface from ttc file in assets directory. 491 * <pre> 492 * <code> 493 * Typeface.Builder buidler = new Typeface.Builder(getAssets(), "your_font_file.ttc"); 494 * builder.setTtcIndex(2); // Set index of font collection. 495 * Typeface typeface = builder.build(); 496 * </code> 497 * </pre> 498 * 499 * 3) Create Typeface with variation settings. 500 * <pre> 501 * <code> 502 * Typeface.Builder buidler = new Typeface.Builder("your_font_file.ttf"); 503 * builder.setFontVariationSettings("'wght' 700, 'slnt' 20, 'ital' 1"); 504 * builder.setWeight(700); // Tell the system that this is a bold font. 505 * builder.setItalic(true); // Tell the system that this is an italic style font. 506 * Typeface typeface = builder.build(); 507 * </code> 508 * </pre> 509 * </p> 510 */ 511 public static final class Builder { 512 513 private int mTtcIndex; 514 private FontVariationAxis[] mAxes; 515 516 private AssetManager mAssetManager; 517 private String mPath; 518 private FileDescriptor mFd; 519 520 private int mWeight = RESOLVE_BY_FONT_TABLE; 521 private int mItalic = RESOLVE_BY_FONT_TABLE; 522 523 /** 524 * Constructs a builder with a file path. 525 * 526 * @param path The file object refers to the font file. 527 */ 528 public Builder(@NonNull File path) { 529 mPath = path.getAbsolutePath(); 530 } 531 532 /** 533 * Constructs a builder with a file descriptor. 534 * 535 * @param fd The file descriptor. The passed fd must be mmap-able. 536 */ 537 public Builder(@NonNull FileDescriptor fd) { 538 mFd = fd; 539 } 540 541 /** 542 * Constructs a builder with a file path. 543 * 544 * @param path The full path to the font file. 545 */ 546 public Builder(@NonNull String path) { 547 mPath = path; 548 } 549 550 /** 551 * Constructs a builder from an asset manager and a file path in an asset directory. 552 * 553 * @param assetManager The application's asset manager 554 * @param path The file name of the font data in the asset directory 555 */ 556 public Builder(@NonNull AssetManager assetManager, @NonNull String path) { 557 mAssetManager = Preconditions.checkNotNull(assetManager); 558 mPath = Preconditions.checkStringNotEmpty(path); 559 } 560 561 /** 562 * Sets weight of the font. 563 * 564 * Tells the system the weight of the given font. If not provided, the system will resolve 565 * the weight value by reading font tables. 566 * @param weight a weight value. 567 */ 568 public Builder setWeight(@IntRange(from = 1, to = 1000) int weight) { 569 mWeight = weight; 570 return this; 571 } 572 573 /** 574 * Sets italic information of the font. 575 * 576 * Tells the system the style of the given font. If not provided, the system will resolve 577 * the style by reading font tables. 578 * @param italic {@code true} if the font is italic. Otherwise {@code false}. 579 */ 580 public Builder setItalic(boolean italic) { 581 mItalic = italic ? STYLE_ITALIC : STYLE_NORMAL; 582 return this; 583 } 584 585 /** 586 * Sets an index of the font collection. 587 * 588 * Can not be used for Typeface source. build() method will return null for invalid index. 589 * @param ttcIndex An index of the font collection. If the font source is not font 590 * collection, do not call this method or specify 0. 591 */ 592 public Builder setTtcIndex(@IntRange(from = 0) int ttcIndex) { 593 mTtcIndex = ttcIndex; 594 return this; 595 } 596 597 /** 598 * Sets a font variation settings. 599 * 600 * @param variationSettings See {@link android.widget.TextView#setFontVariationSettings}. 601 * @throws InvalidFormatException If given string is not a valid font variation settings 602 * format. 603 */ 604 public Builder setFontVariationSettings(@Nullable String variationSettings) 605 throws InvalidFormatException { 606 mAxes = FontVariationAxis.fromFontVariationSettings(variationSettings); 607 return this; 608 } 609 610 /** 611 * Sets a font variation settings. 612 * 613 * @param axes An array of font variation axis tag-value pairs. 614 */ 615 public Builder setFontVariationSettings(@Nullable FontVariationAxis[] axes) { 616 mAxes = axes; 617 return this; 618 } 619 620 /** 621 * Creates a unique id for a given AssetManager and asset path. 622 * 623 * @param mgr AssetManager instance 624 * @param path The path for the asset. 625 * @param ttcIndex The TTC index for the font. 626 * @param axes The font variation settings. 627 * @return Unique id for a given AssetManager and asset path. 628 */ 629 private static String createAssetUid(final AssetManager mgr, String path, int ttcIndex, 630 @Nullable FontVariationAxis[] axes) { 631 final SparseArray<String> pkgs = mgr.getAssignedPackageIdentifiers(); 632 final StringBuilder builder = new StringBuilder(); 633 final int size = pkgs.size(); 634 for (int i = 0; i < size; i++) { 635 builder.append(pkgs.valueAt(i)); 636 builder.append("-"); 637 } 638 builder.append(path); 639 builder.append("-"); 640 builder.append(Integer.toString(ttcIndex)); 641 builder.append("-"); 642 if (axes != null) { 643 for (FontVariationAxis axis : axes) { 644 builder.append(axis.getTag()); 645 builder.append("-"); 646 builder.append(Float.toString(axis.getStyleValue())); 647 } 648 } 649 return builder.toString(); 650 } 651 652 /** 653 * Generates new Typeface from specified configuration. 654 * 655 * @return Newly created Typeface. May return null if some parameters are invalid. 656 */ 657 public Typeface build() { 658 if (mFd != null) { // set source by setSourceFromFile(FileDescriptor) 659 try (FileInputStream fis = new FileInputStream(mFd)) { 660 FileChannel channel = fis.getChannel(); 661 long size = channel.size(); 662 ByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, size); 663 664 final FontFamily fontFamily = new FontFamily(); 665 if (!fontFamily.addFontFromBuffer(buffer, mTtcIndex, mAxes, mWeight, mItalic)) { 666 fontFamily.abortCreation(); 667 return null; 668 } 669 fontFamily.freeze(); 670 FontFamily[] families = { fontFamily }; 671 return createFromFamiliesWithDefault(families); 672 } catch (IOException e) { 673 return null; 674 } 675 } else if (mAssetManager != null) { // set source by setSourceFromAsset() 676 final String key = createAssetUid(mAssetManager, mPath, mTtcIndex, mAxes); 677 synchronized (sDynamicTypefaceCache) { 678 Typeface typeface = sDynamicTypefaceCache.get(key); 679 if (typeface != null) return typeface; 680 final FontFamily fontFamily = new FontFamily(); 681 if (!fontFamily.addFontFromAssetManager(mAssetManager, mPath, mTtcIndex, 682 true /* isAsset */, mTtcIndex, mWeight, mItalic, mAxes)) { 683 fontFamily.abortCreation(); 684 return null; 685 } 686 fontFamily.freeze(); 687 FontFamily[] families = { fontFamily }; 688 typeface = createFromFamiliesWithDefault(families); 689 sDynamicTypefaceCache.put(key, typeface); 690 return typeface; 691 } 692 } else if (mPath != null) { // set source by setSourceFromFile(File) 693 final FontFamily fontFamily = new FontFamily(); 694 if (!fontFamily.addFont(mPath, mTtcIndex, mAxes, mWeight, mItalic)) { 695 fontFamily.abortCreation(); 696 return null; 697 } 698 fontFamily.freeze(); 699 FontFamily[] families = { fontFamily }; 700 return createFromFamiliesWithDefault(families); 701 } 702 703 // Must not reach here. 704 throw new IllegalArgumentException("No source was set."); 705 } 706 } 707 708 /** 709 * Create a typeface object given a family name, and option style information. 710 * If null is passed for the name, then the "default" font will be chosen. 711 * The resulting typeface object can be queried (getStyle()) to discover what 712 * its "real" style characteristics are. 713 * 714 * @param familyName May be null. The name of the font family. 715 * @param style The style (normal, bold, italic) of the typeface. 716 * e.g. NORMAL, BOLD, ITALIC, BOLD_ITALIC 717 * @return The best matching typeface. 718 */ 719 public static Typeface create(String familyName, int style) { 720 if (sSystemFontMap != null) { 721 return create(sSystemFontMap.get(familyName), style); 722 } 723 return null; 724 } 725 726 /** 727 * Create a typeface object that best matches the specified existing 728 * typeface and the specified Style. Use this call if you want to pick a new 729 * style from the same family of an existing typeface object. If family is 730 * null, this selects from the default font's family. 731 * 732 * @param family May be null. The name of the existing type face. 733 * @param style The style (normal, bold, italic) of the typeface. 734 * e.g. NORMAL, BOLD, ITALIC, BOLD_ITALIC 735 * @return The best matching typeface. 736 */ 737 public static Typeface create(Typeface family, int style) { 738 if (style < 0 || style > 3) { 739 style = 0; 740 } 741 long ni = 0; 742 if (family != null) { 743 // Return early if we're asked for the same face/style 744 if (family.mStyle == style) { 745 return family; 746 } 747 748 ni = family.native_instance; 749 } 750 751 Typeface typeface; 752 SparseArray<Typeface> styles = sTypefaceCache.get(ni); 753 754 if (styles != null) { 755 typeface = styles.get(style); 756 if (typeface != null) { 757 return typeface; 758 } 759 } 760 761 typeface = new Typeface(nativeCreateFromTypeface(ni, style)); 762 if (styles == null) { 763 styles = new SparseArray<Typeface>(4); 764 sTypefaceCache.put(ni, styles); 765 } 766 styles.put(style, typeface); 767 768 return typeface; 769 } 770 771 /** @hide */ 772 public static Typeface createFromTypefaceWithVariation(Typeface family, 773 List<FontVariationAxis> axes) { 774 final long ni = family == null ? 0 : family.native_instance; 775 return new Typeface(nativeCreateFromTypefaceWithVariation(ni, axes)); 776 } 777 778 /** 779 * Returns one of the default typeface objects, based on the specified style 780 * 781 * @return the default typeface that corresponds to the style 782 */ 783 public static Typeface defaultFromStyle(int style) { 784 return sDefaults[style]; 785 } 786 787 /** 788 * Create a new typeface from the specified font data. 789 * 790 * @param mgr The application's asset manager 791 * @param path The file name of the font data in the assets directory 792 * @return The new typeface. 793 */ 794 public static Typeface createFromAsset(AssetManager mgr, String path) { 795 if (path == null) { 796 throw new NullPointerException(); // for backward compatibility 797 } 798 if (sFallbackFonts != null) { 799 Typeface typeface = new Builder(mgr, path).build(); 800 if (typeface != null) { 801 return typeface; 802 } 803 } 804 // For the compatibility reasons, throw runtime exception if failed to create Typeface. 805 throw new RuntimeException("Font asset not found " + path); 806 } 807 808 /** 809 * Creates a unique id for a given font provider and query. 810 */ 811 private static String createProviderUid(String authority, String query) { 812 final StringBuilder builder = new StringBuilder(); 813 builder.append("provider:"); 814 builder.append(authority); 815 builder.append("-"); 816 builder.append(query); 817 return builder.toString(); 818 } 819 820 /** 821 * Create a new typeface from the specified font file. 822 * 823 * @param path The path to the font data. 824 * @return The new typeface. 825 */ 826 public static Typeface createFromFile(@Nullable File path) { 827 // For the compatibility reasons, leaving possible NPE here. 828 // See android.graphics.cts.TypefaceTest#testCreateFromFileByFileReferenceNull 829 return createFromFile(path.getAbsolutePath()); 830 } 831 832 /** 833 * Create a new typeface from the specified font file. 834 * 835 * @param path The full path to the font data. 836 * @return The new typeface. 837 */ 838 public static Typeface createFromFile(@Nullable String path) { 839 if (path == null) { 840 // For the compatibility reasons, need to throw NPE if the argument is null. 841 // See android.graphics.cts.TypefaceTest#testCreateFromFileByFileNameNull 842 throw new NullPointerException(); 843 } 844 if (sFallbackFonts != null) { 845 Typeface typeface = new Builder(path).build(); 846 if (typeface != null) { 847 // For the compatibility reasons, throw runtime exception if failed to create 848 // Typeface. 849 return typeface; 850 } 851 } 852 throw new RuntimeException("Font not found " + path); 853 } 854 855 /** 856 * Create a new typeface from an array of font families. 857 * 858 * @param families array of font families 859 */ 860 private static Typeface createFromFamilies(FontFamily[] families) { 861 long[] ptrArray = new long[families.length]; 862 for (int i = 0; i < families.length; i++) { 863 ptrArray[i] = families[i].mNativePtr; 864 } 865 return new Typeface(nativeCreateFromArray(ptrArray)); 866 } 867 868 /** 869 * Create a new typeface from an array of font families, including 870 * also the font families in the fallback list. 871 * 872 * @param families array of font families 873 */ 874 private static Typeface createFromFamiliesWithDefault(FontFamily[] families) { 875 long[] ptrArray = new long[families.length + sFallbackFonts.length]; 876 for (int i = 0; i < families.length; i++) { 877 ptrArray[i] = families[i].mNativePtr; 878 } 879 for (int i = 0; i < sFallbackFonts.length; i++) { 880 ptrArray[i + families.length] = sFallbackFonts[i].mNativePtr; 881 } 882 return new Typeface(nativeCreateFromArray(ptrArray)); 883 } 884 885 // don't allow clients to call this directly 886 private Typeface(long ni) { 887 if (ni == 0) { 888 throw new RuntimeException("native typeface cannot be made"); 889 } 890 891 native_instance = ni; 892 mStyle = nativeGetStyle(ni); 893 } 894 895 private static FontFamily makeFamilyFromParsed(FontConfig.Family family, 896 Map<String, ByteBuffer> bufferForPath) { 897 FontFamily fontFamily = new FontFamily(family.getLanguage(), family.getVariant()); 898 for (FontConfig.Font font : family.getFonts()) { 899 String fullPathName = "/system/fonts/" + font.getFontName(); 900 ByteBuffer fontBuffer = bufferForPath.get(fullPathName); 901 if (fontBuffer == null) { 902 try (FileInputStream file = new FileInputStream(fullPathName)) { 903 FileChannel fileChannel = file.getChannel(); 904 long fontSize = fileChannel.size(); 905 fontBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fontSize); 906 bufferForPath.put(fullPathName, fontBuffer); 907 } catch (IOException e) { 908 Log.e(TAG, "Error mapping font file " + fullPathName); 909 continue; 910 } 911 } 912 if (!fontFamily.addFontFromBuffer(fontBuffer, font.getTtcIndex(), font.getAxes(), 913 font.getWeight(), font.isItalic() ? STYLE_ITALIC : STYLE_NORMAL)) { 914 Log.e(TAG, "Error creating font " + fullPathName + "#" + font.getTtcIndex()); 915 } 916 } 917 fontFamily.freeze(); 918 return fontFamily; 919 } 920 921 /* 922 * (non-Javadoc) 923 * 924 * This should only be called once, from the static class initializer block. 925 */ 926 private static void init() { 927 // Load font config and initialize Minikin state 928 File systemFontConfigLocation = getSystemFontConfigLocation(); 929 File configFilename = new File(systemFontConfigLocation, FONTS_CONFIG); 930 try { 931 FileInputStream fontsIn = new FileInputStream(configFilename); 932 FontConfig fontConfig = FontListParser.parse(fontsIn); 933 934 Map<String, ByteBuffer> bufferForPath = new HashMap<String, ByteBuffer>(); 935 936 List<FontFamily> familyList = new ArrayList<FontFamily>(); 937 // Note that the default typeface is always present in the fallback list; 938 // this is an enhancement from pre-Minikin behavior. 939 for (int i = 0; i < fontConfig.getFamilies().length; i++) { 940 FontConfig.Family f = fontConfig.getFamilies()[i]; 941 if (i == 0 || f.getName() == null) { 942 familyList.add(makeFamilyFromParsed(f, bufferForPath)); 943 } 944 } 945 sFallbackFonts = familyList.toArray(new FontFamily[familyList.size()]); 946 setDefault(Typeface.createFromFamilies(sFallbackFonts)); 947 948 Map<String, Typeface> systemFonts = new HashMap<String, Typeface>(); 949 for (int i = 0; i < fontConfig.getFamilies().length; i++) { 950 Typeface typeface; 951 FontConfig.Family f = fontConfig.getFamilies()[i]; 952 if (f.getName() != null) { 953 if (i == 0) { 954 // The first entry is the default typeface; no sense in 955 // duplicating the corresponding FontFamily. 956 typeface = sDefaultTypeface; 957 } else { 958 FontFamily fontFamily = makeFamilyFromParsed(f, bufferForPath); 959 FontFamily[] families = { fontFamily }; 960 typeface = Typeface.createFromFamiliesWithDefault(families); 961 } 962 systemFonts.put(f.getName(), typeface); 963 } 964 } 965 for (FontConfig.Alias alias : fontConfig.getAliases()) { 966 Typeface base = systemFonts.get(alias.getToName()); 967 Typeface newFace = base; 968 int weight = alias.getWeight(); 969 if (weight != 400) { 970 newFace = new Typeface(nativeCreateWeightAlias(base.native_instance, weight)); 971 } 972 systemFonts.put(alias.getName(), newFace); 973 } 974 sSystemFontMap = systemFonts; 975 976 } catch (RuntimeException e) { 977 Log.w(TAG, "Didn't create default family (most likely, non-Minikin build)", e); 978 // TODO: normal in non-Minikin case, remove or make error when Minikin-only 979 } catch (FileNotFoundException e) { 980 Log.e(TAG, "Error opening " + configFilename, e); 981 } catch (IOException e) { 982 Log.e(TAG, "Error reading " + configFilename, e); 983 } catch (XmlPullParserException e) { 984 Log.e(TAG, "XML parse exception for " + configFilename, e); 985 } 986 } 987 988 static { 989 init(); 990 // Set up defaults and typefaces exposed in public API 991 DEFAULT = create((String) null, 0); 992 DEFAULT_BOLD = create((String) null, Typeface.BOLD); 993 SANS_SERIF = create("sans-serif", 0); 994 SERIF = create("serif", 0); 995 MONOSPACE = create("monospace", 0); 996 997 sDefaults = new Typeface[] { 998 DEFAULT, 999 DEFAULT_BOLD, 1000 create((String) null, Typeface.ITALIC), 1001 create((String) null, Typeface.BOLD_ITALIC), 1002 }; 1003 1004 } 1005 1006 private static File getSystemFontConfigLocation() { 1007 return new File("/system/etc/"); 1008 } 1009 1010 @Override 1011 protected void finalize() throws Throwable { 1012 try { 1013 nativeUnref(native_instance); 1014 native_instance = 0; // Other finalizers can still call us. 1015 } finally { 1016 super.finalize(); 1017 } 1018 } 1019 1020 @Override 1021 public boolean equals(Object o) { 1022 if (this == o) return true; 1023 if (o == null || getClass() != o.getClass()) return false; 1024 1025 Typeface typeface = (Typeface) o; 1026 1027 return mStyle == typeface.mStyle && native_instance == typeface.native_instance; 1028 } 1029 1030 @Override 1031 public int hashCode() { 1032 /* 1033 * Modified method for hashCode with long native_instance derived from 1034 * http://developer.android.com/reference/java/lang/Object.html 1035 */ 1036 int result = 17; 1037 result = 31 * result + (int) (native_instance ^ (native_instance >>> 32)); 1038 result = 31 * result + mStyle; 1039 return result; 1040 } 1041 1042 /** @hide */ 1043 public boolean isSupportedAxes(int axis) { 1044 if (mSupportedAxes == null) { 1045 synchronized (this) { 1046 if (mSupportedAxes == null) { 1047 mSupportedAxes = nativeGetSupportedAxes(native_instance); 1048 if (mSupportedAxes == null) { 1049 mSupportedAxes = EMPTY_AXES; 1050 } 1051 } 1052 } 1053 } 1054 return Arrays.binarySearch(mSupportedAxes, axis) > 0; 1055 } 1056 1057 private static native long nativeCreateFromTypeface(long native_instance, int style); 1058 // TODO: clean up: change List<FontVariationAxis> to FontVariationAxis[] 1059 private static native long nativeCreateFromTypefaceWithVariation( 1060 long native_instance, List<FontVariationAxis> axes); 1061 private static native long nativeCreateWeightAlias(long native_instance, int weight); 1062 private static native void nativeUnref(long native_instance); 1063 private static native int nativeGetStyle(long native_instance); 1064 private static native long nativeCreateFromArray(long[] familyArray); 1065 private static native void nativeSetDefault(long native_instance); 1066 private static native int[] nativeGetSupportedAxes(long native_instance); 1067} 1068