Typeface.java revision a4ab320e0a97f0ac8a336a1990248bfa33232611
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 final ArrayList<FontConfig.Axis> axes = FontListParser.parseFontVariationSettings( 362 result.getFontVariationSettings()); 363 if (!fontFamily.addFontFromBuffer(fontBuffer, result.getTtcIndex(), 364 axes.toArray(new FontConfig.Axis[axes.size()]), 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 public static final class Builder { 494 /** 495 * Value for weight and italic. 496 * 497 * Indicates the value is resolved by font metadata. 498 */ 499 // Must be same with C++ constant in core/jni/android/graphics/FontFamily.cpp 500 public static final int RESOLVE_BY_FONT_TABLE = -1; 501 502 /** 503 * Value for italic. 504 * 505 * Indicates the font style is not italic. 506 */ 507 public static final int NORMAL = 0; 508 509 /** 510 * Value for italic. 511 * 512 * Indicates the font style is italic. 513 */ 514 public static final int ITALIC = 1; 515 516 private int mTtcIndex; 517 private FontConfig.Axis[] mAxes; 518 519 private AssetManager mAssetManager; 520 private String mPath; 521 private FileDescriptor mFd; 522 private @IntRange(from = -1) int mWeight = RESOLVE_BY_FONT_TABLE; 523 524 /** @hide */ 525 @Retention(SOURCE) 526 @IntDef({RESOLVE_BY_FONT_TABLE, NORMAL, ITALIC}) 527 public @interface Italic {} 528 private @Italic int mItalic = RESOLVE_BY_FONT_TABLE; 529 530 private boolean mHasSourceSet = false; 531 private boolean mRecycled = false; 532 533 /** Use Builder.obtain() instead */ 534 private void Builder() {} 535 536 private static AtomicReference<Builder> mCache = new AtomicReference<>(); 537 538 /** 539 * Returns Typeface.Builder from pool. 540 */ 541 public static Builder obtain() { 542 final Builder builder = mCache.getAndSet(null); 543 if (builder != null) { 544 builder.mRecycled = false; 545 return builder; 546 } 547 return new Builder(); 548 } 549 550 /** 551 * Resets the internal states. 552 */ 553 public void reset() { 554 checkNotRecycled(); 555 mTtcIndex = 0; 556 mAxes = null; 557 558 mAssetManager = null; 559 mPath = null; 560 mFd = null; 561 562 mWeight = RESOLVE_BY_FONT_TABLE; 563 mItalic = RESOLVE_BY_FONT_TABLE; 564 565 mHasSourceSet = false; 566 } 567 568 /** 569 * Returns the instance to the pool. 570 */ 571 public void recycle() { 572 reset(); 573 mRecycled = true; 574 575 mCache.compareAndSet(null, this); 576 } 577 578 private void checkNotRecycled() { 579 if (mRecycled) { 580 throw new IllegalStateException("Don't use Builder after calling recycle()"); 581 } 582 } 583 584 private void checkSingleFontSource() { 585 if (mHasSourceSet) { 586 throw new IllegalStateException("Typeface can only built with single font source."); 587 } 588 } 589 590 /** 591 * Sets a font file as a source of Typeface. 592 * 593 * @param path The file object refers to the font file. 594 */ 595 public Builder setSourceFromFile(@NonNull File path) { 596 return setSourceFromFilePath(path.getAbsolutePath()); 597 } 598 599 /** 600 * Sets a font file as a source of Typeface. 601 * 602 * @param fd The file descriptor. The passed fd must be mmap-able. 603 */ 604 public Builder setSourceFromFile(@NonNull FileDescriptor fd) { 605 checkNotRecycled(); 606 checkSingleFontSource(); 607 mFd = fd; 608 mHasSourceSet = true; 609 return this; 610 } 611 612 /** 613 * Sets a font file as a source of Typeface. 614 * 615 * @param path The full path to the font file. 616 */ 617 public Builder setSourceFromFilePath(@NonNull String path) { 618 checkNotRecycled(); 619 checkSingleFontSource(); 620 mPath = path; 621 mHasSourceSet = true; 622 return this; 623 } 624 625 /** 626 * Sets an asset entry as a source of Typeface. 627 * 628 * @param assetManager The application's asset manager 629 * @param path The file name of the font data in the asset directory 630 */ 631 public Builder setSourceFromAsset(@NonNull AssetManager assetManager, 632 @NonNull String path) { 633 checkNotRecycled(); 634 checkSingleFontSource(); 635 mAssetManager = Preconditions.checkNotNull(assetManager); 636 mPath = Preconditions.checkStringNotEmpty(path); 637 mHasSourceSet = true; 638 return this; 639 } 640 641 /** 642 * Sets weight of the font. 643 * 644 * By passing {@link #RESOLVE_BY_FONT_TABLE}, weight value is resolved by OS/2 table in 645 * font file if possible. 646 * @param weight a weight value or {@link #RESOLVE_BY_FONT_TABLE} 647 */ 648 public Builder setWeight(@IntRange(from = -1) int weight) { 649 checkNotRecycled(); 650 mWeight = weight; 651 return this; 652 } 653 654 /** 655 * Sets italic information of the font. 656 * 657 * By passing {@link #RESOLVE_BY_FONT_TABLE}, italic or normal is determined by OS/2 table 658 * in font file if possible. 659 * @param italic One of {@link #NORMAL}, {@link #ITALIC}, {@link #RESOLVE_BY_FONT_TABLE}. 660 * will be used. 661 */ 662 public Builder setItalic(@Italic int italic) { 663 checkNotRecycled(); 664 mItalic = italic; 665 return this; 666 } 667 668 /** 669 * Sets an idex of the font collection. 670 * 671 * Can not be used for Typeface source. build() method will return null for invalid index. 672 * @param ttcIndex An index of the font collection. If the font source is not font 673 * collection, do not call this method or specify 0. 674 */ 675 public Builder setTtcIndex(@IntRange(from = 0) int ttcIndex) { 676 checkNotRecycled(); 677 mTtcIndex = ttcIndex; 678 return this; 679 } 680 681 /** 682 * Sets a font variation settings. 683 * 684 * @param variationSettings See {@link android.widget.TextView#setFontVariationSettings}. 685 */ 686 public Builder setFontVariationSettings(@Nullable String variationSettings) { 687 checkNotRecycled(); 688 if (mAxes != null) { 689 throw new IllegalStateException("Font variation settings are already set."); 690 } 691 final List<FontConfig.Axis> axesList = FontListParser.parseFontVariationSettings( 692 variationSettings); 693 mAxes = axesList.toArray(new FontConfig.Axis[axesList.size()]); 694 return this; 695 } 696 697 /** 698 * Sets a font variation settings. 699 * 700 * @param axes An array of font variation axis tag-value pairs. 701 */ 702 public Builder setFontVariationSettings(@Nullable FontConfig.Axis[] axes) { 703 checkNotRecycled(); 704 if (mAxes != null) { 705 throw new IllegalStateException("Font variation settings are already set."); 706 } 707 mAxes = axes; 708 return this; 709 } 710 711 /** 712 * Creates a unique id for a given AssetManager and asset path. 713 * 714 * @param mgr AssetManager instance 715 * @param path The path for the asset. 716 * @param ttcIndex The TTC index for the font. 717 * @param axes The font variation settings. 718 * @return Unique id for a given AssetManager and asset path. 719 */ 720 private static String createAssetUid(final AssetManager mgr, String path, int ttcIndex, 721 @Nullable FontConfig.Axis[] axes) { 722 final SparseArray<String> pkgs = mgr.getAssignedPackageIdentifiers(); 723 final StringBuilder builder = new StringBuilder(); 724 final int size = pkgs.size(); 725 for (int i = 0; i < size; i++) { 726 builder.append(pkgs.valueAt(i)); 727 builder.append("-"); 728 } 729 builder.append(path); 730 builder.append("-"); 731 builder.append(Integer.toString(ttcIndex)); 732 builder.append("-"); 733 if (axes != null) { 734 for (FontConfig.Axis axis : axes) { 735 builder.append(Integer.toHexString(axis.getTag())); 736 builder.append("-"); 737 builder.append(Float.toString(axis.getStyleValue())); 738 } 739 } 740 return builder.toString(); 741 } 742 743 /** 744 * Generates new Typeface from specified configuration. 745 * 746 * @return Newly created Typeface. May return null if some parameters are invalid. 747 */ 748 public Typeface build() { 749 checkNotRecycled(); 750 if (!mHasSourceSet) { 751 return null; 752 } 753 754 if (mFd != null) { // set source by setSourceFromFile(FileDescriptor) 755 try (FileInputStream fis = new FileInputStream(mFd)) { 756 FileChannel channel = fis.getChannel(); 757 long size = channel.size(); 758 ByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, size); 759 760 final FontFamily fontFamily = new FontFamily(); 761 if (!fontFamily.addFontFromBuffer(buffer, mTtcIndex, mAxes, mWeight, mItalic)) { 762 fontFamily.abortCreation(); 763 return null; 764 } 765 fontFamily.freeze(); 766 FontFamily[] families = { fontFamily }; 767 return createFromFamiliesWithDefault(families); 768 } catch (IOException e) { 769 return null; 770 } 771 } else if (mAssetManager != null) { // set source by setSourceFromAsset() 772 final String key = createAssetUid(mAssetManager, mPath, mTtcIndex, mAxes); 773 synchronized (sDynamicTypefaceCache) { 774 Typeface typeface = sDynamicTypefaceCache.get(key); 775 if (typeface != null) return typeface; 776 final FontFamily fontFamily = new FontFamily(); 777 if (!fontFamily.addFontFromAssetManager(mAssetManager, mPath, mTtcIndex, 778 true /* isAsset */, mTtcIndex, mWeight, mItalic, mAxes)) { 779 fontFamily.abortCreation(); 780 return null; 781 } 782 fontFamily.freeze(); 783 FontFamily[] families = { fontFamily }; 784 typeface = createFromFamiliesWithDefault(families); 785 sDynamicTypefaceCache.put(key, typeface); 786 return typeface; 787 } 788 } else if (mPath != null) { // set source by setSourceFromFile(File) 789 final FontFamily fontFamily = new FontFamily(); 790 if (!fontFamily.addFont(mPath, mTtcIndex, mAxes, mWeight, mItalic)) { 791 fontFamily.abortCreation(); 792 return null; 793 } 794 fontFamily.freeze(); 795 FontFamily[] families = { fontFamily }; 796 return createFromFamiliesWithDefault(families); 797 } else { 798 throw new IllegalArgumentException("No source was set."); 799 } 800 } 801 } 802 803 /** 804 * Create a typeface object given a family name, and option style information. 805 * If null is passed for the name, then the "default" font will be chosen. 806 * The resulting typeface object can be queried (getStyle()) to discover what 807 * its "real" style characteristics are. 808 * 809 * @param familyName May be null. The name of the font family. 810 * @param style The style (normal, bold, italic) of the typeface. 811 * e.g. NORMAL, BOLD, ITALIC, BOLD_ITALIC 812 * @return The best matching typeface. 813 */ 814 public static Typeface create(String familyName, int style) { 815 if (sSystemFontMap != null) { 816 return create(sSystemFontMap.get(familyName), style); 817 } 818 return null; 819 } 820 821 /** 822 * Create a typeface object that best matches the specified existing 823 * typeface and the specified Style. Use this call if you want to pick a new 824 * style from the same family of an existing typeface object. If family is 825 * null, this selects from the default font's family. 826 * 827 * @param family May be null. The name of the existing type face. 828 * @param style The style (normal, bold, italic) of the typeface. 829 * e.g. NORMAL, BOLD, ITALIC, BOLD_ITALIC 830 * @return The best matching typeface. 831 */ 832 public static Typeface create(Typeface family, int style) { 833 if (style < 0 || style > 3) { 834 style = 0; 835 } 836 long ni = 0; 837 if (family != null) { 838 // Return early if we're asked for the same face/style 839 if (family.mStyle == style) { 840 return family; 841 } 842 843 ni = family.native_instance; 844 } 845 846 Typeface typeface; 847 SparseArray<Typeface> styles = sTypefaceCache.get(ni); 848 849 if (styles != null) { 850 typeface = styles.get(style); 851 if (typeface != null) { 852 return typeface; 853 } 854 } 855 856 typeface = new Typeface(nativeCreateFromTypeface(ni, style)); 857 if (styles == null) { 858 styles = new SparseArray<Typeface>(4); 859 sTypefaceCache.put(ni, styles); 860 } 861 styles.put(style, typeface); 862 863 return typeface; 864 } 865 866 /** @hide */ 867 public static Typeface createFromTypefaceWithVariation(Typeface family, 868 List<FontConfig.Axis> axes) { 869 final long ni = family == null ? 0 : family.native_instance; 870 return new Typeface(nativeCreateFromTypefaceWithVariation(ni, axes)); 871 } 872 873 /** 874 * Returns one of the default typeface objects, based on the specified style 875 * 876 * @return the default typeface that corresponds to the style 877 */ 878 public static Typeface defaultFromStyle(int style) { 879 return sDefaults[style]; 880 } 881 882 /** 883 * Create a new typeface from the specified font data. 884 * 885 * @param mgr The application's asset manager 886 * @param path The file name of the font data in the assets directory 887 * @return The new typeface. 888 */ 889 public static Typeface createFromAsset(AssetManager mgr, String path) { 890 if (path == null) { 891 throw new NullPointerException(); // for backward compatibility 892 } 893 if (sFallbackFonts != null) { 894 final Builder builder = Builder.obtain(); 895 try { 896 builder.setSourceFromAsset(mgr, path); 897 Typeface typeface = builder.build(); 898 if (typeface != null) { 899 return typeface; 900 } 901 } finally { 902 builder.recycle(); 903 } 904 } 905 // For the compatibility reasons, throw runtime exception if failed to create Typeface. 906 throw new RuntimeException("Font asset not found " + path); 907 } 908 909 /** 910 * Creates a unique id for a given font provider and query. 911 */ 912 private static String createProviderUid(String authority, String query) { 913 final StringBuilder builder = new StringBuilder(); 914 builder.append("provider:"); 915 builder.append(authority); 916 builder.append("-"); 917 builder.append(query); 918 return builder.toString(); 919 } 920 921 /** 922 * Create a new typeface from the specified font file. 923 * 924 * @param path The path to the font data. 925 * @return The new typeface. 926 */ 927 public static Typeface createFromFile(@Nullable File path) { 928 // For the compatibility reasons, leaving possible NPE here. 929 // See android.graphics.cts.TypefaceTest#testCreateFromFileByFileReferenceNull 930 return createFromFile(path.getAbsolutePath()); 931 } 932 933 /** 934 * Create a new typeface from the specified font file. 935 * 936 * @param path The full path to the font data. 937 * @return The new typeface. 938 */ 939 public static Typeface createFromFile(@Nullable String path) { 940 if (path == null) { 941 // For the compatibility reasons, need to throw NPE if the argument is null. 942 // See android.graphics.cts.TypefaceTest#testCreateFromFileByFileNameNull 943 throw new NullPointerException(); 944 } 945 if (sFallbackFonts != null) { 946 final Builder builder = Builder.obtain(); 947 try { 948 builder.setSourceFromFilePath(path); 949 Typeface typeface = builder.build(); 950 if (typeface != null) { 951 // For the compatibility reasons, throw runtime exception if failed to create 952 // Typeface. 953 return typeface; 954 } 955 } finally { 956 builder.recycle(); 957 } 958 } 959 throw new RuntimeException("Font not found " + path); 960 } 961 962 /** 963 * Create a new typeface from an array of font families. 964 * 965 * @param families array of font families 966 */ 967 private static Typeface createFromFamilies(FontFamily[] families) { 968 long[] ptrArray = new long[families.length]; 969 for (int i = 0; i < families.length; i++) { 970 ptrArray[i] = families[i].mNativePtr; 971 } 972 return new Typeface(nativeCreateFromArray(ptrArray)); 973 } 974 975 /** 976 * Create a new typeface from an array of font families, including 977 * also the font families in the fallback list. 978 * 979 * @param families array of font families 980 */ 981 private static Typeface createFromFamiliesWithDefault(FontFamily[] families) { 982 long[] ptrArray = new long[families.length + sFallbackFonts.length]; 983 for (int i = 0; i < families.length; i++) { 984 ptrArray[i] = families[i].mNativePtr; 985 } 986 for (int i = 0; i < sFallbackFonts.length; i++) { 987 ptrArray[i + families.length] = sFallbackFonts[i].mNativePtr; 988 } 989 return new Typeface(nativeCreateFromArray(ptrArray)); 990 } 991 992 // don't allow clients to call this directly 993 private Typeface(long ni) { 994 if (ni == 0) { 995 throw new RuntimeException("native typeface cannot be made"); 996 } 997 998 native_instance = ni; 999 mStyle = nativeGetStyle(ni); 1000 } 1001 1002 private static FontFamily makeFamilyFromParsed(FontConfig.Family family, 1003 Map<String, ByteBuffer> bufferForPath) { 1004 FontFamily fontFamily = new FontFamily(family.getLanguage(), family.getVariant()); 1005 for (FontConfig.Font font : family.getFonts()) { 1006 ByteBuffer fontBuffer = bufferForPath.get(font.getFontName()); 1007 if (fontBuffer == null) { 1008 try (FileInputStream file = new FileInputStream(font.getFontName())) { 1009 FileChannel fileChannel = file.getChannel(); 1010 long fontSize = fileChannel.size(); 1011 fontBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fontSize); 1012 bufferForPath.put(font.getFontName(), fontBuffer); 1013 } catch (IOException e) { 1014 Log.e(TAG, "Error mapping font file " + font.getFontName()); 1015 continue; 1016 } 1017 } 1018 if (!fontFamily.addFontFromBuffer(fontBuffer, font.getTtcIndex(), font.getAxes(), 1019 font.getWeight(), font.isItalic() ? Builder.ITALIC : Builder.NORMAL)) { 1020 Log.e(TAG, "Error creating font " + font.getFontName() + "#" + font.getTtcIndex()); 1021 } 1022 } 1023 fontFamily.freeze(); 1024 return fontFamily; 1025 } 1026 1027 /* 1028 * (non-Javadoc) 1029 * 1030 * This should only be called once, from the static class initializer block. 1031 */ 1032 private static void init() { 1033 // Load font config and initialize Minikin state 1034 File systemFontConfigLocation = getSystemFontConfigLocation(); 1035 File configFilename = new File(systemFontConfigLocation, FONTS_CONFIG); 1036 try { 1037 FileInputStream fontsIn = new FileInputStream(configFilename); 1038 FontConfig fontConfig = FontListParser.parse(fontsIn); 1039 1040 Map<String, ByteBuffer> bufferForPath = new HashMap<String, ByteBuffer>(); 1041 1042 List<FontFamily> familyList = new ArrayList<FontFamily>(); 1043 // Note that the default typeface is always present in the fallback list; 1044 // this is an enhancement from pre-Minikin behavior. 1045 for (int i = 0; i < fontConfig.getFamilies().length; i++) { 1046 FontConfig.Family f = fontConfig.getFamilies()[i]; 1047 if (i == 0 || f.getName() == null) { 1048 familyList.add(makeFamilyFromParsed(f, bufferForPath)); 1049 } 1050 } 1051 sFallbackFonts = familyList.toArray(new FontFamily[familyList.size()]); 1052 setDefault(Typeface.createFromFamilies(sFallbackFonts)); 1053 1054 Map<String, Typeface> systemFonts = new HashMap<String, Typeface>(); 1055 for (int i = 0; i < fontConfig.getFamilies().length; i++) { 1056 Typeface typeface; 1057 FontConfig.Family f = fontConfig.getFamilies()[i]; 1058 if (f.getName() != null) { 1059 if (i == 0) { 1060 // The first entry is the default typeface; no sense in 1061 // duplicating the corresponding FontFamily. 1062 typeface = sDefaultTypeface; 1063 } else { 1064 FontFamily fontFamily = makeFamilyFromParsed(f, bufferForPath); 1065 FontFamily[] families = { fontFamily }; 1066 typeface = Typeface.createFromFamiliesWithDefault(families); 1067 } 1068 systemFonts.put(f.getName(), typeface); 1069 } 1070 } 1071 for (FontConfig.Alias alias : fontConfig.getAliases()) { 1072 Typeface base = systemFonts.get(alias.getToName()); 1073 Typeface newFace = base; 1074 int weight = alias.getWeight(); 1075 if (weight != 400) { 1076 newFace = new Typeface(nativeCreateWeightAlias(base.native_instance, weight)); 1077 } 1078 systemFonts.put(alias.getName(), newFace); 1079 } 1080 sSystemFontMap = systemFonts; 1081 1082 } catch (RuntimeException e) { 1083 Log.w(TAG, "Didn't create default family (most likely, non-Minikin build)", e); 1084 // TODO: normal in non-Minikin case, remove or make error when Minikin-only 1085 } catch (FileNotFoundException e) { 1086 Log.e(TAG, "Error opening " + configFilename, e); 1087 } catch (IOException e) { 1088 Log.e(TAG, "Error reading " + configFilename, e); 1089 } catch (XmlPullParserException e) { 1090 Log.e(TAG, "XML parse exception for " + configFilename, e); 1091 } 1092 } 1093 1094 static { 1095 init(); 1096 // Set up defaults and typefaces exposed in public API 1097 DEFAULT = create((String) null, 0); 1098 DEFAULT_BOLD = create((String) null, Typeface.BOLD); 1099 SANS_SERIF = create("sans-serif", 0); 1100 SERIF = create("serif", 0); 1101 MONOSPACE = create("monospace", 0); 1102 1103 sDefaults = new Typeface[] { 1104 DEFAULT, 1105 DEFAULT_BOLD, 1106 create((String) null, Typeface.ITALIC), 1107 create((String) null, Typeface.BOLD_ITALIC), 1108 }; 1109 1110 } 1111 1112 private static File getSystemFontConfigLocation() { 1113 return new File("/system/etc/"); 1114 } 1115 1116 @Override 1117 protected void finalize() throws Throwable { 1118 try { 1119 nativeUnref(native_instance); 1120 native_instance = 0; // Other finalizers can still call us. 1121 } finally { 1122 super.finalize(); 1123 } 1124 } 1125 1126 @Override 1127 public boolean equals(Object o) { 1128 if (this == o) return true; 1129 if (o == null || getClass() != o.getClass()) return false; 1130 1131 Typeface typeface = (Typeface) o; 1132 1133 return mStyle == typeface.mStyle && native_instance == typeface.native_instance; 1134 } 1135 1136 @Override 1137 public int hashCode() { 1138 /* 1139 * Modified method for hashCode with long native_instance derived from 1140 * http://developer.android.com/reference/java/lang/Object.html 1141 */ 1142 int result = 17; 1143 result = 31 * result + (int) (native_instance ^ (native_instance >>> 32)); 1144 result = 31 * result + mStyle; 1145 return result; 1146 } 1147 1148 /** @hide */ 1149 public boolean isSupportedAxes(int axis) { 1150 if (mSupportedAxes == null) { 1151 synchronized (this) { 1152 if (mSupportedAxes == null) { 1153 mSupportedAxes = nativeGetSupportedAxes(native_instance); 1154 if (mSupportedAxes == null) { 1155 mSupportedAxes = EMPTY_AXES; 1156 } 1157 } 1158 } 1159 } 1160 return Arrays.binarySearch(mSupportedAxes, axis) > 0; 1161 } 1162 1163 private static native long nativeCreateFromTypeface(long native_instance, int style); 1164 // TODO: clean up: change List<FontConfig.Axis> to FontConfig.Axis[] 1165 private static native long nativeCreateFromTypefaceWithVariation( 1166 long native_instance, List<FontConfig.Axis> axes); 1167 private static native long nativeCreateWeightAlias(long native_instance, int weight); 1168 private static native void nativeUnref(long native_instance); 1169 private static native int nativeGetStyle(long native_instance); 1170 private static native long nativeCreateFromArray(long[] familyArray); 1171 private static native void nativeSetDefault(long native_instance); 1172 private static native int[] nativeGetSupportedAxes(long native_instance); 1173} 1174