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