From e7455a812fab18e6c3d0e8ea3fe701c1c6e4216d Mon Sep 17 00:00:00 2001 From: Seigo Nonaka Date: Wed, 4 Oct 2023 17:42:13 +0900 Subject: Support Locale Fallback Family Customization Bug: 303327287 Test: atest TypefaceSystemFallbackTest Test: atest CtsTextTestCases CtsGraphcisTestCases Change-Id: I50754faf91d6d7222366f26a640f4dd4c1b156b0 --- graphics/java/android/graphics/FontListParser.java | 4 +- .../graphics/fonts/FontCustomizationParser.java | 46 +++++++++- .../java/android/graphics/fonts/SystemFonts.java | 97 +++++++++++++++++++++- 3 files changed, 138 insertions(+), 9 deletions(-) (limited to 'graphics/java') 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 mAdditionalAliases; + private final List mLocaleFamilyCustomizations; + public Result() { mAdditionalNamedFamilies = Collections.emptyMap(); + mLocaleFamilyCustomizations = Collections.emptyList(); mAdditionalAliases = Collections.emptyList(); } public Result(Map additionalNamedFamilies, + List localeFamilyCustomizations, List additionalAliases) { mAdditionalNamedFamilies = additionalNamedFamilies; + mLocaleFamilyCustomizations = localeFamilyCustomizations; mAdditionalAliases = additionalAliases; } @@ -70,6 +77,10 @@ public class FontCustomizationParser { public List getAdditionalAliases() { return mAdditionalAliases; } + + public List getLocaleFamilyCustomizations() { + return mLocaleFamilyCustomizations; + } } /** @@ -89,7 +100,9 @@ public class FontCustomizationParser { } private static Result validateAndTransformToResult( - List families, List aliases) { + List families, + List outLocaleFamilies, + List aliases) { HashMap 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 families = new ArrayList<>(); List aliases = new ArrayList<>(); + List 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 out, + @NonNull List outCustomization, @Nullable Map 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 outBufferCache) { final ArrayMap fallbackListMap = new ArrayMap<>(); + final List localeFallbacks = + fontConfig.getLocaleFallbackCustomizations(); final List 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 customizations = new ArrayList<>(); final List 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; + } } -- cgit v1.2.3-59-g8ed1b