|
18 | 18 |
|
19 | 19 | import com.google.android.material.R; |
20 | 20 |
|
| 21 | +import android.annotation.SuppressLint; |
21 | 22 | import android.content.Context; |
22 | 23 | import android.content.res.ColorStateList; |
23 | 24 | import android.content.res.Resources; |
|
27 | 28 | import android.os.Build.VERSION; |
28 | 29 | import android.os.Build.VERSION_CODES; |
29 | 30 | import android.text.TextPaint; |
| 31 | +import android.util.AttributeSet; |
30 | 32 | import android.util.Log; |
| 33 | +import android.util.Xml; |
31 | 34 | import androidx.annotation.FontRes; |
32 | 35 | import androidx.annotation.NonNull; |
33 | 36 | import androidx.annotation.Nullable; |
|
38 | 41 | import androidx.core.content.res.ResourcesCompat; |
39 | 42 | import androidx.core.content.res.ResourcesCompat.FontCallback; |
40 | 43 | import androidx.core.provider.FontsContractCompat.FontRequestCallback; |
| 44 | +import org.xmlpull.v1.XmlPullParser; |
41 | 45 |
|
42 | 46 | /** |
43 | 47 | * Utility class that contains the data from parsing a TextAppearance style resource. |
@@ -75,6 +79,7 @@ public class TextAppearance { |
75 | 79 | @FontRes private final int fontFamilyResourceId; |
76 | 80 |
|
77 | 81 | private boolean fontResolved = false; |
| 82 | + private boolean systemFontLoadAttempted = false; |
78 | 83 | private Typeface font; |
79 | 84 |
|
80 | 85 | /** Parses the given TextAppearance style resource. */ |
@@ -169,9 +174,7 @@ public Typeface getFont(@NonNull Context context) { |
169 | 174 | */ |
170 | 175 | public void getFontAsync( |
171 | 176 | @NonNull Context context, @NonNull final TextAppearanceFontCallback callback) { |
172 | | - if (shouldLoadFontSynchronously(context)) { |
173 | | - getFont(context); |
174 | | - } else { |
| 177 | + if (!maybeLoadFontSynchronously(context)) { |
175 | 178 | // No-op if font already resolved. |
176 | 179 | createFallbackFont(); |
177 | 180 | } |
@@ -325,8 +328,8 @@ public void updateMeasureState( |
325 | 328 | @NonNull Context context, |
326 | 329 | @NonNull TextPaint textPaint, |
327 | 330 | @NonNull TextAppearanceFontCallback callback) { |
328 | | - if (shouldLoadFontSynchronously(context)) { |
329 | | - updateTextPaintMeasureState(context, textPaint, getFont(context)); |
| 331 | + if (maybeLoadFontSynchronously(context) && fontResolved && font != null) { |
| 332 | + updateTextPaintMeasureState(context, textPaint, font); |
330 | 333 | } else { |
331 | 334 | getFontAsync(context, textPaint, callback); |
332 | 335 | } |
@@ -375,14 +378,81 @@ public void setTextSize(float textSize) { |
375 | 378 | this.textSize = textSize; |
376 | 379 | } |
377 | 380 |
|
378 | | - private boolean shouldLoadFontSynchronously(Context context) { |
| 381 | + private boolean maybeLoadFontSynchronously(Context context) { |
379 | 382 | if (TextAppearanceConfig.shouldLoadFontSynchronously()) { |
| 383 | + getFont(context); |
| 384 | + return true; |
| 385 | + } |
| 386 | + if (fontResolved) { |
| 387 | + return true; |
| 388 | + } |
| 389 | + if (fontFamilyResourceId == 0) { |
| 390 | + return false; |
| 391 | + } |
| 392 | + Typeface cachedFont = ResourcesCompat.getCachedFont(context, fontFamilyResourceId); |
| 393 | + if (cachedFont != null) { |
| 394 | + font = cachedFont; |
| 395 | + fontResolved = true; |
| 396 | + return true; |
| 397 | + } |
| 398 | + Typeface systemFont = getSystemTypeface(context); |
| 399 | + if (systemFont != null) { |
| 400 | + font = systemFont; |
| 401 | + fontResolved = true; |
380 | 402 | return true; |
381 | 403 | } |
382 | | - Typeface typeface = |
383 | | - (fontFamilyResourceId != 0) |
384 | | - ? ResourcesCompat.getCachedFont(context, fontFamilyResourceId) |
385 | | - : null; |
386 | | - return (typeface != null); |
| 404 | + return false; |
| 405 | + } |
| 406 | + |
| 407 | + @Nullable |
| 408 | + private Typeface getSystemTypeface(Context context) { |
| 409 | + if (systemFontLoadAttempted) { |
| 410 | + // Only attempt to load the system font once. |
| 411 | + return null; |
| 412 | + } |
| 413 | + systemFontLoadAttempted = true; |
| 414 | + |
| 415 | + String systemFontFamily = readFontProviderSystemFontFamily(context, fontFamilyResourceId); |
| 416 | + if (systemFontFamily == null) { |
| 417 | + return null; |
| 418 | + } |
| 419 | + |
| 420 | + Typeface regularSystemTypeface = Typeface.create(systemFontFamily, Typeface.NORMAL); |
| 421 | + if (regularSystemTypeface == Typeface.DEFAULT) { |
| 422 | + // If Typeface#create returned Typeface.DEFAULT then systemFontFamily is not present on the |
| 423 | + // device as a system font, so we will have to load the font asynchronously. |
| 424 | + return null; |
| 425 | + } |
| 426 | + |
| 427 | + return Typeface.create(regularSystemTypeface, textStyle); |
| 428 | + } |
| 429 | + |
| 430 | + @SuppressLint("ResourceType") |
| 431 | + @Nullable |
| 432 | + private static String readFontProviderSystemFontFamily( |
| 433 | + Context context, @FontRes int fontFamilyResourceId) { |
| 434 | + Resources resources = context.getResources(); |
| 435 | + if (fontFamilyResourceId == 0 |
| 436 | + || !resources.getResourceTypeName(fontFamilyResourceId).equals("font")) { |
| 437 | + return null; |
| 438 | + } |
| 439 | + |
| 440 | + try { |
| 441 | + XmlPullParser xpp = resources.getXml(fontFamilyResourceId); |
| 442 | + while (xpp.getEventType() != XmlPullParser.END_DOCUMENT) { |
| 443 | + if (xpp.getEventType() == XmlPullParser.START_TAG && xpp.getName().equals("font-family")) { |
| 444 | + AttributeSet attrs = Xml.asAttributeSet(xpp); |
| 445 | + TypedArray a = resources.obtainAttributes(attrs, androidx.core.R.styleable.FontFamily); |
| 446 | + String systemFontFamily = |
| 447 | + a.getString(androidx.core.R.styleable.FontFamily_fontProviderSystemFontFamily); |
| 448 | + a.recycle(); |
| 449 | + return systemFontFamily; |
| 450 | + } |
| 451 | + xpp.next(); |
| 452 | + } |
| 453 | + } catch (Throwable t) { |
| 454 | + // Fail silently if we can't find fontProviderSystemFontFamily for any reason. |
| 455 | + } |
| 456 | + return null; |
387 | 457 | } |
388 | 458 | } |
0 commit comments