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 17 package android.graphics; 18 19 import static android.content.res.FontResourcesParser.FamilyResourceEntry; 20 import static android.content.res.FontResourcesParser.FontFamilyFilesResourceEntry; 21 import static android.content.res.FontResourcesParser.FontFileResourceEntry; 22 import static android.content.res.FontResourcesParser.ProviderResourceEntry; 23 24 import android.annotation.IntDef; 25 import android.annotation.IntRange; 26 import android.annotation.NonNull; 27 import android.annotation.Nullable; 28 import android.compat.annotation.UnsupportedAppUsage; 29 import android.content.res.AssetManager; 30 import android.graphics.fonts.Font; 31 import android.graphics.fonts.FontFamily; 32 import android.graphics.fonts.FontStyle; 33 import android.graphics.fonts.FontVariationAxis; 34 import android.graphics.fonts.SystemFonts; 35 import android.os.Build; 36 import android.os.ParcelFileDescriptor; 37 import android.provider.FontRequest; 38 import android.provider.FontsContract; 39 import android.text.FontConfig; 40 import android.util.Base64; 41 import android.util.LongSparseArray; 42 import android.util.LruCache; 43 import android.util.SparseArray; 44 45 import com.android.internal.annotations.GuardedBy; 46 import com.android.internal.annotations.VisibleForTesting; 47 import com.android.internal.util.Preconditions; 48 49 import dalvik.annotation.optimization.CriticalNative; 50 51 import libcore.util.NativeAllocationRegistry; 52 53 import java.io.File; 54 import java.io.FileDescriptor; 55 import java.io.IOException; 56 import java.io.InputStream; 57 import java.lang.annotation.Retention; 58 import java.lang.annotation.RetentionPolicy; 59 import java.util.ArrayList; 60 import java.util.Arrays; 61 import java.util.Collections; 62 import java.util.HashMap; 63 import java.util.List; 64 import java.util.Map; 65 66 /** 67 * The Typeface class specifies the typeface and intrinsic style of a font. 68 * This is used in the paint, along with optionally Paint settings like 69 * textSize, textSkewX, textScaleX to specify 70 * how text appears when drawn (and measured). 71 */ 72 public class Typeface { 73 74 private static String TAG = "Typeface"; 75 76 private static final NativeAllocationRegistry sRegistry = 77 NativeAllocationRegistry.createMalloced( 78 Typeface.class.getClassLoader(), nativeGetReleaseFunc()); 79 80 /** The default NORMAL typeface object */ 81 public static final Typeface DEFAULT; 82 /** 83 * The default BOLD typeface object. Note: this may be not actually be 84 * bold, depending on what fonts are installed. Call getStyle() to know 85 * for sure. 86 */ 87 public static final Typeface DEFAULT_BOLD; 88 /** The NORMAL style of the default sans serif typeface. */ 89 public static final Typeface SANS_SERIF; 90 /** The NORMAL style of the default serif typeface. */ 91 public static final Typeface SERIF; 92 /** The NORMAL style of the default monospace typeface. */ 93 public static final Typeface MONOSPACE; 94 95 /** 96 * The default {@link Typeface}s for different text styles. 97 * Call {@link #defaultFromStyle(int)} to get the default typeface for the given text style. 98 * It shouldn't be changed for app wide typeface settings. Please use theme and font XML for 99 * the same purpose. 100 */ 101 @UnsupportedAppUsage(trackingBug = 123769446) 102 static Typeface[] sDefaults; 103 104 /** 105 * Cache for Typeface objects for style variant. Currently max size is 3. 106 */ 107 @GuardedBy("sStyledCacheLock") 108 private static final LongSparseArray<SparseArray<Typeface>> sStyledTypefaceCache = 109 new LongSparseArray<>(3); 110 private static final Object sStyledCacheLock = new Object(); 111 112 /** 113 * Cache for Typeface objects for weight variant. Currently max size is 3. 114 */ 115 @GuardedBy("sWeightCacheLock") 116 private static final LongSparseArray<SparseArray<Typeface>> sWeightTypefaceCache = 117 new LongSparseArray<>(3); 118 private static final Object sWeightCacheLock = new Object(); 119 120 /** 121 * Cache for Typeface objects dynamically loaded from assets. Currently max size is 16. 122 */ 123 @GuardedBy("sDynamicCacheLock") 124 private static final LruCache<String, Typeface> sDynamicTypefaceCache = new LruCache<>(16); 125 private static final Object sDynamicCacheLock = new Object(); 126 127 static Typeface sDefaultTypeface; 128 129 // Following two fields are not used but left for hiddenapi private list 130 /** 131 * sSystemFontMap is read only and unmodifiable. 132 * Use public API {@link #create(String, int)} to get the typeface for given familyName. 133 */ 134 @UnsupportedAppUsage(trackingBug = 123769347) 135 static final Map<String, Typeface> sSystemFontMap; 136 137 // We cannot support sSystemFallbackMap since we will migrate to public FontFamily API. 138 /** 139 * @deprecated Use {@link android.graphics.fonts.FontFamily} instead. 140 */ 141 @UnsupportedAppUsage(trackingBug = 123768928) 142 @Deprecated 143 static final Map<String, android.graphics.FontFamily[]> sSystemFallbackMap = 144 Collections.emptyMap(); 145 146 /** 147 * @hide 148 */ 149 @UnsupportedAppUsage 150 public long native_instance; 151 152 /** @hide */ 153 @IntDef(value = {NORMAL, BOLD, ITALIC, BOLD_ITALIC}) 154 @Retention(RetentionPolicy.SOURCE) 155 public @interface Style {} 156 157 // Style 158 public static final int NORMAL = 0; 159 public static final int BOLD = 1; 160 public static final int ITALIC = 2; 161 public static final int BOLD_ITALIC = 3; 162 /** @hide */ public static final int STYLE_MASK = 0x03; 163 164 @UnsupportedAppUsage 165 private @Style int mStyle = 0; 166 167 private @IntRange(from = 0, to = FontStyle.FONT_WEIGHT_MAX) int mWeight = 0; 168 169 // Value for weight and italic. Indicates the value is resolved by font metadata. 170 // Must be the same as the C++ constant in core/jni/android/graphics/FontFamily.cpp 171 /** @hide */ 172 public static final int RESOLVE_BY_FONT_TABLE = -1; 173 private static final String DEFAULT_FAMILY = "sans-serif"; 174 175 // Style value for building typeface. 176 private static final int STYLE_NORMAL = 0; 177 private static final int STYLE_ITALIC = 1; 178 179 private int[] mSupportedAxes; 180 private static final int[] EMPTY_AXES = {}; 181 182 /** 183 * Please use font in xml and also your application global theme to change the default Typeface. 184 * android:textViewStyle and its attribute android:textAppearance can be used in order to change 185 * typeface and other text related properties. 186 */ 187 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) setDefault(Typeface t)188 private static void setDefault(Typeface t) { 189 sDefaultTypeface = t; 190 nativeSetDefault(t.native_instance); 191 } 192 193 /** Returns the typeface's weight value */ getWeight()194 public @IntRange(from = 0, to = 1000) int getWeight() { 195 return mWeight; 196 } 197 198 /** Returns the typeface's intrinsic style attributes */ getStyle()199 public @Style int getStyle() { 200 return mStyle; 201 } 202 203 /** Returns true if getStyle() has the BOLD bit set. */ isBold()204 public final boolean isBold() { 205 return (mStyle & BOLD) != 0; 206 } 207 208 /** Returns true if getStyle() has the ITALIC bit set. */ isItalic()209 public final boolean isItalic() { 210 return (mStyle & ITALIC) != 0; 211 } 212 213 /** 214 * @hide 215 * Used by Resources to load a font resource of type xml. 216 */ 217 @Nullable createFromResources( FamilyResourceEntry entry, AssetManager mgr, String path)218 public static Typeface createFromResources( 219 FamilyResourceEntry entry, AssetManager mgr, String path) { 220 if (entry instanceof ProviderResourceEntry) { 221 final ProviderResourceEntry providerEntry = (ProviderResourceEntry) entry; 222 // Downloadable font 223 List<List<String>> givenCerts = providerEntry.getCerts(); 224 List<List<byte[]>> certs = new ArrayList<>(); 225 if (givenCerts != null) { 226 for (int i = 0; i < givenCerts.size(); i++) { 227 List<String> certSet = givenCerts.get(i); 228 List<byte[]> byteArraySet = new ArrayList<>(); 229 for (int j = 0; j < certSet.size(); j++) { 230 byteArraySet.add(Base64.decode(certSet.get(j), Base64.DEFAULT)); 231 } 232 certs.add(byteArraySet); 233 } 234 } 235 // Downloaded font and it wasn't cached, request it again and return a 236 // default font instead (nothing we can do now). 237 FontRequest request = new FontRequest(providerEntry.getAuthority(), 238 providerEntry.getPackage(), providerEntry.getQuery(), certs); 239 Typeface typeface = FontsContract.getFontSync(request); 240 return typeface == null ? DEFAULT : typeface; 241 } 242 243 Typeface typeface = findFromCache(mgr, path); 244 if (typeface != null) return typeface; 245 246 // family is FontFamilyFilesResourceEntry 247 final FontFamilyFilesResourceEntry filesEntry = (FontFamilyFilesResourceEntry) entry; 248 249 try { 250 FontFamily.Builder familyBuilder = null; 251 for (final FontFileResourceEntry fontFile : filesEntry.getEntries()) { 252 final Font.Builder fontBuilder = new Font.Builder(mgr, fontFile.getFileName(), 253 false /* isAsset */, 0 /* cookie */) 254 .setTtcIndex(fontFile.getTtcIndex()) 255 .setFontVariationSettings(fontFile.getVariationSettings()); 256 if (fontFile.getWeight() != Typeface.RESOLVE_BY_FONT_TABLE) { 257 fontBuilder.setWeight(fontFile.getWeight()); 258 } 259 if (fontFile.getItalic() != Typeface.RESOLVE_BY_FONT_TABLE) { 260 fontBuilder.setSlant(fontFile.getItalic() == FontFileResourceEntry.ITALIC 261 ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT); 262 } 263 264 if (familyBuilder == null) { 265 familyBuilder = new FontFamily.Builder(fontBuilder.build()); 266 } else { 267 familyBuilder.addFont(fontBuilder.build()); 268 } 269 } 270 if (familyBuilder == null) { 271 return Typeface.DEFAULT; 272 } 273 final FontFamily family = familyBuilder.build(); 274 final FontStyle normal = new FontStyle(FontStyle.FONT_WEIGHT_NORMAL, 275 FontStyle.FONT_SLANT_UPRIGHT); 276 Font bestFont = family.getFont(0); 277 int bestScore = normal.getMatchScore(bestFont.getStyle()); 278 for (int i = 1; i < family.getSize(); ++i) { 279 final Font candidate = family.getFont(i); 280 final int score = normal.getMatchScore(candidate.getStyle()); 281 if (score < bestScore) { 282 bestFont = candidate; 283 bestScore = score; 284 } 285 } 286 typeface = new Typeface.CustomFallbackBuilder(family) 287 .setStyle(bestFont.getStyle()) 288 .build(); 289 } catch (IllegalArgumentException e) { 290 // To be a compatible behavior with API28 or before, catch IllegalArgumentExcetpion 291 // thrown by native code and returns null. 292 return null; 293 } catch (IOException e) { 294 typeface = Typeface.DEFAULT; 295 } 296 synchronized (sDynamicCacheLock) { 297 final String key = Builder.createAssetUid(mgr, path, 0 /* ttcIndex */, 298 null /* axes */, RESOLVE_BY_FONT_TABLE /* weight */, 299 RESOLVE_BY_FONT_TABLE /* italic */, DEFAULT_FAMILY); 300 sDynamicTypefaceCache.put(key, typeface); 301 } 302 return typeface; 303 } 304 305 /** 306 * Used by resources for cached loading if the font is available. 307 * @hide 308 */ findFromCache(AssetManager mgr, String path)309 public static Typeface findFromCache(AssetManager mgr, String path) { 310 synchronized (sDynamicCacheLock) { 311 final String key = Builder.createAssetUid(mgr, path, 0 /* ttcIndex */, null /* axes */, 312 RESOLVE_BY_FONT_TABLE /* weight */, RESOLVE_BY_FONT_TABLE /* italic */, 313 DEFAULT_FAMILY); 314 Typeface typeface = sDynamicTypefaceCache.get(key); 315 if (typeface != null) { 316 return typeface; 317 } 318 } 319 return null; 320 } 321 322 /** 323 * A builder class for creating new Typeface instance. 324 * 325 * <p> 326 * Examples, 327 * 1) Create Typeface from ttf file. 328 * <pre> 329 * <code> 330 * Typeface.Builder buidler = new Typeface.Builder("your_font_file.ttf"); 331 * Typeface typeface = builder.build(); 332 * </code> 333 * </pre> 334 * 335 * 2) Create Typeface from ttc file in assets directory. 336 * <pre> 337 * <code> 338 * Typeface.Builder buidler = new Typeface.Builder(getAssets(), "your_font_file.ttc"); 339 * builder.setTtcIndex(2); // Set index of font collection. 340 * Typeface typeface = builder.build(); 341 * </code> 342 * </pre> 343 * 344 * 3) Create Typeface with variation settings. 345 * <pre> 346 * <code> 347 * Typeface.Builder buidler = new Typeface.Builder("your_font_file.ttf"); 348 * builder.setFontVariationSettings("'wght' 700, 'slnt' 20, 'ital' 1"); 349 * builder.setWeight(700); // Tell the system that this is a bold font. 350 * builder.setItalic(true); // Tell the system that this is an italic style font. 351 * Typeface typeface = builder.build(); 352 * </code> 353 * </pre> 354 * </p> 355 */ 356 public static final class Builder { 357 /** @hide */ 358 public static final int NORMAL_WEIGHT = 400; 359 /** @hide */ 360 public static final int BOLD_WEIGHT = 700; 361 362 // Kept for generating asset cache key. 363 private final AssetManager mAssetManager; 364 private final String mPath; 365 366 private final @Nullable Font.Builder mFontBuilder; 367 368 private String mFallbackFamilyName; 369 370 private int mWeight = RESOLVE_BY_FONT_TABLE; 371 private int mItalic = RESOLVE_BY_FONT_TABLE; 372 373 /** 374 * Constructs a builder with a file path. 375 * 376 * @param path The file object refers to the font file. 377 */ Builder(@onNull File path)378 public Builder(@NonNull File path) { 379 mFontBuilder = new Font.Builder(path); 380 mAssetManager = null; 381 mPath = null; 382 } 383 384 /** 385 * Constructs a builder with a file descriptor. 386 * 387 * Caller is responsible for closing the passed file descriptor after {@link #build} is 388 * called. 389 * 390 * @param fd The file descriptor. The passed fd must be mmap-able. 391 */ Builder(@onNull FileDescriptor fd)392 public Builder(@NonNull FileDescriptor fd) { 393 Font.Builder builder; 394 try { 395 builder = new Font.Builder(ParcelFileDescriptor.dup(fd)); 396 } catch (IOException e) { 397 // We cannot tell the error to developer at this moment since we cannot change the 398 // public API signature. Instead, silently fallbacks to system fallback in the build 399 // method as the same as other error cases. 400 builder = null; 401 } 402 mFontBuilder = builder; 403 mAssetManager = null; 404 mPath = null; 405 } 406 407 /** 408 * Constructs a builder with a file path. 409 * 410 * @param path The full path to the font file. 411 */ Builder(@onNull String path)412 public Builder(@NonNull String path) { 413 mFontBuilder = new Font.Builder(new File(path)); 414 mAssetManager = null; 415 mPath = null; 416 } 417 418 /** 419 * Constructs a builder from an asset manager and a file path in an asset directory. 420 * 421 * @param assetManager The application's asset manager 422 * @param path The file name of the font data in the asset directory 423 */ Builder(@onNull AssetManager assetManager, @NonNull String path)424 public Builder(@NonNull AssetManager assetManager, @NonNull String path) { 425 this(assetManager, path, true /* is asset */, 0 /* cookie */); 426 } 427 428 /** 429 * Constructs a builder from an asset manager and a file path in an asset directory. 430 * 431 * @param assetManager The application's asset manager 432 * @param path The file name of the font data in the asset directory 433 * @param cookie a cookie for the asset 434 * @hide 435 */ Builder(@onNull AssetManager assetManager, @NonNull String path, boolean isAsset, int cookie)436 public Builder(@NonNull AssetManager assetManager, @NonNull String path, boolean isAsset, 437 int cookie) { 438 mFontBuilder = new Font.Builder(assetManager, path, isAsset, cookie); 439 mAssetManager = assetManager; 440 mPath = path; 441 } 442 443 /** 444 * Sets weight of the font. 445 * 446 * Tells the system the weight of the given font. If not provided, the system will resolve 447 * the weight value by reading font tables. 448 * @param weight a weight value. 449 */ setWeight(@ntRangefrom = 1, to = 1000) int weight)450 public Builder setWeight(@IntRange(from = 1, to = 1000) int weight) { 451 mWeight = weight; 452 mFontBuilder.setWeight(weight); 453 return this; 454 } 455 456 /** 457 * Sets italic information of the font. 458 * 459 * Tells the system the style of the given font. If not provided, the system will resolve 460 * the style by reading font tables. 461 * @param italic {@code true} if the font is italic. Otherwise {@code false}. 462 */ setItalic(boolean italic)463 public Builder setItalic(boolean italic) { 464 mItalic = italic ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT; 465 mFontBuilder.setSlant(mItalic); 466 return this; 467 } 468 469 /** 470 * Sets an index of the font collection. See {@link android.R.attr#ttcIndex}. 471 * 472 * Can not be used for Typeface source. build() method will return null for invalid index. 473 * @param ttcIndex An index of the font collection. If the font source is not font 474 * collection, do not call this method or specify 0. 475 */ setTtcIndex(@ntRangefrom = 0) int ttcIndex)476 public Builder setTtcIndex(@IntRange(from = 0) int ttcIndex) { 477 mFontBuilder.setTtcIndex(ttcIndex); 478 return this; 479 } 480 481 /** 482 * Sets a font variation settings. 483 * 484 * @param variationSettings See {@link android.widget.TextView#setFontVariationSettings}. 485 * @throws IllegalArgumentException If given string is not a valid font variation settings 486 * format. 487 */ setFontVariationSettings(@ullable String variationSettings)488 public Builder setFontVariationSettings(@Nullable String variationSettings) { 489 mFontBuilder.setFontVariationSettings(variationSettings); 490 return this; 491 } 492 493 /** 494 * Sets a font variation settings. 495 * 496 * @param axes An array of font variation axis tag-value pairs. 497 */ setFontVariationSettings(@ullable FontVariationAxis[] axes)498 public Builder setFontVariationSettings(@Nullable FontVariationAxis[] axes) { 499 mFontBuilder.setFontVariationSettings(axes); 500 return this; 501 } 502 503 /** 504 * Sets a fallback family name. 505 * 506 * By specifying a fallback family name, a fallback Typeface will be returned if the 507 * {@link #build} method fails to create a Typeface from the provided font. The fallback 508 * family will be resolved with the provided weight and italic information specified by 509 * {@link #setWeight} and {@link #setItalic}. 510 * 511 * If {@link #setWeight} is not called, the fallback family keeps the default weight. 512 * Similary, if {@link #setItalic} is not called, the fallback family keeps the default 513 * italic information. For example, calling {@code builder.setFallback("sans-serif-light")} 514 * is equivalent to calling {@code builder.setFallback("sans-serif").setWeight(300)} in 515 * terms of fallback. The default weight and italic information are overridden by calling 516 * {@link #setWeight} and {@link #setItalic}. For example, if a Typeface is constructed 517 * using {@code builder.setFallback("sans-serif-light").setWeight(700)}, the fallback text 518 * will render as sans serif bold. 519 * 520 * @param familyName A family name to be used for fallback if the provided font can not be 521 * used. By passing {@code null}, build() returns {@code null}. 522 * If {@link #setFallback} is not called on the builder, {@code null} 523 * is assumed. 524 */ setFallback(@ullable String familyName)525 public Builder setFallback(@Nullable String familyName) { 526 mFallbackFamilyName = familyName; 527 return this; 528 } 529 530 /** 531 * Creates a unique id for a given AssetManager and asset path. 532 * 533 * @param mgr AssetManager instance 534 * @param path The path for the asset. 535 * @param ttcIndex The TTC index for the font. 536 * @param axes The font variation settings. 537 * @return Unique id for a given AssetManager and asset path. 538 */ createAssetUid(final AssetManager mgr, String path, int ttcIndex, @Nullable FontVariationAxis[] axes, int weight, int italic, String fallback)539 private static String createAssetUid(final AssetManager mgr, String path, int ttcIndex, 540 @Nullable FontVariationAxis[] axes, int weight, int italic, String fallback) { 541 final SparseArray<String> pkgs = mgr.getAssignedPackageIdentifiers(); 542 final StringBuilder builder = new StringBuilder(); 543 final int size = pkgs.size(); 544 for (int i = 0; i < size; i++) { 545 builder.append(pkgs.valueAt(i)); 546 builder.append("-"); 547 } 548 builder.append(path); 549 builder.append("-"); 550 builder.append(Integer.toString(ttcIndex)); 551 builder.append("-"); 552 builder.append(Integer.toString(weight)); 553 builder.append("-"); 554 builder.append(Integer.toString(italic)); 555 // Family name may contain hyphen. Use double hyphen for avoiding key conflicts before 556 // and after appending falblack name. 557 builder.append("--"); 558 builder.append(fallback); 559 builder.append("--"); 560 if (axes != null) { 561 for (FontVariationAxis axis : axes) { 562 builder.append(axis.getTag()); 563 builder.append("-"); 564 builder.append(Float.toString(axis.getStyleValue())); 565 } 566 } 567 return builder.toString(); 568 } 569 resolveFallbackTypeface()570 private Typeface resolveFallbackTypeface() { 571 if (mFallbackFamilyName == null) { 572 return null; 573 } 574 575 final Typeface base = getSystemDefaultTypeface(mFallbackFamilyName); 576 if (mWeight == RESOLVE_BY_FONT_TABLE && mItalic == RESOLVE_BY_FONT_TABLE) { 577 return base; 578 } 579 580 final int weight = (mWeight == RESOLVE_BY_FONT_TABLE) ? base.mWeight : mWeight; 581 final boolean italic = 582 (mItalic == RESOLVE_BY_FONT_TABLE) ? (base.mStyle & ITALIC) != 0 : mItalic == 1; 583 return createWeightStyle(base, weight, italic); 584 } 585 586 /** 587 * Generates new Typeface from specified configuration. 588 * 589 * @return Newly created Typeface. May return null if some parameters are invalid. 590 */ build()591 public Typeface build() { 592 if (mFontBuilder == null) { 593 return resolveFallbackTypeface(); 594 } 595 try { 596 final Font font = mFontBuilder.build(); 597 final String key = mAssetManager == null ? null : createAssetUid( 598 mAssetManager, mPath, font.getTtcIndex(), font.getAxes(), 599 mWeight, mItalic, 600 mFallbackFamilyName == null ? DEFAULT_FAMILY : mFallbackFamilyName); 601 if (key != null) { 602 // Dynamic cache lookup is only for assets. 603 synchronized (sDynamicCacheLock) { 604 final Typeface typeface = sDynamicTypefaceCache.get(key); 605 if (typeface != null) { 606 return typeface; 607 } 608 } 609 } 610 final FontFamily family = new FontFamily.Builder(font).build(); 611 final int weight = mWeight == RESOLVE_BY_FONT_TABLE 612 ? font.getStyle().getWeight() : mWeight; 613 final int slant = mItalic == RESOLVE_BY_FONT_TABLE 614 ? font.getStyle().getSlant() : mItalic; 615 final CustomFallbackBuilder builder = new CustomFallbackBuilder(family) 616 .setStyle(new FontStyle(weight, slant)); 617 if (mFallbackFamilyName != null) { 618 builder.setSystemFallback(mFallbackFamilyName); 619 } 620 final Typeface typeface = builder.build(); 621 if (key != null) { 622 synchronized (sDynamicCacheLock) { 623 sDynamicTypefaceCache.put(key, typeface); 624 } 625 } 626 return typeface; 627 } catch (IOException | IllegalArgumentException e) { 628 return resolveFallbackTypeface(); 629 } 630 } 631 } 632 633 /** 634 * A builder class for creating new Typeface instance. 635 * 636 * There are two font fallback mechanisms, custom font fallback and system font fallback. 637 * The custom font fallback is a simple ordered list. The text renderer tries to see if it can 638 * render a character with the first font and if that font does not support the character, try 639 * next one and so on. It will keep trying until end of the custom fallback chain. The maximum 640 * length of the custom fallback chain is 64. 641 * The system font fallback is a system pre-defined fallback chain. The system fallback is 642 * processed only when no matching font is found in the custom font fallback. 643 * 644 * <p> 645 * Examples, 646 * 1) Create Typeface from single ttf file. 647 * <pre> 648 * <code> 649 * Font font = new Font.Builder("your_font_file.ttf").build(); 650 * FontFamily family = new FontFamily.Builder(font).build(); 651 * Typeface typeface = new Typeface.CustomFallbackBuilder(family).build(); 652 * </code> 653 * </pre> 654 * 655 * 2) Create Typeface from multiple font files and select bold style by default. 656 * <pre> 657 * <code> 658 * Font regularFont = new Font.Builder("regular.ttf").build(); 659 * Font boldFont = new Font.Builder("bold.ttf").build(); 660 * FontFamily family = new FontFamily.Builder(regularFont) 661 * .addFont(boldFont).build(); 662 * Typeface typeface = new Typeface.CustomFallbackBuilder(family) 663 * .setWeight(Font.FONT_WEIGHT_BOLD) // Set bold style as the default style. 664 * // If the font family doesn't have bold style font, 665 * // system will select the closest font. 666 * .build(); 667 * </code> 668 * </pre> 669 * 670 * 3) Create Typeface from single ttf file and if that font does not have glyph for the 671 * characters, use "serif" font family instead. 672 * <pre> 673 * <code> 674 * Font font = new Font.Builder("your_font_file.ttf").build(); 675 * FontFamily family = new FontFamily.Builder(font).build(); 676 * Typeface typeface = new Typeface.CustomFallbackBuilder(family) 677 * .setSystemFallback("serif") // Set serif font family as the fallback. 678 * .build(); 679 * </code> 680 * </pre> 681 * 4) Create Typeface from single ttf file and set another ttf file for the fallback. 682 * <pre> 683 * <code> 684 * Font font = new Font.Builder("English.ttf").build(); 685 * FontFamily family = new FontFamily.Builder(font).build(); 686 * 687 * Font fallbackFont = new Font.Builder("Arabic.ttf").build(); 688 * FontFamily fallbackFamily = new FontFamily.Builder(fallbackFont).build(); 689 * Typeface typeface = new Typeface.CustomFallbackBuilder(family) 690 * .addCustomFallback(fallbackFamily) // Specify fallback family. 691 * .setSystemFallback("serif") // Set serif font family as the fallback. 692 * .build(); 693 * </code> 694 * </pre> 695 * </p> 696 */ 697 public static final class CustomFallbackBuilder { 698 private static final int MAX_CUSTOM_FALLBACK = 64; 699 private final ArrayList<FontFamily> mFamilies = new ArrayList<>(); 700 private String mFallbackName = null; 701 private @Nullable FontStyle mStyle; 702 703 /** 704 * Returns the maximum capacity of custom fallback families. 705 * 706 * This includes the the first font family passed to the constructor. 707 * It is guaranteed that the value will be greater than or equal to 64. 708 * 709 * @return the maximum number of font families for the custom fallback 710 */ getMaxCustomFallbackCount()711 public static @IntRange(from = 64) int getMaxCustomFallbackCount() { 712 return MAX_CUSTOM_FALLBACK; 713 } 714 715 /** 716 * Constructs a builder with a font family. 717 * 718 * @param family a family object 719 */ CustomFallbackBuilder(@onNull FontFamily family)720 public CustomFallbackBuilder(@NonNull FontFamily family) { 721 Preconditions.checkNotNull(family); 722 mFamilies.add(family); 723 } 724 725 /** 726 * Sets a system fallback by name. 727 * 728 * You can specify generic font familiy names or OEM specific family names. If the system 729 * don't have a specified fallback, the default fallback is used instead. 730 * For more information about generic font families, see <a 731 * href="https://www.w3.org/TR/css-fonts-4/#generic-font-families">CSS specification</a> 732 * 733 * For more information about fallback, see class description. 734 * 735 * @param familyName a family name to be used for fallback if the provided fonts can not be 736 * used 737 */ setSystemFallback(@onNull String familyName)738 public @NonNull CustomFallbackBuilder setSystemFallback(@NonNull String familyName) { 739 Preconditions.checkNotNull(familyName); 740 mFallbackName = familyName; 741 return this; 742 } 743 744 /** 745 * Sets a font style of the Typeface. 746 * 747 * If the font family doesn't have a font of given style, system will select the closest 748 * font from font family. For example, if a font family has fonts of 300 weight and 700 749 * weight then setWeight(400) is called, system will select the font of 300 weight. 750 * 751 * @param style a font style 752 */ setStyle(@onNull FontStyle style)753 public @NonNull CustomFallbackBuilder setStyle(@NonNull FontStyle style) { 754 mStyle = style; 755 return this; 756 } 757 758 /** 759 * Append a font family to the end of the custom font fallback. 760 * 761 * You can set up to 64 custom fallback families including the first font family you passed 762 * to the constructor. 763 * For more information about fallback, see class description. 764 * 765 * @param family a fallback family 766 * @throws IllegalArgumentException if you give more than 64 custom fallback families 767 */ addCustomFallback(@onNull FontFamily family)768 public @NonNull CustomFallbackBuilder addCustomFallback(@NonNull FontFamily family) { 769 Preconditions.checkNotNull(family); 770 Preconditions.checkArgument(mFamilies.size() < getMaxCustomFallbackCount(), 771 "Custom fallback limit exceeded(" + getMaxCustomFallbackCount() + ")"); 772 mFamilies.add(family); 773 return this; 774 } 775 776 /** 777 * Create the Typeface based on the configured values. 778 * 779 * @return the Typeface object 780 */ 781 public @NonNull Typeface build() { 782 final int userFallbackSize = mFamilies.size(); 783 final FontFamily[] fallback = SystemFonts.getSystemFallback(mFallbackName); 784 final long[] ptrArray = new long[fallback.length + userFallbackSize]; 785 for (int i = 0; i < userFallbackSize; ++i) { 786 ptrArray[i] = mFamilies.get(i).getNativePtr(); 787 } 788 for (int i = 0; i < fallback.length; ++i) { 789 ptrArray[i + userFallbackSize] = fallback[i].getNativePtr(); 790 } 791 final int weight = mStyle == null ? 400 : mStyle.getWeight(); 792 final int italic = 793 (mStyle == null || mStyle.getSlant() == FontStyle.FONT_SLANT_UPRIGHT) ? 0 : 1; 794 return new Typeface(nativeCreateFromArray(ptrArray, weight, italic)); 795 } 796 } 797 798 /** 799 * Create a typeface object given a family name, and option style information. 800 * If null is passed for the name, then the "default" font will be chosen. 801 * The resulting typeface object can be queried (getStyle()) to discover what 802 * its "real" style characteristics are. 803 * 804 * @param familyName May be null. The name of the font family. 805 * @param style The style (normal, bold, italic) of the typeface. 806 * e.g. NORMAL, BOLD, ITALIC, BOLD_ITALIC 807 * @return The best matching typeface. 808 */ 809 public static Typeface create(String familyName, @Style int style) { 810 return create(getSystemDefaultTypeface(familyName), style); 811 } 812 813 /** 814 * Create a typeface object that best matches the specified existing 815 * typeface and the specified Style. Use this call if you want to pick a new 816 * style from the same family of an existing typeface object. If family is 817 * null, this selects from the default font's family. 818 * 819 * <p> 820 * This method is not thread safe on API 27 or before. 821 * This method is thread safe on API 28 or after. 822 * </p> 823 * 824 * @param family An existing {@link Typeface} object. In case of {@code null}, the default 825 * typeface is used instead. 826 * @param style The style (normal, bold, italic) of the typeface. 827 * e.g. NORMAL, BOLD, ITALIC, BOLD_ITALIC 828 * @return The best matching typeface. 829 */ 830 public static Typeface create(Typeface family, @Style int style) { 831 if ((style & ~STYLE_MASK) != 0) { 832 style = NORMAL; 833 } 834 if (family == null) { 835 family = sDefaultTypeface; 836 } 837 838 // Return early if we're asked for the same face/style 839 if (family.mStyle == style) { 840 return family; 841 } 842 843 final long ni = family.native_instance; 844 845 Typeface typeface; 846 synchronized (sStyledCacheLock) { 847 SparseArray<Typeface> styles = sStyledTypefaceCache.get(ni); 848 849 if (styles == null) { 850 styles = new SparseArray<Typeface>(4); 851 sStyledTypefaceCache.put(ni, styles); 852 } else { 853 typeface = styles.get(style); 854 if (typeface != null) { 855 return typeface; 856 } 857 } 858 859 typeface = new Typeface(nativeCreateFromTypeface(ni, style)); 860 styles.put(style, typeface); 861 } 862 return typeface; 863 } 864 865 /** 866 * Creates a typeface object that best matches the specified existing typeface and the specified 867 * weight and italic style 868 * <p>Below are numerical values and corresponding common weight names.</p> 869 * <table> 870 * <thead> 871 * <tr><th>Value</th><th>Common weight name</th></tr> 872 * </thead> 873 * <tbody> 874 * <tr><td>100</td><td>Thin</td></tr> 875 * <tr><td>200</td><td>Extra Light</td></tr> 876 * <tr><td>300</td><td>Light</td></tr> 877 * <tr><td>400</td><td>Normal</td></tr> 878 * <tr><td>500</td><td>Medium</td></tr> 879 * <tr><td>600</td><td>Semi Bold</td></tr> 880 * <tr><td>700</td><td>Bold</td></tr> 881 * <tr><td>800</td><td>Extra Bold</td></tr> 882 * <tr><td>900</td><td>Black</td></tr> 883 * </tbody> 884 * </table> 885 * 886 * <p> 887 * This method is thread safe. 888 * </p> 889 * 890 * @param family An existing {@link Typeface} object. In case of {@code null}, the default 891 * typeface is used instead. 892 * @param weight The desired weight to be drawn. 893 * @param italic {@code true} if italic style is desired to be drawn. Otherwise, {@code false} 894 * @return A {@link Typeface} object for drawing specified weight and italic style. Never 895 * returns {@code null} 896 * 897 * @see #getWeight() 898 * @see #isItalic() 899 */ 900 public static @NonNull Typeface create(@Nullable Typeface family, 901 @IntRange(from = 1, to = 1000) int weight, boolean italic) { 902 Preconditions.checkArgumentInRange(weight, 0, 1000, "weight"); 903 if (family == null) { 904 family = sDefaultTypeface; 905 } 906 return createWeightStyle(family, weight, italic); 907 } 908 909 private static @NonNull Typeface createWeightStyle(@NonNull Typeface base, 910 @IntRange(from = 1, to = 1000) int weight, boolean italic) { 911 final int key = (weight << 1) | (italic ? 1 : 0); 912 913 Typeface typeface; 914 synchronized(sWeightCacheLock) { 915 SparseArray<Typeface> innerCache = sWeightTypefaceCache.get(base.native_instance); 916 if (innerCache == null) { 917 innerCache = new SparseArray<>(4); 918 sWeightTypefaceCache.put(base.native_instance, innerCache); 919 } else { 920 typeface = innerCache.get(key); 921 if (typeface != null) { 922 return typeface; 923 } 924 } 925 926 typeface = new Typeface( 927 nativeCreateFromTypefaceWithExactStyle(base.native_instance, weight, italic)); 928 innerCache.put(key, typeface); 929 } 930 return typeface; 931 } 932 933 /** @hide */ 934 public static Typeface createFromTypefaceWithVariation(@Nullable Typeface family, 935 @NonNull List<FontVariationAxis> axes) { 936 final Typeface base = family == null ? Typeface.DEFAULT : family; 937 return new Typeface(nativeCreateFromTypefaceWithVariation(base.native_instance, axes)); 938 } 939 940 /** 941 * Returns one of the default typeface objects, based on the specified style 942 * 943 * @return the default typeface that corresponds to the style 944 */ 945 public static Typeface defaultFromStyle(@Style int style) { 946 return sDefaults[style]; 947 } 948 949 /** 950 * Create a new typeface from the specified font data. 951 * 952 * @param mgr The application's asset manager 953 * @param path The file name of the font data in the assets directory 954 * @return The new typeface. 955 */ 956 public static Typeface createFromAsset(AssetManager mgr, String path) { 957 Preconditions.checkNotNull(path); // for backward compatibility 958 Preconditions.checkNotNull(mgr); 959 960 Typeface typeface = new Builder(mgr, path).build(); 961 if (typeface != null) return typeface; 962 // check if the file exists, and throw an exception for backward compatibility 963 try (InputStream inputStream = mgr.open(path)) { 964 } catch (IOException e) { 965 throw new RuntimeException("Font asset not found " + path); 966 } 967 968 return Typeface.DEFAULT; 969 } 970 971 /** 972 * Creates a unique id for a given font provider and query. 973 */ 974 private static String createProviderUid(String authority, String query) { 975 final StringBuilder builder = new StringBuilder(); 976 builder.append("provider:"); 977 builder.append(authority); 978 builder.append("-"); 979 builder.append(query); 980 return builder.toString(); 981 } 982 983 /** 984 * Create a new typeface from the specified font file. 985 * 986 * @param file The path to the font data. 987 * @return The new typeface. 988 */ 989 public static Typeface createFromFile(@Nullable File file) { 990 // For the compatibility reasons, leaving possible NPE here. 991 // See android.graphics.cts.TypefaceTest#testCreateFromFileByFileReferenceNull 992 993 Typeface typeface = new Builder(file).build(); 994 if (typeface != null) return typeface; 995 996 // check if the file exists, and throw an exception for backward compatibility 997 if (!file.exists()) { 998 throw new RuntimeException("Font asset not found " + file.getAbsolutePath()); 999 } 1000 1001 return Typeface.DEFAULT; 1002 } 1003 1004 /** 1005 * Create a new typeface from the specified font file. 1006 * 1007 * @param path The full path to the font data. 1008 * @return The new typeface. 1009 */ 1010 public static Typeface createFromFile(@Nullable String path) { 1011 Preconditions.checkNotNull(path); // for backward compatibility 1012 return createFromFile(new File(path)); 1013 } 1014 1015 /** 1016 * Create a new typeface from an array of font families. 1017 * 1018 * @param families array of font families 1019 * @deprecated 1020 */ 1021 @Deprecated 1022 @UnsupportedAppUsage(trackingBug = 123768928) 1023 private static Typeface createFromFamilies(android.graphics.FontFamily[] families) { 1024 long[] ptrArray = new long[families.length]; 1025 for (int i = 0; i < families.length; i++) { 1026 ptrArray[i] = families[i].mNativePtr; 1027 } 1028 return new Typeface(nativeCreateFromArray( 1029 ptrArray, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE)); 1030 } 1031 1032 /** 1033 * Create a new typeface from an array of android.graphics.fonts.FontFamily. 1034 * 1035 * @param families array of font families 1036 */ 1037 private static Typeface createFromFamilies(@Nullable FontFamily[] families) { 1038 final long[] ptrArray = new long[families.length]; 1039 for (int i = 0; i < families.length; ++i) { 1040 ptrArray[i] = families[i].getNativePtr(); 1041 } 1042 return new Typeface(nativeCreateFromArray(ptrArray, 1043 RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE)); 1044 } 1045 1046 /** 1047 * This method is used by supportlib-v27. 1048 * 1049 * @deprecated Use {@link android.graphics.fonts.FontFamily} instead. 1050 */ 1051 @UnsupportedAppUsage(trackingBug = 123768395) 1052 @Deprecated 1053 private static Typeface createFromFamiliesWithDefault( 1054 android.graphics.FontFamily[] families, int weight, int italic) { 1055 return createFromFamiliesWithDefault(families, DEFAULT_FAMILY, weight, italic); 1056 } 1057 1058 /** 1059 * Create a new typeface from an array of font families, including 1060 * also the font families in the fallback list. 1061 * @param fallbackName the family name. If given families don't support characters, the 1062 * characters will be rendered with this family. 1063 * @param weight the weight for this family. In that case, the table information in the first 1064 * family's font is used. If the first family has multiple fonts, the closest to 1065 * the regular weight and upright font is used. 1066 * @param italic the italic information for this family. In that case, the table information in 1067 * the first family's font is used. If the first family has multiple fonts, the 1068 * closest to the regular weight and upright font is used. 1069 * @param families array of font families 1070 * 1071 * @deprecated Use {@link android.graphics.fonts.FontFamily} instead. 1072 */ 1073 @UnsupportedAppUsage(trackingBug = 123768928) 1074 @Deprecated 1075 private static Typeface createFromFamiliesWithDefault(android.graphics.FontFamily[] families, 1076 String fallbackName, int weight, int italic) { 1077 android.graphics.fonts.FontFamily[] fallback = SystemFonts.getSystemFallback(fallbackName); 1078 long[] ptrArray = new long[families.length + fallback.length]; 1079 for (int i = 0; i < families.length; i++) { 1080 ptrArray[i] = families[i].mNativePtr; 1081 } 1082 for (int i = 0; i < fallback.length; i++) { 1083 ptrArray[i + families.length] = fallback[i].getNativePtr(); 1084 } 1085 return new Typeface(nativeCreateFromArray(ptrArray, weight, italic)); 1086 } 1087 1088 // don't allow clients to call this directly 1089 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) 1090 private Typeface(long ni) { 1091 if (ni == 0) { 1092 throw new RuntimeException("native typeface cannot be made"); 1093 } 1094 1095 native_instance = ni; 1096 sRegistry.registerNativeAllocation(this, native_instance); 1097 mStyle = nativeGetStyle(ni); 1098 mWeight = nativeGetWeight(ni); 1099 } 1100 1101 private static Typeface getSystemDefaultTypeface(@NonNull String familyName) { 1102 Typeface tf = sSystemFontMap.get(familyName); 1103 return tf == null ? Typeface.DEFAULT : tf; 1104 } 1105 1106 /** @hide */ 1107 @VisibleForTesting 1108 public static void initSystemDefaultTypefaces(Map<String, Typeface> systemFontMap, 1109 Map<String, FontFamily[]> fallbacks, 1110 FontConfig.Alias[] aliases) { 1111 for (Map.Entry<String, FontFamily[]> entry : fallbacks.entrySet()) { 1112 systemFontMap.put(entry.getKey(), createFromFamilies(entry.getValue())); 1113 } 1114 1115 for (FontConfig.Alias alias : aliases) { 1116 if (systemFontMap.containsKey(alias.getName())) { 1117 continue; // If alias and named family are conflict, use named family. 1118 } 1119 final Typeface base = systemFontMap.get(alias.getToName()); 1120 if (base == null) { 1121 // The missing target is a valid thing, some configuration don't have font files, 1122 // e.g. wear devices. Just skip this alias. 1123 continue; 1124 } 1125 final int weight = alias.getWeight(); 1126 final Typeface newFace = weight == 400 ? base : 1127 new Typeface(nativeCreateWeightAlias(base.native_instance, weight)); 1128 systemFontMap.put(alias.getName(), newFace); 1129 } 1130 } 1131 1132 private static void registerGenericFamilyNative(@NonNull String familyName, 1133 @Nullable Typeface typeface) { 1134 if (typeface != null) { 1135 nativeRegisterGenericFamily(familyName, typeface.native_instance); 1136 } 1137 } 1138 1139 static { 1140 final HashMap<String, Typeface> systemFontMap = new HashMap<>(); 1141 initSystemDefaultTypefaces(systemFontMap, SystemFonts.getRawSystemFallbackMap(), 1142 SystemFonts.getAliases()); 1143 sSystemFontMap = Collections.unmodifiableMap(systemFontMap); 1144 1145 // We can't assume DEFAULT_FAMILY available on Roboletric. 1146 if (sSystemFontMap.containsKey(DEFAULT_FAMILY)) { 1147 setDefault(sSystemFontMap.get(DEFAULT_FAMILY)); 1148 } 1149 1150 // Set up defaults and typefaces exposed in public API 1151 DEFAULT = create((String) null, 0); 1152 DEFAULT_BOLD = create((String) null, Typeface.BOLD); 1153 SANS_SERIF = create("sans-serif", 0); 1154 SERIF = create("serif", 0); 1155 MONOSPACE = create("monospace", 0); 1156 1157 sDefaults = new Typeface[] { 1158 DEFAULT, 1159 DEFAULT_BOLD, 1160 create((String) null, Typeface.ITALIC), 1161 create((String) null, Typeface.BOLD_ITALIC), 1162 }; 1163 1164 // A list of generic families to be registered in native. 1165 // https://www.w3.org/TR/css-fonts-4/#generic-font-families 1166 String[] genericFamilies = { 1167 "serif", "sans-serif", "cursive", "fantasy", "monospace", "system-ui" 1168 }; 1169 1170 for (String genericFamily : genericFamilies) { 1171 registerGenericFamilyNative(genericFamily, systemFontMap.get(genericFamily)); 1172 } 1173 } 1174 1175 @Override 1176 public boolean equals(Object o) { 1177 if (this == o) return true; 1178 if (o == null || getClass() != o.getClass()) return false; 1179 1180 Typeface typeface = (Typeface) o; 1181 1182 return mStyle == typeface.mStyle && native_instance == typeface.native_instance; 1183 } 1184 1185 @Override 1186 public int hashCode() { 1187 /* 1188 * Modified method for hashCode with long native_instance derived from 1189 * http://developer.android.com/reference/java/lang/Object.html 1190 */ 1191 int result = 17; 1192 result = 31 * result + (int) (native_instance ^ (native_instance >>> 32)); 1193 result = 31 * result + mStyle; 1194 return result; 1195 } 1196 1197 /** @hide */ 1198 public boolean isSupportedAxes(int axis) { 1199 if (mSupportedAxes == null) { 1200 synchronized (this) { 1201 if (mSupportedAxes == null) { 1202 mSupportedAxes = nativeGetSupportedAxes(native_instance); 1203 if (mSupportedAxes == null) { 1204 mSupportedAxes = EMPTY_AXES; 1205 } 1206 } 1207 } 1208 } 1209 return Arrays.binarySearch(mSupportedAxes, axis) >= 0; 1210 } 1211 1212 private static native long nativeCreateFromTypeface(long native_instance, int style); 1213 private static native long nativeCreateFromTypefaceWithExactStyle( 1214 long native_instance, int weight, boolean italic); 1215 // TODO: clean up: change List<FontVariationAxis> to FontVariationAxis[] 1216 private static native long nativeCreateFromTypefaceWithVariation( 1217 long native_instance, List<FontVariationAxis> axes); 1218 @UnsupportedAppUsage 1219 private static native long nativeCreateWeightAlias(long native_instance, int weight); 1220 @UnsupportedAppUsage 1221 private static native long nativeCreateFromArray(long[] familyArray, int weight, int italic); 1222 private static native int[] nativeGetSupportedAxes(long native_instance); 1223 1224 @CriticalNative 1225 private static native void nativeSetDefault(long nativePtr); 1226 1227 @CriticalNative 1228 private static native int nativeGetStyle(long nativePtr); 1229 1230 @CriticalNative 1231 private static native int nativeGetWeight(long nativePtr); 1232 1233 @CriticalNative 1234 private static native long nativeGetReleaseFunc(); 1235 1236 private static native void nativeRegisterGenericFamily(String str, long nativePtr); 1237 } 1238