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