diff options
9 files changed, 436 insertions, 17 deletions
diff --git a/core/java/android/text/FontConfig.java b/core/java/android/text/FontConfig.java index c5857347fd45..94c8eaf13ccb 100644 --- a/core/java/android/text/FontConfig.java +++ b/core/java/android/text/FontConfig.java @@ -29,6 +29,7 @@ import android.compat.annotation.UnsupportedAppUsage; import android.graphics.fonts.FontFamily.Builder.VariableFontFamilyType; import android.graphics.fonts.FontStyle; import android.graphics.fonts.FontVariationAxis; +import android.icu.util.ULocale; import android.os.Build; import android.os.LocaleList; import android.os.Parcel; @@ -39,6 +40,7 @@ import java.lang.annotation.Retention; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.Objects; @@ -58,6 +60,7 @@ public final class FontConfig implements Parcelable { private final @NonNull List<FontFamily> mFamilies; private final @NonNull List<Alias> mAliases; private final @NonNull List<NamedFamilyList> mNamedFamilyLists; + private final @NonNull List<Customization.LocaleFallback> mLocaleFallbackCustomizations; private final long mLastModifiedTimeMillis; private final int mConfigVersion; @@ -71,10 +74,12 @@ public final class FontConfig implements Parcelable { */ public FontConfig(@NonNull List<FontFamily> families, @NonNull List<Alias> aliases, @NonNull List<NamedFamilyList> namedFamilyLists, + @NonNull List<Customization.LocaleFallback> localeFallbackCustomizations, long lastModifiedTimeMillis, @IntRange(from = 0) int configVersion) { mFamilies = families; mAliases = aliases; mNamedFamilyLists = namedFamilyLists; + mLocaleFallbackCustomizations = localeFallbackCustomizations; mLastModifiedTimeMillis = lastModifiedTimeMillis; mConfigVersion = configVersion; } @@ -84,7 +89,8 @@ public final class FontConfig implements Parcelable { */ public FontConfig(@NonNull List<FontFamily> families, @NonNull List<Alias> aliases, long lastModifiedTimeMillis, @IntRange(from = 0) int configVersion) { - this(families, aliases, Collections.emptyList(), lastModifiedTimeMillis, configVersion); + this(families, aliases, Collections.emptyList(), Collections.emptyList(), + lastModifiedTimeMillis, configVersion); } @@ -113,6 +119,18 @@ public final class FontConfig implements Parcelable { } /** + * Returns a locale fallback customizations. + * + * This field is used for creating the system fallback in the system server. This field is + * always empty in the application process. + * + * @hide + */ + public @NonNull List<Customization.LocaleFallback> getLocaleFallbackCustomizations() { + return mLocaleFallbackCustomizations; + } + + /** * Returns the last modified time in milliseconds. * * This is a value of {@link System#currentTimeMillis()} when the system font configuration was @@ -169,7 +187,9 @@ public final class FontConfig implements Parcelable { source.readTypedList(familyLists, NamedFamilyList.CREATOR); long lastModifiedDate = source.readLong(); int configVersion = source.readInt(); - return new FontConfig(families, aliases, familyLists, lastModifiedDate, configVersion); + return new FontConfig(families, aliases, familyLists, + Collections.emptyList(), // Don't need to pass customization to API caller. + lastModifiedDate, configVersion); } @Override @@ -813,4 +833,129 @@ public final class FontConfig implements Parcelable { + '}'; } } + + /** @hide */ + public static class Customization { + private Customization() {} // Singleton + + /** + * A class that represents customization of locale fallback + * + * This class represents a vendor customization of new-locale-family. + * + * <pre> + * <family customizationType="new-locale-family" operation="prepend" lang="ja-JP"> + * <font weight="400" style="normal">MyAlternativeFont.ttf + * <axis tag="wght" stylevalue="400"/> + * </font> + * </family> + * </pre> + * + * The operation can be one of prepend, replace or append. The operation prepend means that + * the new font family is inserted just before the original font family. The original font + * family is still in the fallback. The operation replace means that the original font + * family is replaced with new font family. The original font family is removed from the + * fallback. The operation append means that the new font family is inserted just after the + * original font family. The original font family is still in the fallback. + * + * The lang attribute is a BCP47 compliant language tag. The font fallback mainly uses ISO + * 15924 script code for matching. If the script code is missing, most likely script code + * will be used. + */ + public static class LocaleFallback { + private final Locale mLocale; + private final int mOperation; + private final FontFamily mFamily; + private final String mScript; + + public static final int OPERATION_PREPEND = 0; + public static final int OPERATION_APPEND = 1; + public static final int OPERATION_REPLACE = 2; + + /** @hide */ + @Retention(SOURCE) + @IntDef(prefix = { "OPERATION_" }, value = { + OPERATION_PREPEND, + OPERATION_APPEND, + OPERATION_REPLACE + }) + public @interface Operation {} + + + public LocaleFallback(@NonNull Locale locale, @Operation int operation, + @NonNull FontFamily family) { + mLocale = locale; + mOperation = operation; + mFamily = family; + mScript = resolveScript(locale); + } + + /** + * A customization target locale. + * @return a locale + */ + public @NonNull Locale getLocale() { + return mLocale; + } + + /** + * An operation to be applied to the original font family. + * + * The operation can be one of {@link #OPERATION_PREPEND}, {@link #OPERATION_REPLACE} or + * {@link #OPERATION_APPEND}. + * + * The operation prepend ({@link #OPERATION_PREPEND}) means that the new font family is + * inserted just before the original font family. The original font family is still in + * the fallback. + * + * The operation replace ({@link #OPERATION_REPLACE}) means that the original font + * family is replaced with new font family. The original font family is removed from the + * fallback. + * + * The operation append ({@link #OPERATION_APPEND}) means that the new font family is + * inserted just after the original font family. The original font family is still in + * the fallback. + * + * @return an operation. + */ + public @Operation int getOperation() { + return mOperation; + } + + /** + * Returns a family to be inserted or replaced to the fallback. + * + * @return a family + */ + public @NonNull FontFamily getFamily() { + return mFamily; + } + + /** + * Returns a script of the locale. If the script is missing in the given locale, the + * most likely locale is returned. + */ + public @NonNull String getScript() { + return mScript; + } + + @Override + public String toString() { + return "LocaleFallback{" + + "mLocale=" + mLocale + + ", mOperation=" + mOperation + + ", mFamily=" + mFamily + + '}'; + } + } + } + + /** @hide */ + public static String resolveScript(Locale locale) { + String script = locale.getScript(); + if (script != null && !script.isEmpty()) { + return script; + } + return ULocale.addLikelySubtags(ULocale.forLocale(locale)).getScript(); + } } diff --git a/core/java/android/text/flags/custom_locale_fallback.aconfig b/core/java/android/text/flags/custom_locale_fallback.aconfig new file mode 100644 index 000000000000..04e64f96454b --- /dev/null +++ b/core/java/android/text/flags/custom_locale_fallback.aconfig @@ -0,0 +1,8 @@ +package: "com.android.text.flags" + +flag { + name: "custom_locale_fallback" + namespace: "text" + description: "A feature flag that adds custom locale fallback to the vendor customization XML. This enables vendors to add their locale specific fonts, e.g. Japanese font." + bug: "" +} diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp index 81dab0833af1..2993a0e63228 100644 --- a/core/tests/coretests/Android.bp +++ b/core/tests/coretests/Android.bp @@ -64,6 +64,7 @@ android_test { "servicestests-utils", "device-time-shell-utils", "testables", + "com.android.text.flags-aconfig-java", ], libs: [ diff --git a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java index a8a5059eea20..3f78396e3a70 100644 --- a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java +++ b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java @@ -16,6 +16,8 @@ package android.graphics; +import static com.android.text.flags.Flags.FLAG_CUSTOM_LOCALE_FALLBACK; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -30,6 +32,9 @@ import android.graphics.fonts.FontFamily; import android.graphics.fonts.SystemFonts; import android.graphics.text.PositionedGlyphs; import android.graphics.text.TextRunShaper; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.text.FontConfig; import android.util.ArrayMap; @@ -39,6 +44,7 @@ import androidx.test.runner.AndroidJUnit4; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.xmlpull.v1.XmlPullParserException; @@ -107,6 +113,10 @@ public class TypefaceSystemFallbackTest { GLYPH_2EM_WIDTH = paint.measureText("a"); } + @Rule + public final CheckFlagsRule mCheckFlagsRule = + DeviceFlagsValueProvider.createCheckFlagsRule(); + @Before public void setUp() { final AssetManager am = @@ -877,6 +887,130 @@ public class TypefaceSystemFallbackTest { assertEquals(GLYPH_1EM_WIDTH, paint.measureText("c"), 0.0f); } + private static void assertA3emFontIsUsed(Typeface typeface) { + final Paint paint = new Paint(); + assertNotNull(typeface); + paint.setTypeface(typeface); + assertTrue("a3em font must be used", GLYPH_3EM_WIDTH == paint.measureText("a") + && GLYPH_1EM_WIDTH == paint.measureText("b") + && GLYPH_1EM_WIDTH == paint.measureText("c")); + } + + private static void assertB3emFontIsUsed(Typeface typeface) { + final Paint paint = new Paint(); + assertNotNull(typeface); + paint.setTypeface(typeface); + assertTrue("b3em font must be used", GLYPH_1EM_WIDTH == paint.measureText("a") + && GLYPH_3EM_WIDTH == paint.measureText("b") + && GLYPH_1EM_WIDTH == paint.measureText("c")); + } + + private static String getBaseXml(String font, String lang) { + final String xml = "<?xml version='1.0' encoding='UTF-8'?>" + + "<familyset>" + + " <family>" + + " <font weight='400' style='normal'>no_coverage.ttf</font>" + + " </family>" + + " <family name='named-family'>" + + " <font weight='400' style='normal'>no_coverage.ttf</font>" + + " </family>" + + " <family lang='%s'>" + + " <font weight='400' style='normal'>%s</font>" + + " </family>" + + "</familyset>"; + return String.format(xml, lang, font); + } + + private static String getCustomizationXml(String font, String op, String lang) { + final String xml = "<?xml version='1.0' encoding='UTF-8'?>" + + "<fonts-modification version='1'>" + + " <family customizationType='new-locale-family' operation='%s' lang='%s'>" + + " <font weight='400' style='normal' fallbackFor='named-family'>%s</font>" + + " </family>" + + "</fonts-modification>"; + return String.format(xml, op, lang, font); + } + + @RequiresFlagsEnabled(FLAG_CUSTOM_LOCALE_FALLBACK) + @Test + public void testBuildSystemFallback__Customization_locale_prepend() { + final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); + final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>(); + + buildSystemFallback( + getBaseXml("a3em.ttf", "ja-JP"), + getCustomizationXml("b3em.ttf", "prepend", "ja-JP"), + fontMap, fallbackMap); + Typeface typeface = fontMap.get("named-family"); + + // operation "prepend" places font before the original font, thus b3em is used. + assertB3emFontIsUsed(typeface); + } + + @RequiresFlagsEnabled(FLAG_CUSTOM_LOCALE_FALLBACK) + @Test + public void testBuildSystemFallback__Customization_locale_replace() { + final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); + final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>(); + + buildSystemFallback( + getBaseXml("a3em.ttf", "ja-JP"), + getCustomizationXml("b3em.ttf", "replace", "ja-JP"), + fontMap, fallbackMap); + Typeface typeface = fontMap.get("named-family"); + + // operation "replace" removes the original font, thus b3em font is used. + assertB3emFontIsUsed(typeface); + } + + @RequiresFlagsEnabled(FLAG_CUSTOM_LOCALE_FALLBACK) + @Test + public void testBuildSystemFallback__Customization_locale_append() { + final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); + final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>(); + + buildSystemFallback( + getBaseXml("a3em.ttf", "ja-JP"), + getCustomizationXml("b3em.ttf", "append", "ja-JP"), + fontMap, fallbackMap); + Typeface typeface = fontMap.get("named-family"); + + // operation "append" comes next to the original font, so the original "a3em" is used. + assertA3emFontIsUsed(typeface); + } + + @RequiresFlagsEnabled(FLAG_CUSTOM_LOCALE_FALLBACK) + @Test + public void testBuildSystemFallback__Customization_locale_ScriptMismatch() { + final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); + final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>(); + + buildSystemFallback( + getBaseXml("a3em.ttf", "ja-JP"), + getCustomizationXml("b3em.ttf", "replace", "ko-KR"), + fontMap, fallbackMap); + Typeface typeface = fontMap.get("named-family"); + + // Since the script doesn't match, the customization is ignored. + assertA3emFontIsUsed(typeface); + } + + @RequiresFlagsEnabled(FLAG_CUSTOM_LOCALE_FALLBACK) + @Test + public void testBuildSystemFallback__Customization_locale_SubscriptMatch() { + final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); + final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>(); + + buildSystemFallback( + getBaseXml("a3em.ttf", "ja-JP"), + getCustomizationXml("b3em.ttf", "replace", "ko-Hani-KR"), + fontMap, fallbackMap); + Typeface typeface = fontMap.get("named-family"); + + // Hani script is supported by Japanese, Jpan. + assertB3emFontIsUsed(typeface); + } + @Test(expected = IllegalArgumentException.class) public void testBuildSystemFallback__Customization_new_named_family_no_name_exception() { final String oemXml = "<?xml version='1.0' encoding='UTF-8'?>" @@ -902,7 +1036,6 @@ public class TypefaceSystemFallbackTest { readFontCustomization(oemXml); } - @Test public void testBuildSystemFallback_UpdatableFont() { final String xml = "<?xml version='1.0' encoding='UTF-8'?>" diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java index 735bc180c015..52b0b95d3e76 100644 --- a/graphics/java/android/graphics/FontListParser.java +++ b/graphics/java/android/graphics/FontListParser.java @@ -236,7 +236,9 @@ public class FontListParser { } } - return new FontConfig(families, filtered, resultNamedFamilies, lastModifiedDate, + return new FontConfig(families, filtered, resultNamedFamilies, + customization.getLocaleFamilyCustomizations(), + lastModifiedDate, configVersion); } diff --git a/graphics/java/android/graphics/fonts/FontCustomizationParser.java b/graphics/java/android/graphics/fonts/FontCustomizationParser.java index b458dd9021d0..6e04a2f5e405 100644 --- a/graphics/java/android/graphics/fonts/FontCustomizationParser.java +++ b/graphics/java/android/graphics/fonts/FontCustomizationParser.java @@ -22,6 +22,7 @@ import static android.text.FontConfig.NamedFamilyList; import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.FontListParser; +import android.text.FontConfig; import android.util.Xml; import org.xmlpull.v1.XmlPullParser; @@ -34,6 +35,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; /** @@ -52,14 +54,19 @@ public class FontCustomizationParser { private final List<Alias> mAdditionalAliases; + private final List<FontConfig.Customization.LocaleFallback> mLocaleFamilyCustomizations; + public Result() { mAdditionalNamedFamilies = Collections.emptyMap(); + mLocaleFamilyCustomizations = Collections.emptyList(); mAdditionalAliases = Collections.emptyList(); } public Result(Map<String, NamedFamilyList> additionalNamedFamilies, + List<FontConfig.Customization.LocaleFallback> localeFamilyCustomizations, List<Alias> additionalAliases) { mAdditionalNamedFamilies = additionalNamedFamilies; + mLocaleFamilyCustomizations = localeFamilyCustomizations; mAdditionalAliases = additionalAliases; } @@ -70,6 +77,10 @@ public class FontCustomizationParser { public List<Alias> getAdditionalAliases() { return mAdditionalAliases; } + + public List<FontConfig.Customization.LocaleFallback> getLocaleFamilyCustomizations() { + return mLocaleFamilyCustomizations; + } } /** @@ -89,7 +100,9 @@ public class FontCustomizationParser { } private static Result validateAndTransformToResult( - List<NamedFamilyList> families, List<Alias> aliases) { + List<NamedFamilyList> families, + List<FontConfig.Customization.LocaleFallback> outLocaleFamilies, + List<Alias> aliases) { HashMap<String, NamedFamilyList> namedFamily = new HashMap<>(); for (int i = 0; i < families.size(); ++i) { final NamedFamilyList family = families.get(i); @@ -105,7 +118,7 @@ public class FontCustomizationParser { + "requires fallackTarget attribute"); } } - return new Result(namedFamily, aliases); + return new Result(namedFamily, outLocaleFamilies, aliases); } private static Result readFamilies( @@ -115,12 +128,13 @@ public class FontCustomizationParser { ) throws XmlPullParserException, IOException { List<NamedFamilyList> families = new ArrayList<>(); List<Alias> aliases = new ArrayList<>(); + List<FontConfig.Customization.LocaleFallback> outLocaleFamilies = new ArrayList<>(); parser.require(XmlPullParser.START_TAG, null, "fonts-modification"); while (parser.next() != XmlPullParser.END_TAG) { if (parser.getEventType() != XmlPullParser.START_TAG) continue; String tag = parser.getName(); if (tag.equals("family")) { - readFamily(parser, fontDir, families, updatableFontMap); + readFamily(parser, fontDir, families, outLocaleFamilies, updatableFontMap); } else if (tag.equals("family-list")) { readFamilyList(parser, fontDir, families, updatableFontMap); } else if (tag.equals("alias")) { @@ -129,13 +143,14 @@ public class FontCustomizationParser { FontListParser.skip(parser); } } - return validateAndTransformToResult(families, aliases); + return validateAndTransformToResult(families, outLocaleFamilies, aliases); } private static void readFamily( @NonNull XmlPullParser parser, @NonNull String fontDir, @NonNull List<NamedFamilyList> out, + @NonNull List<FontConfig.Customization.LocaleFallback> outCustomization, @Nullable Map<String, File> updatableFontMap) throws XmlPullParserException, IOException { final String customizationType = parser.getAttributeValue(null, "customizationType"); @@ -148,6 +163,29 @@ public class FontCustomizationParser { if (fontFamily != null) { out.add(fontFamily); } + } else if (customizationType.equals("new-locale-family")) { + final String lang = parser.getAttributeValue(null, "lang"); + final String op = parser.getAttributeValue(null, "operation"); + final int intOp; + if (op.equals("append")) { + intOp = FontConfig.Customization.LocaleFallback.OPERATION_APPEND; + } else if (op.equals("prepend")) { + intOp = FontConfig.Customization.LocaleFallback.OPERATION_PREPEND; + } else if (op.equals("replace")) { + intOp = FontConfig.Customization.LocaleFallback.OPERATION_REPLACE; + } else { + throw new IllegalArgumentException("Unknown operation=" + op); + } + + final FontConfig.FontFamily family = FontListParser.readFamily( + parser, fontDir, updatableFontMap, false); + + // For ignoring the customization, consume the new-locale-family element but don't + // register any customizations. + if (com.android.text.flags.Flags.customLocaleFallback()) { + outCustomization.add(new FontConfig.Customization.LocaleFallback( + Locale.forLanguageTag(lang), intOp, family)); + } } else { throw new IllegalArgumentException("Unknown customizationType=" + customizationType); } diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java index d4e35b30c8d0..618aa5b5019c 100644 --- a/graphics/java/android/graphics/fonts/SystemFonts.java +++ b/graphics/java/android/graphics/fonts/SystemFonts.java @@ -16,10 +16,15 @@ package android.graphics.fonts; +import static android.text.FontConfig.Customization.LocaleFallback.OPERATION_APPEND; +import static android.text.FontConfig.Customization.LocaleFallback.OPERATION_PREPEND; +import static android.text.FontConfig.Customization.LocaleFallback.OPERATION_REPLACE; + import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.FontListParser; import android.graphics.Typeface; +import android.os.LocaleList; import android.text.FontConfig; import android.util.ArrayMap; import android.util.Log; @@ -38,6 +43,7 @@ import java.nio.channels.FileChannel; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; @@ -119,7 +125,6 @@ public final class SystemFonts { } } - final FontFamily defaultFamily = defaultFonts.isEmpty() ? null : createFontFamily( defaultFonts, languageTags, variant, xmlFamily.getVariableFontFamilyType(), false, cache); @@ -300,11 +305,11 @@ public final class SystemFonts { } catch (IOException e) { Log.e(TAG, "Failed to open/read system font configurations.", e); return new FontConfig(Collections.emptyList(), Collections.emptyList(), - Collections.emptyList(), 0, 0); + Collections.emptyList(), Collections.emptyList(), 0, 0); } catch (XmlPullParserException e) { Log.e(TAG, "Failed to parse the system font configuration.", e); return new FontConfig(Collections.emptyList(), Collections.emptyList(), - Collections.emptyList(), 0, 0); + Collections.emptyList(), Collections.emptyList(), 0, 0); } } @@ -328,6 +333,8 @@ public final class SystemFonts { ArrayMap<String, ByteBuffer> outBufferCache) { final ArrayMap<String, NativeFamilyListSet> fallbackListMap = new ArrayMap<>(); + final List<FontConfig.Customization.LocaleFallback> localeFallbacks = + fontConfig.getLocaleFallbackCustomizations(); final List<FontConfig.NamedFamilyList> namedFamilies = fontConfig.getNamedFamilyLists(); for (int i = 0; i < namedFamilies.size(); ++i) { @@ -336,10 +343,54 @@ public final class SystemFonts { } // Then, add fallback fonts to the fallback map. + final List<FontConfig.Customization.LocaleFallback> customizations = new ArrayList<>(); final List<FontConfig.FontFamily> xmlFamilies = fontConfig.getFontFamilies(); + final SparseIntArray seenCustomization = new SparseIntArray(); for (int i = 0; i < xmlFamilies.size(); i++) { final FontConfig.FontFamily xmlFamily = xmlFamilies.get(i); - pushFamilyToFallback(xmlFamily, fallbackListMap, outBufferCache); + + customizations.clear(); + for (int j = 0; j < localeFallbacks.size(); ++j) { + if (seenCustomization.get(j, -1) != -1) { + continue; // The customization is already applied. + } + FontConfig.Customization.LocaleFallback localeFallback = localeFallbacks.get(j); + if (scriptMatch(xmlFamily.getLocaleList(), localeFallback.getScript())) { + customizations.add(localeFallback); + seenCustomization.put(j, 1); + } + } + + if (customizations.isEmpty()) { + pushFamilyToFallback(xmlFamily, fallbackListMap, outBufferCache); + } else { + for (int j = 0; j < customizations.size(); ++j) { + FontConfig.Customization.LocaleFallback localeFallback = customizations.get(j); + if (localeFallback.getOperation() == OPERATION_PREPEND) { + pushFamilyToFallback(localeFallback.getFamily(), fallbackListMap, + outBufferCache); + } + } + boolean isReplaced = false; + for (int j = 0; j < customizations.size(); ++j) { + FontConfig.Customization.LocaleFallback localeFallback = customizations.get(j); + if (localeFallback.getOperation() == OPERATION_REPLACE) { + pushFamilyToFallback(localeFallback.getFamily(), fallbackListMap, + outBufferCache); + isReplaced = true; + } + } + if (!isReplaced) { // If nothing is replaced, push the original one. + pushFamilyToFallback(xmlFamily, fallbackListMap, outBufferCache); + } + for (int j = 0; j < customizations.size(); ++j) { + FontConfig.Customization.LocaleFallback localeFallback = customizations.get(j); + if (localeFallback.getOperation() == OPERATION_APPEND) { + pushFamilyToFallback(localeFallback.getFamily(), fallbackListMap, + outBufferCache); + } + } + } } // Build the font map and fallback map. @@ -365,4 +416,42 @@ public final class SystemFonts { Typeface.initSystemDefaultTypefaces(fallbackMap, fontConfig.getAliases(), result); return result; } + + private static boolean scriptMatch(LocaleList localeList, String targetScript) { + if (localeList == null || localeList.isEmpty()) { + return false; + } + for (int i = 0; i < localeList.size(); ++i) { + Locale locale = localeList.get(i); + if (locale == null) { + continue; + } + String baseScript = FontConfig.resolveScript(locale); + if (baseScript.equals(targetScript)) { + return true; + } + + // Subtag match + if (targetScript.equals("Bopo") && baseScript.equals("Hanb")) { + // Hanb is Han with Bopomofo. + return true; + } else if (targetScript.equals("Hani")) { + if (baseScript.equals("Hanb") || baseScript.equals("Hans") + || baseScript.equals("Hant") || baseScript.equals("Kore") + || baseScript.equals("Jpan")) { + // Han id suppoted by Taiwanese, Traditional Chinese, Simplified Chinese, Korean + // and Japanese. + return true; + } + } else if (targetScript.equals("Hira") || targetScript.equals("Hrkt") + || targetScript.equals("Kana")) { + if (baseScript.equals("Jpan") || baseScript.equals("Hrkt")) { + // Hiragana, Hiragana-Katakana, Katakana is supported by Japanese and + // Hiragana-Katakana script. + return true; + } + } + } + return false; + } } diff --git a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java index cd3d2f031455..0f40ca082663 100644 --- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java +++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java @@ -619,8 +619,8 @@ final class UpdatableFontDir { } return new FontConfig( - config.getFontFamilies(), config.getAliases(), mergedFamilies, mLastModifiedMillis, - mConfigVersion); + config.getFontFamilies(), config.getAliases(), mergedFamilies, + config.getLocaleFallbackCustomizations(), mLastModifiedMillis, mConfigVersion); } private PersistentSystemFontConfig.Config readPersistentConfig() { diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java index 184c976b86bf..3de167e72ba0 100644 --- a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java +++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java @@ -336,7 +336,8 @@ public final class UpdatableFontDirTest { return new FontConfig(Collections.emptyList(), Collections.emptyList(), Collections.singletonList(new FontConfig.NamedFamilyList( - Collections.singletonList(family), "sans-serif")), 0, 1); + Collections.singletonList(family), "sans-serif")), + Collections.emptyList(), 0, 1); }; UpdatableFontDir dirForPreparation = new UpdatableFontDir( @@ -499,7 +500,8 @@ public final class UpdatableFontDirTest { Collections.emptyList(), Collections.emptyList(), Collections.singletonList(new FontConfig.NamedFamilyList( - Collections.singletonList(family), "sans-serif")), 0, 1); + Collections.singletonList(family), "sans-serif")), + Collections.emptyList(), 0, 1); }); dir.loadFontFileMap(); @@ -651,7 +653,8 @@ public final class UpdatableFontDirTest { FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE); return new FontConfig(Collections.emptyList(), Collections.emptyList(), Collections.singletonList(new FontConfig.NamedFamilyList( - Collections.singletonList(family), "sans-serif")), 0, 1); + Collections.singletonList(family), "sans-serif")), + Collections.emptyList(), 0, 1); }); dir.loadFontFileMap(); |