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