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