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