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.annotation.TestApi; 29 import android.annotation.UiThread; 30 import android.compat.annotation.UnsupportedAppUsage; 31 import android.content.res.AssetManager; 32 import android.graphics.fonts.Font; 33 import android.graphics.fonts.FontFamily; 34 import android.graphics.fonts.FontStyle; 35 import android.graphics.fonts.FontVariationAxis; 36 import android.graphics.fonts.SystemFonts; 37 import android.icu.util.ULocale; 38 import android.os.Build; 39 import android.os.ParcelFileDescriptor; 40 import android.os.SharedMemory; 41 import android.os.SystemProperties; 42 import android.os.Trace; 43 import android.provider.FontRequest; 44 import android.provider.FontsContract; 45 import android.ravenwood.annotation.RavenwoodReplace; 46 import android.system.ErrnoException; 47 import android.system.OsConstants; 48 import android.text.FontConfig; 49 import android.util.ArrayMap; 50 import android.util.Base64; 51 import android.util.Log; 52 import android.util.LongSparseArray; 53 import android.util.LruCache; 54 import android.util.Pair; 55 import android.util.SparseArray; 56 57 import com.android.internal.annotations.GuardedBy; 58 import com.android.internal.annotations.VisibleForTesting; 59 import com.android.internal.util.Preconditions; 60 import com.android.text.flags.Flags; 61 62 import dalvik.annotation.optimization.CriticalNative; 63 import dalvik.annotation.optimization.FastNative; 64 65 import libcore.util.NativeAllocationRegistry; 66 67 import java.io.ByteArrayOutputStream; 68 import java.io.File; 69 import java.io.FileDescriptor; 70 import java.io.IOException; 71 import java.io.InputStream; 72 import java.lang.annotation.Retention; 73 import java.lang.annotation.RetentionPolicy; 74 import java.nio.ByteBuffer; 75 import java.nio.ByteOrder; 76 import java.util.ArrayList; 77 import java.util.Arrays; 78 import java.util.Collections; 79 import java.util.Comparator; 80 import java.util.List; 81 import java.util.Map; 82 import java.util.Objects; 83 import java.util.function.BiConsumer; 84 85 /** 86 * The Typeface class specifies the typeface and intrinsic style of a font. 87 * This is used in the paint, along with optionally Paint settings like 88 * textSize, textSkewX, textScaleX to specify 89 * how text appears when drawn (and measured). 90 */ 91 @android.ravenwood.annotation.RavenwoodKeepWholeClass 92 public class Typeface { 93 94 private static String TAG = "Typeface"; 95 96 /** @hide */ 97 public static final boolean ENABLE_LAZY_TYPEFACE_INITIALIZATION = true; 98 99 private static class NoImagePreloadHolder { 100 static final NativeAllocationRegistry sRegistry = 101 NativeAllocationRegistry.createMalloced( 102 Typeface.class.getClassLoader(), nativeGetReleaseFunc()); 103 } 104 105 /** The default NORMAL typeface object */ 106 public static final Typeface DEFAULT = null; 107 /** 108 * The default BOLD typeface object. Note: this may be not actually be 109 * bold, depending on what fonts are installed. Call getStyle() to know 110 * for sure. 111 */ 112 public static final Typeface DEFAULT_BOLD = null; 113 /** The NORMAL style of the default sans serif typeface. */ 114 public static final Typeface SANS_SERIF = null; 115 /** The NORMAL style of the default serif typeface. */ 116 public static final Typeface SERIF = null; 117 /** The NORMAL style of the default monospace typeface. */ 118 public static final Typeface MONOSPACE = null; 119 120 /** 121 * The default {@link Typeface}s for different text styles. 122 * Call {@link #defaultFromStyle(int)} to get the default typeface for the given text style. 123 * It shouldn't be changed for app wide typeface settings. Please use theme and font XML for 124 * the same purpose. 125 */ 126 @GuardedBy("SYSTEM_FONT_MAP_LOCK") 127 @UnsupportedAppUsage(trackingBug = 123769446) 128 static Typeface[] sDefaults; 129 130 /** 131 * Cache for Typeface objects for style variant. Currently max size is 3. 132 */ 133 @GuardedBy("sStyledCacheLock") 134 private static final LongSparseArray<SparseArray<Typeface>> sStyledTypefaceCache = 135 new LongSparseArray<>(3); 136 private static final Object sStyledCacheLock = new Object(); 137 138 /** 139 * Cache for Typeface objects for weight variant. Currently max size is 3. 140 */ 141 @GuardedBy("sWeightCacheLock") 142 private static final LongSparseArray<SparseArray<Typeface>> sWeightTypefaceCache = 143 new LongSparseArray<>(3); 144 private static final Object sWeightCacheLock = new Object(); 145 146 /** 147 * Cache for Typeface objects dynamically loaded from assets. Currently max size is 16. 148 */ 149 @GuardedBy("sDynamicCacheLock") 150 private static final LruCache<String, Typeface> sDynamicTypefaceCache = new LruCache<>(16); 151 private static final Object sDynamicCacheLock = new Object(); 152 153 private static final LruCache<Long, LruCache<String, Typeface>> sVariableCache = 154 new LruCache<>(16); 155 private static final Object sVariableCacheLock = new Object(); 156 157 /** @hide */ 158 @VisibleForTesting clearTypefaceCachesForTestingPurpose()159 public static void clearTypefaceCachesForTestingPurpose() { 160 synchronized (sWeightCacheLock) { 161 sWeightTypefaceCache.clear(); 162 } 163 synchronized (sDynamicCacheLock) { 164 sDynamicTypefaceCache.evictAll(); 165 } 166 synchronized (sVariableCacheLock) { 167 sVariableCache.evictAll(); 168 } 169 } 170 171 @GuardedBy("SYSTEM_FONT_MAP_LOCK") 172 static Typeface sDefaultTypeface; 173 174 /** 175 * sSystemFontMap is read only and unmodifiable. 176 * Use public API {@link #create(String, int)} to get the typeface for given familyName. 177 */ 178 @GuardedBy("SYSTEM_FONT_MAP_LOCK") 179 @UnsupportedAppUsage(trackingBug = 123769347) 180 static final Map<String, Typeface> sSystemFontMap = new ArrayMap<>(); 181 182 // DirectByteBuffer object to hold sSystemFontMap's backing memory mapping. 183 static ByteBuffer sSystemFontMapBuffer = null; 184 static SharedMemory sSystemFontMapSharedMemory = null; 185 186 // Lock to guard sSystemFontMap and derived default or public typefaces. 187 // sStyledCacheLock may be held while this lock is held. Holding them in the reverse order may 188 // introduce deadlock. 189 private static final Object SYSTEM_FONT_MAP_LOCK = new Object(); 190 191 // This field is used but left for hiddenapi private list 192 // We cannot support sSystemFallbackMap since we will migrate to public FontFamily API. 193 /** 194 * @deprecated Use {@link android.graphics.fonts.FontFamily} instead. 195 */ 196 @UnsupportedAppUsage(trackingBug = 123768928) 197 @Deprecated 198 static final Map<String, android.graphics.FontFamily[]> sSystemFallbackMap = 199 Collections.emptyMap(); 200 201 /** 202 * Returns the shared memory that used for creating Typefaces. 203 * 204 * @return A SharedMemory used for creating Typeface. Maybe null if the lazy initialization is 205 * disabled or inside SystemServer or Zygote. 206 * @hide 207 */ 208 @TestApi getSystemFontMapSharedMemory()209 public static @Nullable SharedMemory getSystemFontMapSharedMemory() { 210 if (ENABLE_LAZY_TYPEFACE_INITIALIZATION) { 211 Objects.requireNonNull(sSystemFontMapSharedMemory); 212 } 213 return sSystemFontMapSharedMemory; 214 } 215 216 /** 217 * @hide 218 */ 219 @UnsupportedAppUsage 220 public final long native_instance; 221 222 private final Typeface mDerivedFrom; 223 224 private final String mSystemFontFamilyName; 225 226 private final Runnable mCleaner; 227 228 /** @hide */ 229 @IntDef(value = {NORMAL, BOLD, ITALIC, BOLD_ITALIC}) 230 @Retention(RetentionPolicy.SOURCE) 231 public @interface Style {} 232 233 // Style 234 public static final int NORMAL = 0; 235 public static final int BOLD = 1; 236 public static final int ITALIC = 2; 237 public static final int BOLD_ITALIC = 3; 238 /** @hide */ public static final int STYLE_MASK = 0x03; 239 240 @UnsupportedAppUsage 241 private @Style final int mStyle; 242 243 private @IntRange(from = 0, to = FontStyle.FONT_WEIGHT_MAX) final int mWeight; 244 245 private boolean mIsVariationInstance; 246 247 // Value for weight and italic. Indicates the value is resolved by font metadata. 248 // Must be the same as the C++ constant in core/jni/android/graphics/FontFamily.cpp 249 /** @hide */ 250 public static final int RESOLVE_BY_FONT_TABLE = -1; 251 /** 252 * The key of the default font family. 253 * @hide 254 */ 255 public static final String DEFAULT_FAMILY = "sans-serif"; 256 257 // Style value for building typeface. 258 private static final int STYLE_NORMAL = 0; 259 private static final int STYLE_ITALIC = 1; 260 261 @GuardedBy("this") 262 private int[] mSupportedAxes; 263 private static final int[] EMPTY_AXES = {}; 264 265 /** 266 * Please use font in xml and also your application global theme to change the default Typeface. 267 * android:textViewStyle and its attribute android:textAppearance can be used in order to change 268 * typeface and other text related properties. 269 */ 270 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) setDefault(Typeface t)271 private static void setDefault(Typeface t) { 272 synchronized (SYSTEM_FONT_MAP_LOCK) { 273 sDefaultTypeface = t; 274 nativeSetDefault(t.native_instance); 275 } 276 } 277 getDefault()278 private static Typeface getDefault() { 279 synchronized (SYSTEM_FONT_MAP_LOCK) { 280 return sDefaultTypeface; 281 } 282 } 283 284 /** Returns the typeface's weight value */ getWeight()285 public @IntRange(from = 0, to = 1000) int getWeight() { 286 return mWeight; 287 } 288 289 /** @hide */ isVariationInstance()290 public boolean isVariationInstance() { 291 return mIsVariationInstance; 292 } 293 294 /** Returns the typeface's intrinsic style attributes */ getStyle()295 public @Style int getStyle() { 296 return mStyle; 297 } 298 299 /** Returns true if getStyle() has the BOLD bit set. */ isBold()300 public final boolean isBold() { 301 return (mStyle & BOLD) != 0; 302 } 303 304 /** Returns true if getStyle() has the ITALIC bit set. */ isItalic()305 public final boolean isItalic() { 306 return (mStyle & ITALIC) != 0; 307 } 308 309 /** 310 * Returns the Typeface used for creating this Typeface. 311 * 312 * Maybe null if this is not derived from other Typeface. 313 * TODO(b/357707916): Make this public API. 314 * @hide 315 */ 316 @VisibleForTesting getDerivedFrom()317 public final @Nullable Typeface getDerivedFrom() { 318 return mDerivedFrom; 319 } 320 321 /** 322 * Returns the system font family name if the typeface was created from a system font family, 323 * otherwise returns null. 324 */ getSystemFontFamilyName()325 public final @Nullable String getSystemFontFamilyName() { 326 return mSystemFontFamilyName; 327 } 328 329 /** 330 * Returns true if the system has the font family with the name [familyName]. For example 331 * querying with "sans-serif" would check if the "sans-serif" family is defined in the system 332 * and return true if does. 333 * 334 * @param familyName The name of the font family, cannot be null. If null, exception will be 335 * thrown. 336 */ hasFontFamily(@onNull String familyName)337 private static boolean hasFontFamily(@NonNull String familyName) { 338 Objects.requireNonNull(familyName, "familyName cannot be null"); 339 synchronized (SYSTEM_FONT_MAP_LOCK) { 340 return sSystemFontMap.containsKey(familyName); 341 } 342 } 343 344 /** 345 * @hide 346 * Used by Resources to load a font resource of type xml. 347 */ 348 @Nullable createFromResources( FamilyResourceEntry entry, AssetManager mgr, String path)349 public static Typeface createFromResources( 350 FamilyResourceEntry entry, AssetManager mgr, String path) { 351 if (entry instanceof ProviderResourceEntry) { 352 final ProviderResourceEntry providerEntry = (ProviderResourceEntry) entry; 353 354 String systemFontFamilyName = providerEntry.getSystemFontFamilyName(); 355 if (systemFontFamilyName != null && hasFontFamily(systemFontFamilyName)) { 356 return Typeface.create(systemFontFamilyName, NORMAL); 357 } 358 // Downloadable font 359 List<List<String>> givenCerts = providerEntry.getCerts(); 360 List<List<byte[]>> certs = new ArrayList<>(); 361 if (givenCerts != null) { 362 for (int i = 0; i < givenCerts.size(); i++) { 363 List<String> certSet = givenCerts.get(i); 364 List<byte[]> byteArraySet = new ArrayList<>(); 365 for (int j = 0; j < certSet.size(); j++) { 366 byteArraySet.add(Base64.decode(certSet.get(j), Base64.DEFAULT)); 367 } 368 certs.add(byteArraySet); 369 } 370 } 371 // Downloaded font and it wasn't cached, request it again and return a 372 // default font instead (nothing we can do now). 373 FontRequest request = new FontRequest(providerEntry.getAuthority(), 374 providerEntry.getPackage(), providerEntry.getQuery(), certs); 375 Typeface typeface = FontsContract.getFontSync(request); 376 return typeface == null ? DEFAULT : typeface; 377 } 378 379 Typeface typeface = findFromCache(mgr, path); 380 if (typeface != null) return typeface; 381 382 // family is FontFamilyFilesResourceEntry 383 final FontFamilyFilesResourceEntry filesEntry = (FontFamilyFilesResourceEntry) entry; 384 385 try { 386 FontFamily.Builder familyBuilder = null; 387 for (final FontFileResourceEntry fontFile : filesEntry.getEntries()) { 388 final Font.Builder fontBuilder = new Font.Builder(mgr, fontFile.getFileName(), 389 false /* isAsset */, AssetManager.COOKIE_UNKNOWN) 390 .setTtcIndex(fontFile.getTtcIndex()) 391 .setFontVariationSettings(fontFile.getVariationSettings()); 392 if (fontFile.getWeight() != Typeface.RESOLVE_BY_FONT_TABLE) { 393 fontBuilder.setWeight(fontFile.getWeight()); 394 } 395 if (fontFile.getItalic() != Typeface.RESOLVE_BY_FONT_TABLE) { 396 fontBuilder.setSlant(fontFile.getItalic() == FontFileResourceEntry.ITALIC 397 ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT); 398 } 399 400 if (familyBuilder == null) { 401 familyBuilder = new FontFamily.Builder(fontBuilder.build()); 402 } else { 403 familyBuilder.addFont(fontBuilder.build()); 404 } 405 } 406 if (familyBuilder == null) { 407 return Typeface.DEFAULT; 408 } 409 final FontFamily family = familyBuilder.build(); 410 final FontStyle normal = new FontStyle(FontStyle.FONT_WEIGHT_NORMAL, 411 FontStyle.FONT_SLANT_UPRIGHT); 412 Font bestFont = family.getFont(0); 413 int bestScore = normal.getMatchScore(bestFont.getStyle()); 414 for (int i = 1; i < family.getSize(); ++i) { 415 final Font candidate = family.getFont(i); 416 final int score = normal.getMatchScore(candidate.getStyle()); 417 if (score < bestScore) { 418 bestFont = candidate; 419 bestScore = score; 420 } 421 } 422 typeface = new Typeface.CustomFallbackBuilder(family) 423 .setStyle(bestFont.getStyle()) 424 .build(); 425 } catch (IllegalArgumentException e) { 426 // To be a compatible behavior with API28 or before, catch IllegalArgumentExcetpion 427 // thrown by native code and returns null. 428 return null; 429 } catch (IOException e) { 430 typeface = Typeface.DEFAULT; 431 } 432 synchronized (sDynamicCacheLock) { 433 final String key = Builder.createAssetUid(mgr, path, 0 /* ttcIndex */, 434 null /* axes */, RESOLVE_BY_FONT_TABLE /* weight */, 435 RESOLVE_BY_FONT_TABLE /* italic */, DEFAULT_FAMILY); 436 sDynamicTypefaceCache.put(key, typeface); 437 } 438 return typeface; 439 } 440 441 /** 442 * Used by resources for cached loading if the font is available. 443 * @hide 444 */ findFromCache(AssetManager mgr, String path)445 public static Typeface findFromCache(AssetManager mgr, String path) { 446 synchronized (sDynamicCacheLock) { 447 final String key = Builder.createAssetUid(mgr, path, 0 /* ttcIndex */, null /* axes */, 448 RESOLVE_BY_FONT_TABLE /* weight */, RESOLVE_BY_FONT_TABLE /* italic */, 449 DEFAULT_FAMILY); 450 Typeface typeface = sDynamicTypefaceCache.get(key); 451 if (typeface != null) { 452 return typeface; 453 } 454 } 455 return null; 456 } 457 458 /** 459 * A builder class for creating new Typeface instance. 460 * 461 * <p> 462 * Examples, 463 * 1) Create Typeface from ttf file. 464 * <pre> 465 * <code> 466 * Typeface.Builder builder = new Typeface.Builder("your_font_file.ttf"); 467 * Typeface typeface = builder.build(); 468 * </code> 469 * </pre> 470 * 471 * 2) Create Typeface from ttc file in assets directory. 472 * <pre> 473 * <code> 474 * Typeface.Builder builder = new Typeface.Builder(getAssets(), "your_font_file.ttc"); 475 * builder.setTtcIndex(2); // Set index of font collection. 476 * Typeface typeface = builder.build(); 477 * </code> 478 * </pre> 479 * 480 * 3) Create Typeface with variation settings. 481 * <pre> 482 * <code> 483 * Typeface.Builder builder = new Typeface.Builder("your_font_file.ttf"); 484 * builder.setFontVariationSettings("'wght' 700, 'slnt' 20, 'ital' 1"); 485 * builder.setWeight(700); // Tell the system that this is a bold font. 486 * builder.setItalic(true); // Tell the system that this is an italic style font. 487 * Typeface typeface = builder.build(); 488 * </code> 489 * </pre> 490 * </p> 491 */ 492 public static final class Builder { 493 /** @hide */ 494 public static final int NORMAL_WEIGHT = 400; 495 /** @hide */ 496 public static final int BOLD_WEIGHT = 700; 497 498 // Kept for generating asset cache key. 499 private final AssetManager mAssetManager; 500 private final String mPath; 501 502 private final @Nullable Font.Builder mFontBuilder; 503 504 private String mFallbackFamilyName; 505 506 private int mWeight = RESOLVE_BY_FONT_TABLE; 507 private int mItalic = RESOLVE_BY_FONT_TABLE; 508 509 /** 510 * Constructs a builder with a file path. 511 * 512 * @param path The file object refers to the font file. 513 */ Builder(@onNull File path)514 public Builder(@NonNull File path) { 515 mFontBuilder = new Font.Builder(path); 516 mAssetManager = null; 517 mPath = null; 518 } 519 520 /** 521 * Constructs a builder with a file descriptor. 522 * 523 * Caller is responsible for closing the passed file descriptor after {@link #build} is 524 * called. 525 * 526 * @param fd The file descriptor. The passed fd must be mmap-able. 527 */ Builder(@onNull FileDescriptor fd)528 public Builder(@NonNull FileDescriptor fd) { 529 Font.Builder builder; 530 try { 531 builder = new Font.Builder(ParcelFileDescriptor.dup(fd)); 532 } catch (IOException e) { 533 // We cannot tell the error to developer at this moment since we cannot change the 534 // public API signature. Instead, silently fallbacks to system fallback in the build 535 // method as the same as other error cases. 536 builder = null; 537 } 538 mFontBuilder = builder; 539 mAssetManager = null; 540 mPath = null; 541 } 542 543 /** 544 * Constructs a builder with a file path. 545 * 546 * @param path The full path to the font file. 547 */ Builder(@onNull String path)548 public Builder(@NonNull String path) { 549 mFontBuilder = new Font.Builder(new File(path)); 550 mAssetManager = null; 551 mPath = null; 552 } 553 554 /** 555 * Constructs a builder from an asset manager and a file path in an asset directory. 556 * 557 * @param assetManager The application's asset manager 558 * @param path The file name of the font data in the asset directory 559 */ Builder(@onNull AssetManager assetManager, @NonNull String path)560 public Builder(@NonNull AssetManager assetManager, @NonNull String path) { 561 this(assetManager, path, true /* is asset */, 0 /* cookie */); 562 } 563 564 /** 565 * Constructs a builder from an asset manager and a file path in an asset directory. 566 * 567 * @param assetManager The application's asset manager 568 * @param path The file name of the font data in the asset directory 569 * @param cookie a cookie for the asset 570 * @hide 571 */ Builder(@onNull AssetManager assetManager, @NonNull String path, boolean isAsset, int cookie)572 public Builder(@NonNull AssetManager assetManager, @NonNull String path, boolean isAsset, 573 int cookie) { 574 mFontBuilder = new Font.Builder(assetManager, path, isAsset, cookie); 575 mAssetManager = assetManager; 576 mPath = path; 577 } 578 579 /** 580 * Sets weight of the font. 581 * 582 * Tells the system the weight of the given font. If not provided, the system will resolve 583 * the weight value by reading font tables. 584 * @param weight a weight value. 585 */ setWeight(@ntRangefrom = 1, to = 1000) int weight)586 public Builder setWeight(@IntRange(from = 1, to = 1000) int weight) { 587 mWeight = weight; 588 mFontBuilder.setWeight(weight); 589 return this; 590 } 591 592 /** 593 * Sets italic information of the font. 594 * 595 * Tells the system the style of the given font. If not provided, the system will resolve 596 * the style by reading font tables. 597 * @param italic {@code true} if the font is italic. Otherwise {@code false}. 598 */ setItalic(boolean italic)599 public Builder setItalic(boolean italic) { 600 mItalic = italic ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT; 601 mFontBuilder.setSlant(mItalic); 602 return this; 603 } 604 605 /** 606 * Sets an index of the font collection. See {@link android.R.attr#ttcIndex}. 607 * 608 * Can not be used for Typeface source. build() method will return null for invalid index. 609 * @param ttcIndex An index of the font collection. If the font source is not font 610 * collection, do not call this method or specify 0. 611 */ setTtcIndex(@ntRangefrom = 0) int ttcIndex)612 public Builder setTtcIndex(@IntRange(from = 0) int ttcIndex) { 613 mFontBuilder.setTtcIndex(ttcIndex); 614 return this; 615 } 616 617 /** 618 * Sets a font variation settings. 619 * 620 * @param variationSettings See {@link android.widget.TextView#setFontVariationSettings}. 621 * @throws IllegalArgumentException If given string is not a valid font variation settings 622 * format. 623 */ setFontVariationSettings(@ullable String variationSettings)624 public Builder setFontVariationSettings(@Nullable String variationSettings) { 625 mFontBuilder.setFontVariationSettings(variationSettings); 626 return this; 627 } 628 629 /** 630 * Sets a font variation settings. 631 * 632 * @param axes An array of font variation axis tag-value pairs. 633 */ setFontVariationSettings(@ullable FontVariationAxis[] axes)634 public Builder setFontVariationSettings(@Nullable FontVariationAxis[] axes) { 635 mFontBuilder.setFontVariationSettings(axes); 636 return this; 637 } 638 639 /** 640 * Sets a fallback family name. 641 * 642 * By specifying a fallback family name, a fallback Typeface will be returned if the 643 * {@link #build} method fails to create a Typeface from the provided font. The fallback 644 * family will be resolved with the provided weight and italic information specified by 645 * {@link #setWeight} and {@link #setItalic}. 646 * 647 * If {@link #setWeight} is not called, the fallback family keeps the default weight. 648 * Similarly, if {@link #setItalic} is not called, the fallback family keeps the default 649 * italic information. For example, calling {@code builder.setFallback("sans-serif-light")} 650 * is equivalent to calling {@code builder.setFallback("sans-serif").setWeight(300)} in 651 * terms of fallback. The default weight and italic information are overridden by calling 652 * {@link #setWeight} and {@link #setItalic}. For example, if a Typeface is constructed 653 * using {@code builder.setFallback("sans-serif-light").setWeight(700)}, the fallback text 654 * will render as sans serif bold. 655 * 656 * @param familyName A family name to be used for fallback if the provided font can not be 657 * used. By passing {@code null}, build() returns {@code null}. 658 * If {@link #setFallback} is not called on the builder, {@code null} 659 * is assumed. 660 */ setFallback(@ullable String familyName)661 public Builder setFallback(@Nullable String familyName) { 662 mFallbackFamilyName = familyName; 663 return this; 664 } 665 666 /** 667 * Creates a unique id for a given AssetManager and asset path. 668 * 669 * @param mgr AssetManager instance 670 * @param path The path for the asset. 671 * @param ttcIndex The TTC index for the font. 672 * @param axes The font variation settings. 673 * @return Unique id for a given AssetManager and asset path. 674 */ createAssetUid(final AssetManager mgr, String path, int ttcIndex, @Nullable FontVariationAxis[] axes, int weight, int italic, String fallback)675 private static String createAssetUid(final AssetManager mgr, String path, int ttcIndex, 676 @Nullable FontVariationAxis[] axes, int weight, int italic, String fallback) { 677 final SparseArray<String> pkgs = mgr.getAssignedPackageIdentifiers(); 678 final StringBuilder builder = new StringBuilder(); 679 final int size = pkgs.size(); 680 for (int i = 0; i < size; i++) { 681 builder.append(pkgs.valueAt(i)); 682 builder.append("-"); 683 } 684 builder.append(path); 685 builder.append("-"); 686 builder.append(Integer.toString(ttcIndex)); 687 builder.append("-"); 688 builder.append(Integer.toString(weight)); 689 builder.append("-"); 690 builder.append(Integer.toString(italic)); 691 // Family name may contain hyphen. Use double hyphen for avoiding key conflicts before 692 // and after appending falblack name. 693 builder.append("--"); 694 builder.append(fallback); 695 builder.append("--"); 696 if (axes != null) { 697 for (FontVariationAxis axis : axes) { 698 builder.append(axis.getTag()); 699 builder.append("-"); 700 builder.append(Float.toString(axis.getStyleValue())); 701 } 702 } 703 return builder.toString(); 704 } 705 resolveFallbackTypeface()706 private Typeface resolveFallbackTypeface() { 707 if (mFallbackFamilyName == null) { 708 return null; 709 } 710 711 final Typeface base = getSystemDefaultTypeface(mFallbackFamilyName); 712 if (mWeight == RESOLVE_BY_FONT_TABLE && mItalic == RESOLVE_BY_FONT_TABLE) { 713 return base; 714 } 715 716 final int weight = (mWeight == RESOLVE_BY_FONT_TABLE) ? base.mWeight : mWeight; 717 final boolean italic = 718 (mItalic == RESOLVE_BY_FONT_TABLE) ? (base.mStyle & ITALIC) != 0 : mItalic == 1; 719 return createWeightStyle(base, weight, italic); 720 } 721 722 /** 723 * Generates new Typeface from specified configuration. 724 * 725 * @return Newly created Typeface. May return null if some parameters are invalid. 726 */ build()727 public Typeface build() { 728 if (mFontBuilder == null) { 729 return resolveFallbackTypeface(); 730 } 731 try { 732 final Font font = mFontBuilder.build(); 733 final String key = mAssetManager == null ? null : createAssetUid( 734 mAssetManager, mPath, font.getTtcIndex(), font.getAxes(), 735 mWeight, mItalic, 736 mFallbackFamilyName == null ? DEFAULT_FAMILY : mFallbackFamilyName); 737 if (key != null) { 738 // Dynamic cache lookup is only for assets. 739 synchronized (sDynamicCacheLock) { 740 final Typeface typeface = sDynamicTypefaceCache.get(key); 741 if (typeface != null) { 742 return typeface; 743 } 744 } 745 } 746 final FontFamily family = new FontFamily.Builder(font).build(); 747 final int weight = mWeight == RESOLVE_BY_FONT_TABLE 748 ? font.getStyle().getWeight() : mWeight; 749 final int slant = mItalic == RESOLVE_BY_FONT_TABLE 750 ? font.getStyle().getSlant() : mItalic; 751 final CustomFallbackBuilder builder = new CustomFallbackBuilder(family) 752 .setStyle(new FontStyle(weight, slant)); 753 if (mFallbackFamilyName != null) { 754 builder.setSystemFallback(mFallbackFamilyName); 755 } 756 final Typeface typeface = builder.build(); 757 if (key != null) { 758 synchronized (sDynamicCacheLock) { 759 sDynamicTypefaceCache.put(key, typeface); 760 } 761 } 762 return typeface; 763 } catch (IOException | IllegalArgumentException e) { 764 return resolveFallbackTypeface(); 765 } 766 } 767 } 768 769 /** 770 * A builder class for creating new Typeface instance. 771 * 772 * There are two font fallback mechanisms, custom font fallback and system font fallback. 773 * The custom font fallback is a simple ordered list. The text renderer tries to see if it can 774 * render a character with the first font and if that font does not support the character, try 775 * next one and so on. It will keep trying until end of the custom fallback chain. The maximum 776 * length of the custom fallback chain is 64. 777 * The system font fallback is a system pre-defined fallback chain. The system fallback is 778 * processed only when no matching font is found in the custom font fallback. 779 * 780 * <p> 781 * Examples, 782 * 1) Create Typeface from single ttf file. 783 * <pre> 784 * <code> 785 * Font font = new Font.Builder("your_font_file.ttf").build(); 786 * FontFamily family = new FontFamily.Builder(font).build(); 787 * Typeface typeface = new Typeface.CustomFallbackBuilder(family).build(); 788 * </code> 789 * </pre> 790 * 791 * 2) Create Typeface from multiple font files and select bold style by default. 792 * <pre> 793 * <code> 794 * Font regularFont = new Font.Builder("regular.ttf").build(); 795 * Font boldFont = new Font.Builder("bold.ttf").build(); 796 * FontFamily family = new FontFamily.Builder(regularFont) 797 * .addFont(boldFont).build(); 798 * Typeface typeface = new Typeface.CustomFallbackBuilder(family) 799 * .setWeight(Font.FONT_WEIGHT_BOLD) // Set bold style as the default style. 800 * // If the font family doesn't have bold style font, 801 * // system will select the closest font. 802 * .build(); 803 * </code> 804 * </pre> 805 * 806 * 3) Create Typeface from single ttf file and if that font does not have glyph for the 807 * characters, use "serif" font family instead. 808 * <pre> 809 * <code> 810 * Font font = new Font.Builder("your_font_file.ttf").build(); 811 * FontFamily family = new FontFamily.Builder(font).build(); 812 * Typeface typeface = new Typeface.CustomFallbackBuilder(family) 813 * .setSystemFallback("serif") // Set serif font family as the fallback. 814 * .build(); 815 * </code> 816 * </pre> 817 * 4) Create Typeface from single ttf file and set another ttf file for the fallback. 818 * <pre> 819 * <code> 820 * Font font = new Font.Builder("English.ttf").build(); 821 * FontFamily family = new FontFamily.Builder(font).build(); 822 * 823 * Font fallbackFont = new Font.Builder("Arabic.ttf").build(); 824 * FontFamily fallbackFamily = new FontFamily.Builder(fallbackFont).build(); 825 * Typeface typeface = new Typeface.CustomFallbackBuilder(family) 826 * .addCustomFallback(fallbackFamily) // Specify fallback family. 827 * .setSystemFallback("serif") // Set serif font family as the fallback. 828 * .build(); 829 * </code> 830 * </pre> 831 * </p> 832 */ 833 public static final class CustomFallbackBuilder { 834 private static final int MAX_CUSTOM_FALLBACK = 64; 835 private final ArrayList<FontFamily> mFamilies = new ArrayList<>(); 836 private String mFallbackName = null; 837 private @Nullable FontStyle mStyle; 838 839 /** 840 * Returns the maximum capacity of custom fallback families. 841 * 842 * This includes the first font family passed to the constructor. 843 * It is guaranteed that the value will be greater than or equal to 64. 844 * 845 * @return the maximum number of font families for the custom fallback 846 */ getMaxCustomFallbackCount()847 public static @IntRange(from = 64) int getMaxCustomFallbackCount() { 848 return MAX_CUSTOM_FALLBACK; 849 } 850 851 /** 852 * Constructs a builder with a font family. 853 * 854 * @param family a family object 855 */ CustomFallbackBuilder(@onNull FontFamily family)856 public CustomFallbackBuilder(@NonNull FontFamily family) { 857 Preconditions.checkNotNull(family); 858 mFamilies.add(family); 859 } 860 861 /** 862 * Sets a system fallback by name. 863 * 864 * You can specify generic font family names or OEM specific family names. If the system 865 * don't have a specified fallback, the default fallback is used instead. 866 * For more information about generic font families, see <a 867 * href="https://www.w3.org/TR/css-fonts-4/#generic-font-families">CSS specification</a> 868 * 869 * For more information about fallback, see class description. 870 * 871 * @param familyName a family name to be used for fallback if the provided fonts can not be 872 * used 873 */ setSystemFallback(@onNull String familyName)874 public @NonNull CustomFallbackBuilder setSystemFallback(@NonNull String familyName) { 875 Preconditions.checkNotNull(familyName); 876 mFallbackName = familyName; 877 return this; 878 } 879 880 /** 881 * Sets a font style of the Typeface. 882 * 883 * If the font family doesn't have a font of given style, system will select the closest 884 * font from font family. For example, if a font family has fonts of 300 weight and 700 885 * weight then setWeight(400) is called, system will select the font of 300 weight. 886 * 887 * @param style a font style 888 */ setStyle(@onNull FontStyle style)889 public @NonNull CustomFallbackBuilder setStyle(@NonNull FontStyle style) { 890 mStyle = style; 891 return this; 892 } 893 894 /** 895 * Append a font family to the end of the custom font fallback. 896 * 897 * You can set up to 64 custom fallback families including the first font family you passed 898 * to the constructor. 899 * For more information about fallback, see class description. 900 * 901 * @param family a fallback family 902 * @throws IllegalArgumentException if you give more than 64 custom fallback families 903 */ addCustomFallback(@onNull FontFamily family)904 public @NonNull CustomFallbackBuilder addCustomFallback(@NonNull FontFamily family) { 905 Preconditions.checkNotNull(family); 906 Preconditions.checkArgument(mFamilies.size() < getMaxCustomFallbackCount(), 907 "Custom fallback limit exceeded(%d)", getMaxCustomFallbackCount()); 908 mFamilies.add(family); 909 return this; 910 } 911 912 /** 913 * Create the Typeface based on the configured values. 914 * 915 * @return the Typeface object 916 */ 917 public @NonNull Typeface build() { 918 final int userFallbackSize = mFamilies.size(); 919 final Typeface fallbackTypeface = getSystemDefaultTypeface(mFallbackName); 920 final long[] ptrArray = new long[userFallbackSize]; 921 for (int i = 0; i < userFallbackSize; ++i) { 922 ptrArray[i] = mFamilies.get(i).getNativePtr(); 923 } 924 final int weight = mStyle == null ? 400 : mStyle.getWeight(); 925 final int italic = 926 (mStyle == null || mStyle.getSlant() == FontStyle.FONT_SLANT_UPRIGHT) ? 0 : 1; 927 return new Typeface(nativeCreateFromArray( 928 ptrArray, fallbackTypeface.native_instance, weight, italic), null); 929 } 930 } 931 932 /** 933 * Create a typeface object given a family name, and option style information. 934 * If null is passed for the name, then the "default" font will be chosen. 935 * The resulting typeface object can be queried (getStyle()) to discover what 936 * its "real" style characteristics are. 937 * 938 * @param familyName May be null. The name of the font family. 939 * @param style The style (normal, bold, italic) of the typeface. 940 * e.g. NORMAL, BOLD, ITALIC, BOLD_ITALIC 941 * @return The best matching typeface. 942 */ 943 public static Typeface create(String familyName, @Style int style) { 944 return create(getSystemDefaultTypeface(familyName), style); 945 } 946 947 /** 948 * Create a typeface object that best matches the specified existing 949 * typeface and the specified Style. Use this call if you want to pick a new 950 * style from the same family of an existing typeface object. If family is 951 * null, this selects from the default font's family. 952 * 953 * <p> 954 * This method is not thread safe on API 27 or before. 955 * This method is thread safe on API 28 or after. 956 * </p> 957 * 958 * @param family An existing {@link Typeface} object. In case of {@code null}, the default 959 * typeface is used instead. 960 * @param style The style (normal, bold, italic) of the typeface. 961 * e.g. NORMAL, BOLD, ITALIC, BOLD_ITALIC 962 * @return The best matching typeface. 963 */ 964 public static Typeface create(Typeface family, @Style int style) { 965 if ((style & ~STYLE_MASK) != 0) { 966 style = NORMAL; 967 } 968 if (family == null) { 969 family = getDefault(); 970 } 971 972 // Return early if we're asked for the same face/style 973 if (family.mStyle == style) { 974 return family; 975 } 976 977 final long ni = family.native_instance; 978 979 Typeface typeface; 980 synchronized (sStyledCacheLock) { 981 SparseArray<Typeface> styles = sStyledTypefaceCache.get(ni); 982 983 if (styles == null) { 984 styles = new SparseArray<Typeface>(4); 985 sStyledTypefaceCache.put(ni, styles); 986 } else { 987 typeface = styles.get(style); 988 if (typeface != null) { 989 return typeface; 990 } 991 } 992 993 typeface = new Typeface(nativeCreateFromTypeface(ni, style), 994 family.getSystemFontFamilyName()); 995 styles.put(style, typeface); 996 } 997 return typeface; 998 } 999 1000 /** 1001 * Creates a typeface object that best matches the specified existing typeface and the specified 1002 * weight and italic style 1003 * <p>Below are numerical values and corresponding common weight names.</p> 1004 * <table> 1005 * <thead> 1006 * <tr><th>Value</th><th>Common weight name</th></tr> 1007 * </thead> 1008 * <tbody> 1009 * <tr><td>100</td><td>Thin</td></tr> 1010 * <tr><td>200</td><td>Extra Light</td></tr> 1011 * <tr><td>300</td><td>Light</td></tr> 1012 * <tr><td>400</td><td>Normal</td></tr> 1013 * <tr><td>500</td><td>Medium</td></tr> 1014 * <tr><td>600</td><td>Semi Bold</td></tr> 1015 * <tr><td>700</td><td>Bold</td></tr> 1016 * <tr><td>800</td><td>Extra Bold</td></tr> 1017 * <tr><td>900</td><td>Black</td></tr> 1018 * </tbody> 1019 * </table> 1020 * 1021 * <p> 1022 * This method is thread safe. 1023 * </p> 1024 * 1025 * @param family An existing {@link Typeface} object. In case of {@code null}, the default 1026 * typeface is used instead. 1027 * @param weight The desired weight to be drawn. 1028 * @param italic {@code true} if italic style is desired to be drawn. Otherwise, {@code false} 1029 * @return A {@link Typeface} object for drawing specified weight and italic style. Never 1030 * returns {@code null} 1031 * 1032 * @see #getWeight() 1033 * @see #isItalic() 1034 */ 1035 public static @NonNull Typeface create(@Nullable Typeface family, 1036 @IntRange(from = 1, to = 1000) int weight, boolean italic) { 1037 Preconditions.checkArgumentInRange(weight, 0, 1000, "weight"); 1038 if (family == null) { 1039 family = getDefault(); 1040 } 1041 return createWeightStyle(family, weight, italic); 1042 } 1043 1044 private static @NonNull Typeface createWeightStyle(@NonNull Typeface base, 1045 @IntRange(from = 1, to = 1000) int weight, boolean italic) { 1046 final int key = (weight << 1) | (italic ? 1 : 0); 1047 1048 Typeface typeface; 1049 synchronized(sWeightCacheLock) { 1050 SparseArray<Typeface> innerCache = sWeightTypefaceCache.get(base.native_instance); 1051 if (innerCache == null) { 1052 innerCache = new SparseArray<>(4); 1053 sWeightTypefaceCache.put(base.native_instance, innerCache); 1054 } else { 1055 typeface = innerCache.get(key); 1056 if (typeface != null) { 1057 return typeface; 1058 } 1059 } 1060 1061 typeface = new Typeface( 1062 nativeCreateFromTypefaceWithExactStyle(base.native_instance, weight, italic), 1063 base.getSystemFontFamilyName()); 1064 innerCache.put(key, typeface); 1065 } 1066 return typeface; 1067 } 1068 1069 private static String axesToVarKey(@NonNull List<FontVariationAxis> axes) { 1070 // The given list can be mutated because it is allocated in Paint#setFontVariationSettings. 1071 // Currently, Paint#setFontVariationSettings is the only code path reaches this method. 1072 axes.sort(Comparator.comparingInt(FontVariationAxis::getOpenTypeTagValue)); 1073 StringBuilder sb = new StringBuilder(); 1074 for (int i = 0; i < axes.size(); ++i) { 1075 final FontVariationAxis fva = axes.get(i); 1076 sb.append(fva.getTag()); 1077 sb.append(fva.getStyleValue()); 1078 } 1079 return sb.toString(); 1080 } 1081 1082 /** 1083 * TODO(b/357707916): Make this public API. 1084 * @hide 1085 */ 1086 public static Typeface createFromTypefaceWithVariation(@Nullable Typeface family, 1087 @NonNull List<FontVariationAxis> axes) { 1088 if (Flags.typefaceCacheForVarSettings()) { 1089 final Typeface target = (family == null) ? Typeface.DEFAULT : family; 1090 final Typeface base = (target.mDerivedFrom == null) ? target : target.mDerivedFrom; 1091 1092 final String key = axesToVarKey(axes); 1093 1094 synchronized (sVariableCacheLock) { 1095 LruCache<String, Typeface> innerCache = sVariableCache.get(base.native_instance); 1096 if (innerCache == null) { 1097 // Cache up to 16 var instance per root Typeface 1098 innerCache = new LruCache<>(16); 1099 sVariableCache.put(base.native_instance, innerCache); 1100 } else { 1101 Typeface cached = innerCache.get(key); 1102 if (cached != null) { 1103 return cached; 1104 } 1105 } 1106 Typeface typeface = new Typeface( 1107 nativeCreateFromTypefaceWithVariation(base.native_instance, axes), 1108 base.getSystemFontFamilyName(), base); 1109 innerCache.put(key, typeface); 1110 return typeface; 1111 } 1112 } 1113 1114 final Typeface base = family == null ? Typeface.DEFAULT : family; 1115 Typeface typeface = new Typeface( 1116 nativeCreateFromTypefaceWithVariation(base.native_instance, axes), 1117 base.getSystemFontFamilyName()); 1118 return typeface; 1119 } 1120 1121 /** 1122 * Returns one of the default typeface objects, based on the specified style 1123 * 1124 * @return the default typeface that corresponds to the style 1125 */ 1126 public static Typeface defaultFromStyle(@Style int style) { 1127 synchronized (SYSTEM_FONT_MAP_LOCK) { 1128 return sDefaults[style]; 1129 } 1130 } 1131 1132 /** 1133 * Create a new typeface from the specified font data. 1134 * 1135 * @param mgr The application's asset manager 1136 * @param path The file name of the font data in the assets directory 1137 * @return The new typeface. 1138 */ 1139 public static Typeface createFromAsset(AssetManager mgr, String path) { 1140 Preconditions.checkNotNull(path); // for backward compatibility 1141 Preconditions.checkNotNull(mgr); 1142 1143 Typeface typeface = new Builder(mgr, path).build(); 1144 if (typeface != null) return typeface; 1145 // check if the file exists, and throw an exception for backward compatibility 1146 try (InputStream inputStream = mgr.open(path)) { 1147 } catch (IOException e) { 1148 throw new RuntimeException("Font asset not found " + path); 1149 } 1150 1151 return Typeface.DEFAULT; 1152 } 1153 1154 /** 1155 * Creates a unique id for a given font provider and query. 1156 */ 1157 private static String createProviderUid(String authority, String query) { 1158 final StringBuilder builder = new StringBuilder(); 1159 builder.append("provider:"); 1160 builder.append(authority); 1161 builder.append("-"); 1162 builder.append(query); 1163 return builder.toString(); 1164 } 1165 1166 /** 1167 * Create a new typeface from the specified font file. 1168 * 1169 * @param file The path to the font data. 1170 * @return The new typeface. 1171 */ 1172 public static Typeface createFromFile(@Nullable File file) { 1173 // For the compatibility reasons, leaving possible NPE here. 1174 // See android.graphics.cts.TypefaceTest#testCreateFromFileByFileReferenceNull 1175 1176 Typeface typeface = new Builder(file).build(); 1177 if (typeface != null) return typeface; 1178 1179 // check if the file exists, and throw an exception for backward compatibility 1180 if (!file.exists()) { 1181 throw new RuntimeException("Font asset not found " + file.getAbsolutePath()); 1182 } 1183 1184 return Typeface.DEFAULT; 1185 } 1186 1187 /** 1188 * Create a new typeface from the specified font file. 1189 * 1190 * @param path The full path to the font data. 1191 * @return The new typeface. 1192 */ 1193 public static Typeface createFromFile(@Nullable String path) { 1194 Preconditions.checkNotNull(path); // for backward compatibility 1195 return createFromFile(new File(path)); 1196 } 1197 1198 /** 1199 * Create a new typeface from an array of font families. 1200 * 1201 * @param families array of font families 1202 * @deprecated 1203 */ 1204 @Deprecated 1205 @UnsupportedAppUsage(trackingBug = 123768928) 1206 private static Typeface createFromFamilies(android.graphics.FontFamily[] families) { 1207 long[] ptrArray = new long[families.length]; 1208 for (int i = 0; i < families.length; i++) { 1209 ptrArray[i] = families[i].mNativePtr; 1210 } 1211 return new Typeface(nativeCreateFromArray( 1212 ptrArray, 0, RESOLVE_BY_FONT_TABLE, 1213 RESOLVE_BY_FONT_TABLE), null); 1214 } 1215 1216 /** 1217 * Create a new typeface from an array of android.graphics.fonts.FontFamily. 1218 * 1219 * @param families array of font families 1220 */ 1221 private static Typeface createFromFamilies(@NonNull String familyName, 1222 @Nullable FontFamily[] families) { 1223 final long[] ptrArray = new long[families.length]; 1224 for (int i = 0; i < families.length; ++i) { 1225 ptrArray[i] = families[i].getNativePtr(); 1226 } 1227 return new Typeface(nativeCreateFromArray(ptrArray, 0, 1228 RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE), familyName); 1229 } 1230 1231 /** 1232 * This method is used by supportlib-v27. 1233 * 1234 * @deprecated Use {@link android.graphics.fonts.FontFamily} instead. 1235 */ 1236 @UnsupportedAppUsage(trackingBug = 123768395) 1237 @Deprecated 1238 private static Typeface createFromFamiliesWithDefault( 1239 android.graphics.FontFamily[] families, int weight, int italic) { 1240 return createFromFamiliesWithDefault(families, DEFAULT_FAMILY, weight, italic); 1241 } 1242 1243 /** 1244 * Create a new typeface from an array of font families, including 1245 * also the font families in the fallback list. 1246 * @param fallbackName the family name. If given families don't support characters, the 1247 * characters will be rendered with this family. 1248 * @param weight the weight for this family. In that case, the table information in the first 1249 * family's font is used. If the first family has multiple fonts, the closest to 1250 * the regular weight and upright font is used. 1251 * @param italic the italic information for this family. In that case, the table information in 1252 * the first family's font is used. If the first family has multiple fonts, the 1253 * closest to the regular weight and upright font is used. 1254 * @param families array of font families 1255 * 1256 * @deprecated Use {@link android.graphics.fonts.FontFamily} instead. 1257 */ 1258 @UnsupportedAppUsage(trackingBug = 123768928) 1259 @Deprecated 1260 private static Typeface createFromFamiliesWithDefault(android.graphics.FontFamily[] families, 1261 String fallbackName, int weight, int italic) { 1262 final Typeface fallbackTypeface = getSystemDefaultTypeface(fallbackName); 1263 long[] ptrArray = new long[families.length]; 1264 for (int i = 0; i < families.length; i++) { 1265 ptrArray[i] = families[i].mNativePtr; 1266 } 1267 return new Typeface(nativeCreateFromArray( 1268 ptrArray, fallbackTypeface.native_instance, weight, italic), null); 1269 } 1270 1271 // don't allow clients to call this directly 1272 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) 1273 private Typeface(long ni) { 1274 this(ni, null, null); 1275 } 1276 1277 1278 // don't allow clients to call this directly 1279 // This is kept for robolectric. 1280 private Typeface(long ni, @Nullable String systemFontFamilyName) { 1281 this(ni, systemFontFamilyName, null); 1282 } 1283 1284 // don't allow clients to call this directly 1285 private Typeface(long ni, @Nullable String systemFontFamilyName, 1286 @Nullable Typeface derivedFrom) { 1287 if (ni == 0) { 1288 throw new RuntimeException("native typeface cannot be made"); 1289 } 1290 1291 native_instance = ni; 1292 mCleaner = NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, native_instance); 1293 mStyle = nativeGetStyle(ni); 1294 mWeight = nativeGetWeight(ni); 1295 mIsVariationInstance = nativeIsVariationInstance(ni); 1296 mSystemFontFamilyName = systemFontFamilyName; 1297 mDerivedFrom = derivedFrom; 1298 } 1299 1300 /** 1301 * Releases the underlying native object. 1302 * 1303 * <p>For testing only. Do not use the instance after this method is called. 1304 * It is safe to call this method twice or more on the same instance. 1305 * @hide 1306 */ 1307 @TestApi 1308 public void releaseNativeObjectForTest() { 1309 mCleaner.run(); 1310 } 1311 1312 private static Typeface getSystemDefaultTypeface(@NonNull String familyName) { 1313 Typeface tf = sSystemFontMap.get(familyName); 1314 return tf == null ? Typeface.DEFAULT : tf; 1315 } 1316 1317 /** @hide */ 1318 @VisibleForTesting 1319 public static void initSystemDefaultTypefaces(Map<String, FontFamily[]> fallbacks, 1320 List<FontConfig.Alias> aliases, 1321 Map<String, Typeface> outSystemFontMap) { 1322 for (Map.Entry<String, FontFamily[]> entry : fallbacks.entrySet()) { 1323 outSystemFontMap.put(entry.getKey(), 1324 createFromFamilies(entry.getKey(), entry.getValue())); 1325 } 1326 1327 for (int i = 0; i < aliases.size(); ++i) { 1328 final FontConfig.Alias alias = aliases.get(i); 1329 if (outSystemFontMap.containsKey(alias.getName())) { 1330 continue; // If alias and named family are conflict, use named family. 1331 } 1332 final Typeface base = outSystemFontMap.get(alias.getOriginal()); 1333 if (base == null) { 1334 // The missing target is a valid thing, some configuration don't have font files, 1335 // e.g. wear devices. Just skip this alias. 1336 continue; 1337 } 1338 final int weight = alias.getWeight(); 1339 final Typeface newFace = weight == 400 ? base : new Typeface( 1340 nativeCreateWeightAlias(base.native_instance, weight), alias.getName()); 1341 outSystemFontMap.put(alias.getName(), newFace); 1342 } 1343 } 1344 1345 private static void registerGenericFamilyNative(@NonNull String familyName, 1346 @Nullable Typeface typeface) { 1347 if (typeface != null) { 1348 nativeRegisterGenericFamily(familyName, typeface.native_instance); 1349 } 1350 } 1351 1352 /** 1353 * Create a serialized system font mappings. 1354 * 1355 * @hide 1356 */ 1357 @TestApi 1358 public static @NonNull SharedMemory serializeFontMap(@NonNull Map<String, Typeface> fontMap) 1359 throws IOException, ErrnoException { 1360 long[] nativePtrs = new long[fontMap.size()]; 1361 // The name table will not be large, so let's create a byte array in memory. 1362 ByteArrayOutputStream namesBytes = new ByteArrayOutputStream(); 1363 int i = 0; 1364 for (Map.Entry<String, Typeface> entry : fontMap.entrySet()) { 1365 nativePtrs[i++] = entry.getValue().native_instance; 1366 writeString(namesBytes, entry.getKey()); 1367 } 1368 // int (typefacesBytesCount), typefaces, namesBytes 1369 final int typefaceBytesCountSize = Integer.BYTES; 1370 int typefacesBytesCount = nativeWriteTypefaces(null, typefaceBytesCountSize, nativePtrs); 1371 SharedMemory sharedMemory = SharedMemory.create( 1372 "fontMap", typefaceBytesCountSize + typefacesBytesCount + namesBytes.size()); 1373 ByteBuffer writableBuffer = sharedMemory.mapReadWrite().order(ByteOrder.BIG_ENDIAN); 1374 try { 1375 writableBuffer.putInt(typefacesBytesCount); 1376 int writtenBytesCount = 1377 nativeWriteTypefaces(writableBuffer, writableBuffer.position(), nativePtrs); 1378 if (writtenBytesCount != typefacesBytesCount) { 1379 throw new IOException(String.format("Unexpected bytes written: %d, expected: %d", 1380 writtenBytesCount, typefacesBytesCount)); 1381 } 1382 writableBuffer.position(writableBuffer.position() + writtenBytesCount); 1383 writableBuffer.put(namesBytes.toByteArray()); 1384 } finally { 1385 SharedMemory.unmap(writableBuffer); 1386 } 1387 sharedMemory.setProtect(OsConstants.PROT_READ); 1388 return sharedMemory; 1389 } 1390 1391 // buffer's byte order should be BIG_ENDIAN. 1392 /** 1393 * Deserialize the font mapping from the serialized byte buffer. 1394 * 1395 * <p>Warning: the given {@code buffer} must outlive generated Typeface 1396 * objects in {@code out}. In production code, this is guaranteed by 1397 * storing the buffer in {@link #sSystemFontMapBuffer}. 1398 * If you call this method in a test, please make sure to destroy the 1399 * generated Typeface objects by calling 1400 * {@link #releaseNativeObjectForTest()}. 1401 * 1402 * @hide 1403 */ 1404 @TestApi 1405 public static @NonNull long[] deserializeFontMap( 1406 @NonNull ByteBuffer buffer, @NonNull Map<String, Typeface> out) 1407 throws IOException { 1408 int typefacesBytesCount = buffer.getInt(); 1409 // Note: Do not call buffer.slice(), as nativeReadTypefaces() expects 1410 // that buffer.address() is page-aligned. 1411 long[] nativePtrs = nativeReadTypefaces(buffer, buffer.position()); 1412 if (nativePtrs == null) { 1413 throw new IOException("Could not read typefaces"); 1414 } 1415 out.clear(); 1416 buffer.position(buffer.position() + typefacesBytesCount); 1417 for (long nativePtr : nativePtrs) { 1418 String name = readString(buffer); 1419 out.put(name, new Typeface(nativePtr, name)); 1420 } 1421 return nativePtrs; 1422 } 1423 1424 private static String readString(ByteBuffer buffer) { 1425 int length = buffer.getInt(); 1426 byte[] bytes = new byte[length]; 1427 buffer.get(bytes); 1428 return new String(bytes); 1429 } 1430 1431 private static void writeString(ByteArrayOutputStream bos, String value) throws IOException { 1432 byte[] bytes = value.getBytes(); 1433 writeInt(bos, bytes.length); 1434 bos.write(bytes); 1435 } 1436 1437 private static void writeInt(ByteArrayOutputStream bos, int value) { 1438 // Write in the big endian order. 1439 bos.write((value >> 24) & 0xFF); 1440 bos.write((value >> 16) & 0xFF); 1441 bos.write((value >> 8) & 0xFF); 1442 bos.write(value & 0xFF); 1443 } 1444 1445 /** @hide */ 1446 public static Map<String, Typeface> getSystemFontMap() { 1447 synchronized (SYSTEM_FONT_MAP_LOCK) { 1448 return sSystemFontMap; 1449 } 1450 } 1451 1452 /** 1453 * Deserialize font map and set it as system font map. This method should be called at most once 1454 * per process. 1455 */ 1456 /** @hide */ 1457 @UiThread 1458 public static void setSystemFontMap(@Nullable SharedMemory sharedMemory) 1459 throws IOException, ErrnoException { 1460 if (sSystemFontMapBuffer != null) { 1461 // Apps can re-send BIND_APPLICATION message from their code. This is a work around to 1462 // detect it and avoid crashing. 1463 if (sharedMemory == null || sharedMemory == sSystemFontMapSharedMemory) { 1464 return; 1465 } 1466 throw new UnsupportedOperationException( 1467 "Once set, buffer-based system font map cannot be updated"); 1468 } 1469 sSystemFontMapSharedMemory = sharedMemory; 1470 Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "setSystemFontMap"); 1471 try { 1472 if (sharedMemory == null) { 1473 // FontManagerService is not started. This may happen in FACTORY_TEST_LOW_LEVEL 1474 // mode for example. 1475 loadPreinstalledSystemFontMap(); 1476 return; 1477 } 1478 sSystemFontMapBuffer = sharedMemory.mapReadOnly().order(ByteOrder.BIG_ENDIAN); 1479 Map<String, Typeface> systemFontMap = new ArrayMap<>(); 1480 long[] nativePtrs = deserializeFontMap(sSystemFontMapBuffer, systemFontMap); 1481 1482 // Initialize native font APIs. The native font API will read fonts.xml by itself if 1483 // Typeface is initialized with loadPreinstalledSystemFontMap. 1484 for (long ptr : nativePtrs) { 1485 nativeAddFontCollections(ptr); 1486 } 1487 setSystemFontMap(systemFontMap); 1488 } finally { 1489 Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS); 1490 } 1491 } 1492 1493 /** @hide */ 1494 @VisibleForTesting 1495 public static void setSystemFontMap(Map<String, Typeface> systemFontMap) { 1496 synchronized (SYSTEM_FONT_MAP_LOCK) { 1497 sSystemFontMap.clear(); 1498 sSystemFontMap.putAll(systemFontMap); 1499 1500 // We can't assume DEFAULT_FAMILY available on Roboletric. 1501 if (sSystemFontMap.containsKey(DEFAULT_FAMILY)) { 1502 setDefault(sSystemFontMap.get(DEFAULT_FAMILY)); 1503 } 1504 1505 // Set up defaults and typefaces exposed in public API 1506 // Use sDefaultTypeface here, because create(String, int) uses DEFAULT as fallback. 1507 nativeForceSetStaticFinalField("DEFAULT", create(sDefaultTypeface, 0)); 1508 nativeForceSetStaticFinalField("DEFAULT_BOLD", create(sDefaultTypeface, Typeface.BOLD)); 1509 nativeForceSetStaticFinalField("SANS_SERIF", create("sans-serif", 0)); 1510 nativeForceSetStaticFinalField("SERIF", create("serif", 0)); 1511 nativeForceSetStaticFinalField("MONOSPACE", create("monospace", 0)); 1512 1513 sDefaults = new Typeface[]{ 1514 DEFAULT, 1515 DEFAULT_BOLD, 1516 create((String) null, Typeface.ITALIC), 1517 create((String) null, Typeface.BOLD_ITALIC), 1518 }; 1519 1520 // A list of generic families to be registered in native. 1521 // https://www.w3.org/TR/css-fonts-4/#generic-font-families 1522 String[] genericFamilies = { 1523 "serif", "sans-serif", "cursive", "fantasy", "monospace", "system-ui" 1524 }; 1525 1526 for (String genericFamily : genericFamilies) { 1527 registerGenericFamilyNative(genericFamily, systemFontMap.get(genericFamily)); 1528 } 1529 } 1530 } 1531 1532 /** 1533 * Change default typefaces for testing purpose. 1534 * 1535 * Note: The existing TextView or Paint instance still holds the old Typeface. 1536 * 1537 * @param defaults array of [default, default_bold, default_italic, default_bolditalic]. 1538 * @param genericFamilies array of [sans-serif, serif, monospace] 1539 * @return return the old defaults and genericFamilies 1540 * @hide 1541 */ 1542 @TestApi 1543 @NonNull 1544 public static Pair<List<Typeface>, List<Typeface>> changeDefaultFontForTest( 1545 @NonNull List<Typeface> defaults, 1546 @NonNull List<Typeface> genericFamilies 1547 ) { 1548 synchronized (SYSTEM_FONT_MAP_LOCK) { 1549 List<Typeface> oldDefaults = Arrays.asList(sDefaults); 1550 sDefaults = defaults.toArray(new Typeface[4]); 1551 setDefault(defaults.get(0)); 1552 1553 ArrayList<Typeface> oldGenerics = new ArrayList<>(); 1554 BiConsumer<Typeface, String> swapTypeface = (typeface, key) -> { 1555 oldGenerics.add(sSystemFontMap.get(key)); 1556 sSystemFontMap.put(key, typeface); 1557 }; 1558 1559 Typeface sansSerif = genericFamilies.get(0); 1560 swapTypeface.accept(sansSerif, "sans-serif"); 1561 swapTypeface.accept(Typeface.create(sansSerif, 100, false), "sans-serif-thin"); 1562 swapTypeface.accept(Typeface.create(sansSerif, 300, false), "sans-serif-light"); 1563 swapTypeface.accept(Typeface.create(sansSerif, 500, false), "sans-serif-medium"); 1564 swapTypeface.accept(Typeface.create(sansSerif, 700, false), "sans-serif-bold"); 1565 swapTypeface.accept(Typeface.create(sansSerif, 900, false), "sans-serif-black"); 1566 1567 swapTypeface.accept(genericFamilies.get(1), "serif"); 1568 swapTypeface.accept(genericFamilies.get(2), "monospace"); 1569 1570 return new Pair<>(oldDefaults, oldGenerics); 1571 } 1572 } 1573 1574 static { 1575 staticInitializer(); 1576 } 1577 1578 @RavenwoodReplace(reason = "Prevent circular reference on host side JVM", bug = 337329128) 1579 private static void staticInitializer() { 1580 init(); 1581 } 1582 1583 private static void staticInitializer$ravenwood() { 1584 /* no-op */ 1585 } 1586 1587 /** @hide */ 1588 public static void init() { 1589 // Preload Roboto-Regular.ttf in Zygote for improving app launch performance. 1590 preloadFontFile(SystemFonts.SYSTEM_FONT_DIR + "Roboto-Regular.ttf"); 1591 preloadFontFile(SystemFonts.SYSTEM_FONT_DIR + "RobotoStatic-Regular.ttf"); 1592 1593 String locale = SystemProperties.get("persist.sys.locale", "en-US"); 1594 String script = ULocale.addLikelySubtags(ULocale.forLanguageTag(locale)).getScript(); 1595 1596 // The feature flag cannot be referred from Zygote. Use legacy fonts.xml for preloading font 1597 // files. 1598 // TODO(nona): Use new XML file once the feature is fully launched. 1599 FontConfig config = SystemFonts.getSystemPreinstalledFontConfigFromLegacyXml(); 1600 for (int i = 0; i < config.getFontFamilies().size(); ++i) { 1601 FontConfig.FontFamily family = config.getFontFamilies().get(i); 1602 if (!family.getLocaleList().isEmpty()) { 1603 nativeRegisterLocaleList(family.getLocaleList().toLanguageTags()); 1604 } 1605 boolean loadFamily = false; 1606 for (int j = 0; j < family.getLocaleList().size(); ++j) { 1607 String fontScript = ULocale.addLikelySubtags( 1608 ULocale.forLocale(family.getLocaleList().get(j))).getScript(); 1609 loadFamily = fontScript.equals(script); 1610 if (loadFamily) { 1611 break; 1612 } 1613 } 1614 if (loadFamily) { 1615 for (int j = 0; j < family.getFontList().size(); ++j) { 1616 preloadFontFile(family.getFontList().get(j).getFile().getAbsolutePath()); 1617 } 1618 } 1619 } 1620 } 1621 1622 private static void preloadFontFile(String filePath) { 1623 File file = new File(filePath); 1624 if (file.exists()) { 1625 Log.i(TAG, "Preloading " + file.getAbsolutePath()); 1626 nativeWarmUpCache(filePath); 1627 } 1628 } 1629 1630 /** @hide */ 1631 @VisibleForTesting 1632 public static void destroySystemFontMap() { 1633 synchronized (SYSTEM_FONT_MAP_LOCK) { 1634 for (Typeface typeface : sSystemFontMap.values()) { 1635 typeface.releaseNativeObjectForTest(); 1636 } 1637 sSystemFontMap.clear(); 1638 if (sSystemFontMapBuffer != null) { 1639 SharedMemory.unmap(sSystemFontMapBuffer); 1640 } 1641 sSystemFontMapBuffer = null; 1642 sSystemFontMapSharedMemory = null; 1643 synchronized (sStyledCacheLock) { 1644 destroyTypefaceCacheLocked(sStyledTypefaceCache); 1645 } 1646 synchronized (sWeightCacheLock) { 1647 destroyTypefaceCacheLocked(sWeightTypefaceCache); 1648 } 1649 } 1650 } 1651 1652 private static void destroyTypefaceCacheLocked(LongSparseArray<SparseArray<Typeface>> cache) { 1653 for (int i = 0; i < cache.size(); i++) { 1654 SparseArray<Typeface> array = cache.valueAt(i); 1655 for (int j = 0; j < array.size(); j++) { 1656 array.valueAt(j).releaseNativeObjectForTest(); 1657 } 1658 } 1659 cache.clear(); 1660 } 1661 1662 /** @hide */ 1663 public static void loadPreinstalledSystemFontMap() { 1664 final FontConfig fontConfig = SystemFonts.getSystemPreinstalledFontConfig(); 1665 final Map<String, FontFamily[]> fallback = SystemFonts.buildSystemFallback(fontConfig); 1666 final Map<String, Typeface> typefaceMap = 1667 SystemFonts.buildSystemTypefaces(fontConfig, fallback); 1668 setSystemFontMap(typefaceMap); 1669 } 1670 1671 /** 1672 * {@link #loadPreinstalledSystemFontMap()} does not actually initialize the native 1673 * system font APIs. Add a new method to actually load the font files without going 1674 * through SharedMemory. 1675 * 1676 * @hide 1677 */ 1678 public static void loadNativeSystemFonts() { 1679 synchronized (SYSTEM_FONT_MAP_LOCK) { 1680 for (var type : sSystemFontMap.values()) { 1681 nativeAddFontCollections(type.native_instance); 1682 } 1683 } 1684 } 1685 1686 static { 1687 if (!ENABLE_LAZY_TYPEFACE_INITIALIZATION) { 1688 loadPreinstalledSystemFontMap(); 1689 } 1690 } 1691 1692 @Override 1693 public boolean equals(Object o) { 1694 if (this == o) return true; 1695 if (o == null || getClass() != o.getClass()) return false; 1696 1697 Typeface typeface = (Typeface) o; 1698 1699 return mStyle == typeface.mStyle && native_instance == typeface.native_instance; 1700 } 1701 1702 @Override 1703 public int hashCode() { 1704 /* 1705 * Modified method for hashCode with long native_instance derived from 1706 * http://developer.android.com/reference/java/lang/Object.html 1707 */ 1708 int result = 17; 1709 result = 31 * result + (int) (native_instance ^ (native_instance >>> 32)); 1710 result = 31 * result + mStyle; 1711 return result; 1712 } 1713 1714 /** @hide */ 1715 public boolean isSupportedAxes(int axis) { 1716 synchronized (this) { 1717 if (mSupportedAxes == null) { 1718 mSupportedAxes = nativeGetSupportedAxes(native_instance); 1719 if (mSupportedAxes == null) { 1720 mSupportedAxes = EMPTY_AXES; 1721 } 1722 } 1723 } 1724 return Arrays.binarySearch(mSupportedAxes, axis) >= 0; 1725 } 1726 1727 private static native long nativeCreateFromTypeface(long native_instance, int style); 1728 private static native long nativeCreateFromTypefaceWithExactStyle( 1729 long native_instance, int weight, boolean italic); 1730 // TODO: clean up: change List<FontVariationAxis> to FontVariationAxis[] 1731 private static native long nativeCreateFromTypefaceWithVariation( 1732 long native_instance, List<FontVariationAxis> axes); 1733 @UnsupportedAppUsage 1734 private static native long nativeCreateWeightAlias(long native_instance, int weight); 1735 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 1736 private static native long nativeCreateFromArray( 1737 long[] familyArray, long fallbackTypeface, int weight, int italic); 1738 private static native int[] nativeGetSupportedAxes(long native_instance); 1739 1740 @CriticalNative 1741 private static native void nativeSetDefault(long nativePtr); 1742 1743 @CriticalNative 1744 private static native int nativeGetStyle(long nativePtr); 1745 1746 @CriticalNative 1747 private static native int nativeGetWeight(long nativePtr); 1748 1749 @CriticalNative 1750 private static native boolean nativeIsVariationInstance(long nativePtr); 1751 1752 @CriticalNative 1753 private static native long nativeGetReleaseFunc(); 1754 1755 private static native void nativeRegisterGenericFamily(String str, long nativePtr); 1756 1757 private static native int nativeWriteTypefaces( 1758 @Nullable ByteBuffer buffer, int position, @NonNull long[] nativePtrs); 1759 1760 private static native 1761 @Nullable long[] nativeReadTypefaces(@NonNull ByteBuffer buffer, int position); 1762 1763 private static native void nativeForceSetStaticFinalField(String fieldName, Typeface typeface); 1764 1765 @CriticalNative 1766 private static native void nativeAddFontCollections(long nativePtr); 1767 1768 private static native void nativeWarmUpCache(String fileName); 1769 1770 @FastNative 1771 private static native void nativeRegisterLocaleList(String locales); 1772 } 1773