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