diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 566ac4535b0d1..c5b7a712bfa2c 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -16178,6 +16178,7 @@ package android.text { method @IntRange(from=0) public int getConfigVersion(); method @NonNull public java.util.List getFontFamilies(); method public long getLastModifiedTimeMillis(); + method @NonNull public java.util.List getNamedFamilyLists(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator CREATOR; } @@ -16207,7 +16208,7 @@ package android.text { method public int describeContents(); method @NonNull public java.util.List getFontList(); method @NonNull public android.os.LocaleList getLocaleList(); - method @Nullable public String getName(); + method @Deprecated @Nullable public String getName(); method public int getVariant(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator CREATOR; @@ -16216,6 +16217,14 @@ package android.text { field public static final int VARIANT_ELEGANT = 2; // 0x2 } + public static final class FontConfig.NamedFamilyList implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public java.util.List getFamilies(); + method @NonNull public String getName(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + } + } package android.util { diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 5e02e72f20885..5238c5da244ed 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -2706,6 +2706,7 @@ package android.text { method @IntRange(from=0) public int getConfigVersion(); method @NonNull public java.util.List getFontFamilies(); method public long getLastModifiedTimeMillis(); + method @NonNull public java.util.List getNamedFamilyLists(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator CREATOR; } @@ -2735,7 +2736,7 @@ package android.text { method public int describeContents(); method @NonNull public java.util.List getFontList(); method @NonNull public android.os.LocaleList getLocaleList(); - method @Nullable public String getName(); + method @Deprecated @Nullable public String getName(); method public int getVariant(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator CREATOR; @@ -2744,6 +2745,14 @@ package android.text { field public static final int VARIANT_ELEGANT = 2; // 0x2 } + public static final class FontConfig.NamedFamilyList implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public java.util.List getFamilies(); + method @NonNull public String getName(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + } + public static final class Selection.MemoryTextWatcher implements android.text.TextWatcher { ctor public Selection.MemoryTextWatcher(); method public void afterTextChanged(android.text.Editable); diff --git a/core/java/android/text/FontConfig.java b/core/java/android/text/FontConfig.java index 32b3bc62a8cd0..cb488b08456bd 100644 --- a/core/java/android/text/FontConfig.java +++ b/core/java/android/text/FontConfig.java @@ -36,6 +36,7 @@ import android.os.Parcelable; import java.io.File; import java.lang.annotation.Retention; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Objects; @@ -55,6 +56,7 @@ import java.util.Objects; public final class FontConfig implements Parcelable { private final @NonNull List mFamilies; private final @NonNull List mAliases; + private final @NonNull List mNamedFamilyLists; private final long mLastModifiedTimeMillis; private final int mConfigVersion; @@ -67,13 +69,24 @@ public final class FontConfig implements Parcelable { * @hide Only system server can create this instance and passed via IPC. */ public FontConfig(@NonNull List families, @NonNull List aliases, + @NonNull List namedFamilyLists, long lastModifiedTimeMillis, @IntRange(from = 0) int configVersion) { mFamilies = families; mAliases = aliases; + mNamedFamilyLists = namedFamilyLists; mLastModifiedTimeMillis = lastModifiedTimeMillis; mConfigVersion = configVersion; } + /** + * @hide Keep this constructor for reoborectric. + */ + public FontConfig(@NonNull List families, @NonNull List aliases, + long lastModifiedTimeMillis, @IntRange(from = 0) int configVersion) { + this(families, aliases, Collections.emptyList(), lastModifiedTimeMillis, configVersion); + } + + /** * Returns the ordered list of font families available in the system. * @@ -94,6 +107,10 @@ public final class FontConfig implements Parcelable { return mAliases; } + public @NonNull List getNamedFamilyLists() { + return mNamedFamilyLists; + } + /** * Returns the last modified time in milliseconds. * @@ -133,8 +150,9 @@ public final class FontConfig implements Parcelable { @Override public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeParcelableList(mFamilies, flags); - dest.writeParcelableList(mAliases, flags); + dest.writeTypedList(mFamilies, flags); + dest.writeTypedList(mAliases, flags); + dest.writeTypedList(mNamedFamilyLists, flags); dest.writeLong(mLastModifiedTimeMillis); dest.writeInt(mConfigVersion); } @@ -142,13 +160,15 @@ public final class FontConfig implements Parcelable { public static final @NonNull Creator CREATOR = new Creator() { @Override public FontConfig createFromParcel(Parcel source) { - List families = source.readParcelableList(new ArrayList<>(), - FontFamily.class.getClassLoader(), android.text.FontConfig.FontFamily.class); - List aliases = source.readParcelableList(new ArrayList<>(), - Alias.class.getClassLoader(), android.text.FontConfig.Alias.class); + final List families = new ArrayList<>(); + source.readTypedList(families, FontFamily.CREATOR); + final List aliases = new ArrayList<>(); + source.readTypedList(aliases, Alias.CREATOR); + final List familyLists = new ArrayList<>(); + source.readTypedList(familyLists, NamedFamilyList.CREATOR); long lastModifiedDate = source.readLong(); int configVersion = source.readInt(); - return new FontConfig(families, aliases, lastModifiedDate, configVersion); + return new FontConfig(families, aliases, familyLists, lastModifiedDate, configVersion); } @Override @@ -506,7 +526,6 @@ public final class FontConfig implements Parcelable { */ public static final class FontFamily implements Parcelable { private final @NonNull List mFonts; - private final @Nullable String mName; private final @NonNull LocaleList mLocaleList; private final @Variant int mVariant; @@ -547,10 +566,9 @@ public final class FontConfig implements Parcelable { * * @hide Only system server can create this instance and passed via IPC. */ - public FontFamily(@NonNull List fonts, @Nullable String name, - @NonNull LocaleList localeList, @Variant int variant) { + public FontFamily(@NonNull List fonts, @NonNull LocaleList localeList, + @Variant int variant) { mFonts = fonts; - mName = name; mLocaleList = localeList; mVariant = variant; } @@ -577,9 +595,13 @@ public final class FontConfig implements Parcelable { * * When the name of a {@link FontFamily} is null, it will be appended to all of the * {@code Fallback List}s. + * + * @deprecated From API 34, this function always returns null. All font families which have + * name attribute will be reported as a {@link NamedFamilyList}. */ + @Deprecated public @Nullable String getName() { - return mName; + return null; } /** @@ -606,8 +628,7 @@ public final class FontConfig implements Parcelable { @Override public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeParcelableList(mFonts, flags); - dest.writeString8(mName); + dest.writeTypedList(mFonts, flags); dest.writeString8(mLocaleList.toLanguageTags()); dest.writeInt(mVariant); } @@ -616,13 +637,12 @@ public final class FontConfig implements Parcelable { @Override public FontFamily createFromParcel(Parcel source) { - List fonts = source.readParcelableList( - new ArrayList<>(), Font.class.getClassLoader(), android.text.FontConfig.Font.class); - String name = source.readString8(); + List fonts = new ArrayList<>(); + source.readTypedList(fonts, Font.CREATOR); String langTags = source.readString8(); int variant = source.readInt(); - return new FontFamily(fonts, name, LocaleList.forLanguageTags(langTags), variant); + return new FontFamily(fonts, LocaleList.forLanguageTags(langTags), variant); } @Override @@ -659,23 +679,118 @@ public final class FontConfig implements Parcelable { FontFamily that = (FontFamily) o; return mVariant == that.mVariant && Objects.equals(mFonts, that.mFonts) - && Objects.equals(mName, that.mName) && Objects.equals(mLocaleList, that.mLocaleList); } @Override public int hashCode() { - return Objects.hash(mFonts, mName, mLocaleList, mVariant); + return Objects.hash(mFonts, mLocaleList, mVariant); } @Override public String toString() { return "FontFamily{" + "mFonts=" + mFonts - + ", mName='" + mName + '\'' + ", mLocaleList=" + mLocaleList + ", mVariant=" + mVariant + '}'; } } + + /** + * Represents list of font family in the system font configuration. + * + * In the fonts_customization.xml, it can define the list of FontFamily as a named family. The + * list of FontFamily is treated as a fallback list when drawing. + * + * @see android.graphics.fonts.FontFamily + */ + public static final class NamedFamilyList implements Parcelable { + private final List mFamilies; + private final String mName; + + /** @hide */ + public NamedFamilyList(@NonNull List families, @NonNull String name) { + mFamilies = families; + mName = name; + } + + /** @hide */ + public NamedFamilyList(@NonNull FontFamily family) { + mFamilies = new ArrayList<>(); + mFamilies.add(family); + mName = family.getName(); + } + + /** + * A list of font families. + * + * @return a list of font families. + */ + public @NonNull List getFamilies() { + return mFamilies; + } + + /** + * Returns the name of the {@link FontFamily}. + * + * This name is used to create a new {@code Fallback List}. + * + * For example, if the {@link FontFamily} has the name "serif", then the system will create + * a “serif” {@code Fallback List} and it can be used by creating a Typeface via + * {@code Typeface.create("serif", Typeface.NORMAL);} + */ + public @NonNull String getName() { + return mName; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@androidx.annotation.NonNull Parcel dest, int flags) { + dest.writeTypedList(mFamilies, flags); + dest.writeString8(mName); + } + + public static final @NonNull Creator CREATOR = new Creator<>() { + + @Override + public NamedFamilyList createFromParcel(Parcel source) { + final List families = new ArrayList<>(); + source.readTypedList(families, FontFamily.CREATOR); + String name = source.readString8(); + return new NamedFamilyList(families, name); + } + + @Override + public NamedFamilyList[] newArray(int size) { + return new NamedFamilyList[size]; + } + }; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + NamedFamilyList that = (NamedFamilyList) o; + return Objects.equals(mFamilies, that.mFamilies) && Objects.equals(mName, + that.mName); + } + + @Override + public int hashCode() { + return Objects.hash(mFamilies, mName); + } + + @Override + public String toString() { + return "NamedFamilyList{" + + "mFamilies=" + mFamilies + + ", mName='" + mName + '\'' + + '}'; + } + } } diff --git a/core/tests/coretests/assets/fonts/a3em.ttf b/core/tests/coretests/assets/fonts/a3em.ttf index a601ce2ed9324..4c1ad1be814dc 100644 Binary files a/core/tests/coretests/assets/fonts/a3em.ttf and b/core/tests/coretests/assets/fonts/a3em.ttf differ diff --git a/core/tests/coretests/assets/fonts/a3em.ttx b/core/tests/coretests/assets/fonts/a3em.ttx index d3b9e16037641..d7da0da2b0ab4 100644 --- a/core/tests/coretests/assets/fonts/a3em.ttx +++ b/core/tests/coretests/assets/fonts/a3em.ttx @@ -156,7 +156,7 @@ Sample Font - SampleFont-Regular + a3em Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/core/tests/coretests/assets/fonts/b3em.ttf b/core/tests/coretests/assets/fonts/b3em.ttf index 63948a22c1137..b18d4bb4d8f7c 100644 Binary files a/core/tests/coretests/assets/fonts/b3em.ttf and b/core/tests/coretests/assets/fonts/b3em.ttf differ diff --git a/core/tests/coretests/assets/fonts/b3em.ttx b/core/tests/coretests/assets/fonts/b3em.ttx index b5a77ef09bd3d..217393a6b8520 100644 --- a/core/tests/coretests/assets/fonts/b3em.ttx +++ b/core/tests/coretests/assets/fonts/b3em.ttx @@ -156,7 +156,7 @@ Sample Font - SampleFont-Regular + b3em Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/core/tests/coretests/assets/fonts/c3em.ttf b/core/tests/coretests/assets/fonts/c3em.ttf index badc3e29868fb..83a5db28b991e 100644 Binary files a/core/tests/coretests/assets/fonts/c3em.ttf and b/core/tests/coretests/assets/fonts/c3em.ttf differ diff --git a/core/tests/coretests/assets/fonts/c3em.ttx b/core/tests/coretests/assets/fonts/c3em.ttx index f5ed8e556332b..7535bea979db4 100644 --- a/core/tests/coretests/assets/fonts/c3em.ttx +++ b/core/tests/coretests/assets/fonts/c3em.ttx @@ -156,7 +156,7 @@ Sample Font - SampleFont-Regular + c3em Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/core/tests/coretests/assets/fonts/fallback.ttf b/core/tests/coretests/assets/fonts/fallback.ttf new file mode 100644 index 0000000000000..1ba8639c07ab4 Binary files /dev/null and b/core/tests/coretests/assets/fonts/fallback.ttf differ diff --git a/core/tests/coretests/assets/fonts/fallback.ttx b/core/tests/coretests/assets/fonts/fallback.ttx new file mode 100644 index 0000000000000..8e4b3e6712d32 --- /dev/null +++ b/core/tests/coretests/assets/fonts/fallback.ttx @@ -0,0 +1,205 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Copyright (C) 2017 The Android Open Source Project + + + Sample Font + + + Regular + + + Sample Font + + + fallback + + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + http://www.apache.org/licenses/LICENSE-2.0 + + + + + + + + + + + + + + + + diff --git a/core/tests/coretests/assets/fonts/fallback_capital.ttf b/core/tests/coretests/assets/fonts/fallback_capital.ttf new file mode 100644 index 0000000000000..073761f08341b Binary files /dev/null and b/core/tests/coretests/assets/fonts/fallback_capital.ttf differ diff --git a/core/tests/coretests/assets/fonts/fallback_capital.ttx b/core/tests/coretests/assets/fonts/fallback_capital.ttx new file mode 100644 index 0000000000000..710c95e091a73 --- /dev/null +++ b/core/tests/coretests/assets/fonts/fallback_capital.ttx @@ -0,0 +1,205 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Copyright (C) 2017 The Android Open Source Project + + + Sample Font + + + Regular + + + Sample Font + + + fallback_capital + + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + http://www.apache.org/licenses/LICENSE-2.0 + + + + + + + + + + + + + + + + diff --git a/core/tests/coretests/src/android/graphics/FontListParserTest.java b/core/tests/coretests/src/android/graphics/FontListParserTest.java index 479e52aab0297..d46f7625b596f 100644 --- a/core/tests/coretests/src/android/graphics/FontListParserTest.java +++ b/core/tests/coretests/src/android/graphics/FontListParserTest.java @@ -47,6 +47,7 @@ import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.Arrays; +import java.util.Collections; import java.util.List; @SmallTest @@ -59,14 +60,12 @@ public final class FontListParserTest { + "" + " test.ttf" + ""; - FontConfig.FontFamily expected = new FontConfig.FontFamily( - Arrays.asList( - new FontConfig.Font(new File("test.ttf"), null, "test", - new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), - 0, "", null)), - "sans-serif", LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT); - - FontConfig.FontFamily family = readFamily(xml); + FontConfig.NamedFamilyList expected = new FontConfig.NamedFamilyList( + Collections.singletonList(new FontConfig.FontFamily( + Arrays.asList(new FontConfig.Font(new File("test.ttf"), null, "test", + new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 0, "", null)), + LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT)), "sans-serif"); + FontConfig.NamedFamilyList family = readNamedFamily(xml); assertThat(family).isEqualTo(expected); } @@ -85,7 +84,7 @@ public final class FontListParserTest { new FontConfig.Font(new File("test.ttf"), null, "test", new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 0, "", "serif")), - null, LocaleList.forLanguageTags("en"), VARIANT_DEFAULT); + LocaleList.forLanguageTags("en"), VARIANT_DEFAULT); FontConfig.FontFamily family = readFamily(xml); assertThat(family).isEqualTo(expected); @@ -102,7 +101,7 @@ public final class FontListParserTest { new FontConfig.Font(new File("test.ttf"), null, "test", new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 0, "", null)), - null, LocaleList.forLanguageTags("en"), VARIANT_COMPACT); + LocaleList.forLanguageTags("en"), VARIANT_COMPACT); FontConfig.FontFamily family = readFamily(xml); assertThat(family).isEqualTo(expected); @@ -119,7 +118,7 @@ public final class FontListParserTest { new FontConfig.Font(new File("test.ttf"), null, "test", new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 0, "", null)), - null, LocaleList.forLanguageTags("en"), VARIANT_ELEGANT); + LocaleList.forLanguageTags("en"), VARIANT_ELEGANT); FontConfig.FontFamily family = readFamily(xml); assertThat(family).isEqualTo(expected); @@ -133,19 +132,16 @@ public final class FontListParserTest { + " weight.ttf" + " italic.ttf" + ""; - FontConfig.FontFamily expected = new FontConfig.FontFamily( - Arrays.asList( - new FontConfig.Font(new File("normal.ttf"), null, "test", - new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), - 0, "", null), - new FontConfig.Font(new File("weight.ttf"), null, "test", - new FontStyle(100, FONT_SLANT_UPRIGHT), - 0, "", null), - new FontConfig.Font(new File("italic.ttf"), null, "test", - new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_ITALIC), - 0, "", null)), - "sans-serif", LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT); - FontConfig.FontFamily family = readFamily(xml); + FontConfig.NamedFamilyList expected = new FontConfig.NamedFamilyList( + Collections.singletonList(new FontConfig.FontFamily(Arrays.asList( + new FontConfig.Font(new File("normal.ttf"), null, "test", + new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 0, "", null), + new FontConfig.Font(new File("weight.ttf"), null, "test", + new FontStyle(100, FONT_SLANT_UPRIGHT), 0, "", null), + new FontConfig.Font(new File("italic.ttf"), null, "test", + new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_ITALIC), 0, "", null)), + LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT)), "sans-serif"); + FontConfig.NamedFamilyList family = readNamedFamily(xml); assertThat(family).isEqualTo(expected); } @@ -162,16 +158,17 @@ public final class FontListParserTest { + " " + " " + ""; - FontConfig.FontFamily expected = new FontConfig.FontFamily( - Arrays.asList( + FontConfig.NamedFamilyList expected = new FontConfig.NamedFamilyList( + Collections.singletonList(new FontConfig.FontFamily(Arrays.asList( new FontConfig.Font(new File("test-VF.ttf"), null, "test", new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 0, "'wdth' 100.0,'wght' 200.0", null), new FontConfig.Font(new File("test-VF.ttf"), null, "test", new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 0, "'wdth' 400.0,'wght' 700.0", null)), - "sans-serif", LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT); - FontConfig.FontFamily family = readFamily(xml); + LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT)), + "sans-serif"); + FontConfig.NamedFamilyList family = readNamedFamily(xml); assertThat(family).isEqualTo(expected); } @@ -182,16 +179,17 @@ public final class FontListParserTest { + " test.ttc" + " test.ttc" + ""; - FontConfig.FontFamily expected = new FontConfig.FontFamily( - Arrays.asList( + FontConfig.NamedFamilyList expected = new FontConfig.NamedFamilyList( + Collections.singletonList(new FontConfig.FontFamily(Arrays.asList( new FontConfig.Font(new File("test.ttc"), null, "test", new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 0, "", null), new FontConfig.Font(new File("test.ttc"), null, "test", new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 1, "", null)), - "sans-serif", LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT); - FontConfig.FontFamily family = readFamily(xml); + LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT)), + "sans-serif"); + FontConfig.NamedFamilyList family = readNamedFamily(xml); assertThat(family).isEqualTo(expected); } @@ -202,16 +200,14 @@ public final class FontListParserTest { + " test.ttc" + " test.ttc" + ""; - FontConfig.FontFamily expected = new FontConfig.FontFamily( - Arrays.asList( - new FontConfig.Font(new File("test.ttc"), null, "foo", - new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), - 0, "", null), - new FontConfig.Font(new File("test.ttc"), null, "test", - new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), - 1, "", null)), - "sans-serif", LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT); - FontConfig.FontFamily family = readFamily(xml); + FontConfig.NamedFamilyList expected = new FontConfig.NamedFamilyList( + Collections.singletonList(new FontConfig.FontFamily(Arrays.asList( + new FontConfig.Font(new File("test.ttc"), null, "foo", + new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 0, "", null), + new FontConfig.Font(new File("test.ttc"), null, "test", + new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 1, "", null)), + LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT)), "sans-serif"); + FontConfig.NamedFamilyList family = readNamedFamily(xml); assertThat(family).isEqualTo(expected); } @@ -396,4 +392,14 @@ public final class FontListParserTest { parser.nextTag(); return FontListParser.readFamily(parser, "", null, true); } + + private FontConfig.NamedFamilyList readNamedFamily(String xml) + throws IOException, XmlPullParserException { + ByteArrayInputStream buffer = new ByteArrayInputStream( + xml.getBytes(StandardCharsets.UTF_8)); + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(buffer, "UTF-8"); + parser.nextTag(); + return FontListParser.readNamedFamily(parser, "", null, true); + } } diff --git a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java index 3df2e90241ffa..a8a5059eea200 100644 --- a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java +++ b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java @@ -28,6 +28,8 @@ import android.content.res.AssetManager; import android.graphics.fonts.FontCustomizationParser; import android.graphics.fonts.FontFamily; import android.graphics.fonts.SystemFonts; +import android.graphics.text.PositionedGlyphs; +import android.graphics.text.TextRunShaper; import android.text.FontConfig; import android.util.ArrayMap; @@ -64,6 +66,8 @@ public class TypefaceSystemFallbackTest { "b3em.ttf", // Supports "a","b","c". The width of "b" is 3em, others are 1em. "c3em.ttf", // Supports "a","b","c". The width of "c" is 3em, others are 1em. "all2em.ttf", // Supports "a,","b","c". All of them have the same width of 2em. + "fallback.ttf", // SUpports all small alphabets. + "fallback_capital.ttf", // SUpports all capital alphabets. "no_coverage.ttf", // This font doesn't support any characters. }; private static final String TEST_FONTS_XML; @@ -165,7 +169,10 @@ public class TypefaceSystemFallbackTest { Map updatableFontMap = new HashMap<>(); for (File file : new File(TEST_UPDATABLE_FONT_DIR).listFiles()) { - updatableFontMap.put(file.getName(), file); + final String fileName = file.getName(); + final int periodIndex = fileName.lastIndexOf("."); + final String psName = fileName.substring(0, periodIndex); + updatableFontMap.put(psName, file); } FontConfig fontConfig; @@ -710,6 +717,47 @@ public class TypefaceSystemFallbackTest { assertEquals(GLYPH_1EM_WIDTH, paint.measureText("c"), 0.0f); } + private String getFontName(Paint paint, String text) { + PositionedGlyphs glyphs = TextRunShaper.shapeTextRun( + text, 0, text.length(), 0, text.length(), 0f, 0f, false, paint); + assertEquals(1, glyphs.glyphCount()); + return glyphs.getFont(0).getFile().getName(); + } + + @Test + public void testBuildSystemFallback__Customization_new_named_familyList() { + final String xml = "" + + "" + + " " + + " fallback_capital.ttf" + + " " + + ""; + final String oemXml = "" + + "" + + " " + + " " + + " b3em.ttf" + + " " + + " " + + " fallback.ttf" + + " " + + " " + + ""; + final ArrayMap fontMap = new ArrayMap<>(); + final ArrayMap fallbackMap = new ArrayMap<>(); + + buildSystemFallback(xml, oemXml, fontMap, fallbackMap); + + final Paint paint = new Paint(); + + Typeface testTypeface = fontMap.get("google-sans"); + assertNotNull(testTypeface); + paint.setTypeface(testTypeface); + assertEquals("b3em.ttf", getFontName(paint, "a")); + assertEquals("fallback.ttf", getFontName(paint, "x")); + assertEquals("fallback_capital.ttf", getFontName(paint, "A")); + } + @Test public void testBuildSystemFallback__Customization_new_named_family_override() { final String xml = "" diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java index 4bb16c6b81860..674246acafef2 100644 --- a/graphics/java/android/graphics/FontListParser.java +++ b/graphics/java/android/graphics/FontListParser.java @@ -16,6 +16,8 @@ package android.graphics; +import static android.text.FontConfig.NamedFamilyList; + import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; @@ -36,6 +38,7 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; @@ -46,6 +49,7 @@ import java.util.regex.Pattern; * @hide */ public class FontListParser { + private static final String TAG = "FontListParser"; // XML constants for FontFamily. private static final String ATTR_NAME = "name"; @@ -148,27 +152,60 @@ public class FontListParser { boolean allowNonExistingFile) throws XmlPullParserException, IOException { List families = new ArrayList<>(); + List resultNamedFamilies = new ArrayList<>(); List aliases = new ArrayList<>(customization.getAdditionalAliases()); - Map oemNamedFamilies = + Map oemNamedFamilies = customization.getAdditionalNamedFamilies(); + boolean firstFamily = true; parser.require(XmlPullParser.START_TAG, null, "familyset"); while (keepReading(parser)) { if (parser.getEventType() != XmlPullParser.START_TAG) continue; String tag = parser.getName(); if (tag.equals("family")) { - FontConfig.FontFamily family = readFamily(parser, fontDir, updatableFontMap, - allowNonExistingFile); - if (family == null) { + final String name = parser.getAttributeValue(null, "name"); + if (name == null) { + FontConfig.FontFamily family = readFamily(parser, fontDir, updatableFontMap, + allowNonExistingFile); + if (family == null) { + continue; + } + families.add(family); + + } else { + FontConfig.NamedFamilyList namedFamilyList = readNamedFamily( + parser, fontDir, updatableFontMap, allowNonExistingFile); + if (namedFamilyList == null) { + continue; + } + if (!oemNamedFamilies.containsKey(name)) { + // The OEM customization overrides system named family. Skip if OEM + // customization XML defines the same named family. + resultNamedFamilies.add(namedFamilyList); + } + if (firstFamily) { + // The first font family is used as a fallback family as well. + families.addAll(namedFamilyList.getFamilies()); + } + } + firstFamily = false; + } else if (tag.equals("family-list")) { + FontConfig.NamedFamilyList namedFamilyList = readNamedFamilyList( + parser, fontDir, updatableFontMap, allowNonExistingFile); + if (namedFamilyList == null) { continue; } - String name = family.getName(); - if (name == null || !oemNamedFamilies.containsKey(name)) { + if (!oemNamedFamilies.containsKey(namedFamilyList.getName())) { // The OEM customization overrides system named family. Skip if OEM // customization XML defines the same named family. - families.add(family); + resultNamedFamilies.add(namedFamilyList); } + if (firstFamily) { + // The first font family is used as a fallback family as well. + families.addAll(namedFamilyList.getFamilies()); + } + firstFamily = false; } else if (tag.equals("alias")) { aliases.add(readAlias(parser)); } else { @@ -176,12 +213,12 @@ public class FontListParser { } } - families.addAll(oemNamedFamilies.values()); + resultNamedFamilies.addAll(oemNamedFamilies.values()); // Filters aliases that point to non-existing families. Set namedFamilies = new ArraySet<>(); - for (int i = 0; i < families.size(); ++i) { - String name = families.get(i).getName(); + for (int i = 0; i < resultNamedFamilies.size(); ++i) { + String name = resultNamedFamilies.get(i).getName(); if (name != null) { namedFamilies.add(name); } @@ -194,7 +231,8 @@ public class FontListParser { } } - return new FontConfig(families, filtered, lastModifiedDate, configVersion); + return new FontConfig(families, filtered, resultNamedFamilies, lastModifiedDate, + configVersion); } private static boolean keepReading(XmlPullParser parser) @@ -215,7 +253,6 @@ public class FontListParser { public static @Nullable FontConfig.FontFamily readFamily(XmlPullParser parser, String fontDir, @Nullable Map updatableFontMap, boolean allowNonExistingFile) throws XmlPullParserException, IOException { - final String name = parser.getAttributeValue(null, "name"); final String lang = parser.getAttributeValue("", "lang"); final String variant = parser.getAttributeValue(null, "variant"); final String ignore = parser.getAttributeValue(null, "ignore"); @@ -246,7 +283,68 @@ public class FontListParser { if (skip || fonts.isEmpty()) { return null; } - return new FontConfig.FontFamily(fonts, name, LocaleList.forLanguageTags(lang), intVariant); + return new FontConfig.FontFamily(fonts, LocaleList.forLanguageTags(lang), intVariant); + } + + private static void throwIfAttributeExists(String attrName, XmlPullParser parser) { + if (parser.getAttributeValue(null, attrName) != null) { + throw new IllegalArgumentException(attrName + " cannot be used in FontFamily inside " + + " family or family-list with name attribute."); + } + } + + /** + * Read a font family with name attribute as a single element family-list element. + */ + public static @Nullable FontConfig.NamedFamilyList readNamedFamily( + @NonNull XmlPullParser parser, @NonNull String fontDir, + @Nullable Map updatableFontMap, boolean allowNonExistingFile) + throws XmlPullParserException, IOException { + final String name = parser.getAttributeValue(null, "name"); + throwIfAttributeExists("lang", parser); + throwIfAttributeExists("variant", parser); + throwIfAttributeExists("ignore", parser); + + final FontConfig.FontFamily family = readFamily(parser, fontDir, updatableFontMap, + allowNonExistingFile); + if (family == null) { + return null; + } + return new NamedFamilyList(Collections.singletonList(family), name); + } + + /** + * Read a family-list element + */ + public static @Nullable FontConfig.NamedFamilyList readNamedFamilyList( + @NonNull XmlPullParser parser, @NonNull String fontDir, + @Nullable Map updatableFontMap, boolean allowNonExistingFile) + throws XmlPullParserException, IOException { + final String name = parser.getAttributeValue(null, "name"); + final List familyList = new ArrayList<>(); + while (keepReading(parser)) { + if (parser.getEventType() != XmlPullParser.START_TAG) continue; + final String tag = parser.getName(); + if (tag.equals("family")) { + throwIfAttributeExists("name", parser); + throwIfAttributeExists("lang", parser); + throwIfAttributeExists("variant", parser); + throwIfAttributeExists("ignore", parser); + + final FontConfig.FontFamily family = readFamily(parser, fontDir, updatableFontMap, + allowNonExistingFile); + if (family != null) { + familyList.add(family); + } + } else { + skip(parser); + } + } + + if (familyList.isEmpty()) { + return null; + } + return new FontConfig.NamedFamilyList(familyList, name); } /** Matches leading and trailing XML whitespace. */ diff --git a/graphics/java/android/graphics/fonts/FontCustomizationParser.java b/graphics/java/android/graphics/fonts/FontCustomizationParser.java index df47f73eb04a7..b458dd9021d06 100644 --- a/graphics/java/android/graphics/fonts/FontCustomizationParser.java +++ b/graphics/java/android/graphics/fonts/FontCustomizationParser.java @@ -17,7 +17,7 @@ package android.graphics.fonts; import static android.text.FontConfig.Alias; -import static android.text.FontConfig.FontFamily; +import static android.text.FontConfig.NamedFamilyList; import android.annotation.NonNull; import android.annotation.Nullable; @@ -42,11 +42,14 @@ import java.util.Map; * @hide */ public class FontCustomizationParser { + private static final String TAG = "FontCustomizationParser"; + /** * Represents a customization XML */ public static class Result { - private final Map mAdditionalNamedFamilies; + private final Map mAdditionalNamedFamilies; + private final List mAdditionalAliases; public Result() { @@ -54,13 +57,13 @@ public class FontCustomizationParser { mAdditionalAliases = Collections.emptyList(); } - public Result(Map additionalNamedFamilies, + public Result(Map additionalNamedFamilies, List additionalAliases) { mAdditionalNamedFamilies = additionalNamedFamilies; mAdditionalAliases = additionalAliases; } - public Map getAdditionalNamedFamilies() { + public Map getAdditionalNamedFamilies() { return mAdditionalNamedFamilies; } @@ -85,20 +88,24 @@ public class FontCustomizationParser { return readFamilies(parser, fontDir, updatableFontMap); } - private static Map validateAndTransformToMap(List families) { - HashMap namedFamily = new HashMap<>(); + private static Result validateAndTransformToResult( + List families, List aliases) { + HashMap namedFamily = new HashMap<>(); for (int i = 0; i < families.size(); ++i) { - final FontFamily family = families.get(i); + final NamedFamilyList family = families.get(i); final String name = family.getName(); - if (name == null) { - throw new IllegalArgumentException("new-named-family requires name attribute"); - } - if (namedFamily.put(name, family) != null) { + if (name != null) { + if (namedFamily.put(name, family) != null) { + throw new IllegalArgumentException( + "new-named-family requires unique name attribute"); + } + } else { throw new IllegalArgumentException( - "new-named-family requires unique name attribute"); + "new-named-family requires name attribute or new-default-fallback-family" + + "requires fallackTarget attribute"); } } - return namedFamily; + return new Result(namedFamily, aliases); } private static Result readFamilies( @@ -106,7 +113,7 @@ public class FontCustomizationParser { @NonNull String fontDir, @Nullable Map updatableFontMap ) throws XmlPullParserException, IOException { - List families = new ArrayList<>(); + List families = new ArrayList<>(); List aliases = new ArrayList<>(); parser.require(XmlPullParser.START_TAG, null, "fonts-modification"); while (parser.next() != XmlPullParser.END_TAG) { @@ -114,19 +121,21 @@ public class FontCustomizationParser { String tag = parser.getName(); if (tag.equals("family")) { readFamily(parser, fontDir, families, updatableFontMap); + } else if (tag.equals("family-list")) { + readFamilyList(parser, fontDir, families, updatableFontMap); } else if (tag.equals("alias")) { aliases.add(FontListParser.readAlias(parser)); } else { FontListParser.skip(parser); } } - return new Result(validateAndTransformToMap(families), aliases); + return validateAndTransformToResult(families, aliases); } private static void readFamily( @NonNull XmlPullParser parser, @NonNull String fontDir, - @NonNull List out, + @NonNull List out, @Nullable Map updatableFontMap) throws XmlPullParserException, IOException { final String customizationType = parser.getAttributeValue(null, "customizationType"); @@ -134,7 +143,28 @@ public class FontCustomizationParser { throw new IllegalArgumentException("customizationType must be specified"); } if (customizationType.equals("new-named-family")) { - FontFamily fontFamily = FontListParser.readFamily( + NamedFamilyList fontFamily = FontListParser.readNamedFamily( + parser, fontDir, updatableFontMap, false); + if (fontFamily != null) { + out.add(fontFamily); + } + } else { + throw new IllegalArgumentException("Unknown customizationType=" + customizationType); + } + } + + private static void readFamilyList( + @NonNull XmlPullParser parser, + @NonNull String fontDir, + @NonNull List out, + @Nullable Map updatableFontMap) + throws XmlPullParserException, IOException { + final String customizationType = parser.getAttributeValue(null, "customizationType"); + if (customizationType == null) { + throw new IllegalArgumentException("customizationType must be specified"); + } + if (customizationType.equals("new-named-family")) { + NamedFamilyList fontFamily = FontListParser.readNamedFamilyList( parser, fontDir, updatableFontMap, false); if (fontFamily != null) { out.add(fontFamily); diff --git a/graphics/java/android/graphics/fonts/FontFamily.java b/graphics/java/android/graphics/fonts/FontFamily.java index a771a6e353456..bf79b1bedd8e4 100644 --- a/graphics/java/android/graphics/fonts/FontFamily.java +++ b/graphics/java/android/graphics/fonts/FontFamily.java @@ -114,17 +114,19 @@ public final class FontFamily { * @return a font family */ public @NonNull FontFamily build() { - return build("", FontConfig.FontFamily.VARIANT_DEFAULT, true /* isCustomFallback */); + return build("", FontConfig.FontFamily.VARIANT_DEFAULT, true /* isCustomFallback */, + false /* isDefaultFallback */); } /** @hide */ public @NonNull FontFamily build(@NonNull String langTags, int variant, - boolean isCustomFallback) { + boolean isCustomFallback, boolean isDefaultFallback) { final long builderPtr = nInitBuilder(); for (int i = 0; i < mFonts.size(); ++i) { nAddFont(builderPtr, mFonts.get(i).getNativePtr()); } - final long ptr = nBuild(builderPtr, langTags, variant, isCustomFallback); + final long ptr = nBuild(builderPtr, langTags, variant, isCustomFallback, + isDefaultFallback); final FontFamily family = new FontFamily(ptr); sFamilyRegistory.registerNativeAllocation(family, ptr); return family; @@ -138,7 +140,7 @@ public final class FontFamily { @CriticalNative private static native void nAddFont(long builderPtr, long fontPtr); private static native long nBuild(long builderPtr, String langTags, int variant, - boolean isCustomFallback); + boolean isCustomFallback, boolean isDefaultFallback); @CriticalNative private static native long nGetReleaseNativeFamily(); } diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java index 6278c0e23f271..ec8b2d66da7f2 100644 --- a/graphics/java/android/graphics/fonts/SystemFonts.java +++ b/graphics/java/android/graphics/fonts/SystemFonts.java @@ -22,6 +22,7 @@ import android.graphics.FontListParser; import android.graphics.Typeface; import android.text.FontConfig; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.Log; import com.android.internal.annotations.GuardedBy; @@ -92,9 +93,8 @@ public final class SystemFonts { } private static void pushFamilyToFallback(@NonNull FontConfig.FontFamily xmlFamily, - @NonNull ArrayMap> fallbackMap, + @NonNull ArrayMap fallbackMap, @NonNull Map cache) { - final String languageTags = xmlFamily.getLocaleList().toLanguageTags(); final int variant = xmlFamily.getVariant(); @@ -118,26 +118,29 @@ public final class SystemFonts { } final FontFamily defaultFamily = defaultFonts.isEmpty() ? null : createFontFamily( - xmlFamily.getName(), defaultFonts, languageTags, variant, cache); + defaultFonts, languageTags, variant, false, cache); // Insert family into fallback map. for (int i = 0; i < fallbackMap.size(); i++) { - String name = fallbackMap.keyAt(i); + final String name = fallbackMap.keyAt(i); + final NativeFamilyListSet familyListSet = fallbackMap.valueAt(i); + if (familyListSet.seenXmlFamilies.contains(xmlFamily)) { + continue; + } else { + familyListSet.seenXmlFamilies.add(xmlFamily); + } final ArrayList fallback = specificFallbackFonts.get(name); if (fallback == null) { - String familyName = xmlFamily.getName(); - if (defaultFamily != null - // do not add myself to the fallback chain. - && (familyName == null || !familyName.equals(name))) { - fallbackMap.valueAt(i).add(defaultFamily); + if (defaultFamily != null) { + familyListSet.familyList.add(defaultFamily); } } else { - final FontFamily family = createFontFamily( - xmlFamily.getName(), fallback, languageTags, variant, cache); + final FontFamily family = createFontFamily(fallback, languageTags, variant, false, + cache); if (family != null) { - fallbackMap.valueAt(i).add(family); + familyListSet.familyList.add(family); } else if (defaultFamily != null) { - fallbackMap.valueAt(i).add(defaultFamily); + familyListSet.familyList.add(defaultFamily); } else { // There is no valid for for default fallback. Ignore. } @@ -145,10 +148,11 @@ public final class SystemFonts { } } - private static @Nullable FontFamily createFontFamily(@NonNull String familyName, + private static @Nullable FontFamily createFontFamily( @NonNull List fonts, @NonNull String languageTags, @FontConfig.FontFamily.Variant int variant, + boolean isDefaultFallback, @NonNull Map cache) { if (fonts.size() == 0) { return null; @@ -188,23 +192,30 @@ public final class SystemFonts { b.addFont(font); } } - return b == null ? null : b.build(languageTags, variant, false /* isCustomFallback */); + return b == null ? null : b.build(languageTags, variant, false /* isCustomFallback */, + isDefaultFallback); } - private static void appendNamedFamily(@NonNull FontConfig.FontFamily xmlFamily, + private static void appendNamedFamilyList(@NonNull FontConfig.NamedFamilyList namedFamilyList, @NonNull ArrayMap bufferCache, - @NonNull ArrayMap> fallbackListMap) { - final String familyName = xmlFamily.getName(); - final FontFamily family = createFontFamily( - familyName, xmlFamily.getFontList(), - xmlFamily.getLocaleList().toLanguageTags(), xmlFamily.getVariant(), - bufferCache); - if (family == null) { - return; + @NonNull ArrayMap fallbackListMap) { + final String familyName = namedFamilyList.getName(); + final NativeFamilyListSet familyListSet = new NativeFamilyListSet(); + final List xmlFamilies = namedFamilyList.getFamilies(); + for (int i = 0; i < xmlFamilies.size(); ++i) { + FontConfig.FontFamily xmlFamily = xmlFamilies.get(i); + final FontFamily family = createFontFamily( + xmlFamily.getFontList(), + xmlFamily.getLocaleList().toLanguageTags(), xmlFamily.getVariant(), + true, // named family is always default + bufferCache); + if (family == null) { + return; + } + familyListSet.familyList.add(family); + familyListSet.seenXmlFamilies.add(xmlFamily); } - final ArrayList fallback = new ArrayList<>(); - fallback.add(family); - fallbackListMap.put(familyName, fallback); + fallbackListMap.put(familyName, familyListSet); } /** @@ -245,10 +256,12 @@ public final class SystemFonts { updatableFontMap, lastModifiedDate, configVersion); } catch (IOException e) { Log.e(TAG, "Failed to open/read system font configurations.", e); - return new FontConfig(Collections.emptyList(), Collections.emptyList(), 0, 0); + return new FontConfig(Collections.emptyList(), 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(), 0, 0); + return new FontConfig(Collections.emptyList(), Collections.emptyList(), + Collections.emptyList(), 0, 0); } } @@ -261,37 +274,36 @@ public final class SystemFonts { return buildSystemFallback(fontConfig, new ArrayMap<>()); } + private static final class NativeFamilyListSet { + public List familyList = new ArrayList<>(); + public Set seenXmlFamilies = new ArraySet<>(); + } + /** @hide */ @VisibleForTesting public static Map buildSystemFallback(FontConfig fontConfig, ArrayMap outBufferCache) { - final Map fallbackMap = new ArrayMap<>(); - final List xmlFamilies = fontConfig.getFontFamilies(); - final ArrayMap> fallbackListMap = new ArrayMap<>(); - // First traverse families which have a 'name' attribute to create fallback map. - for (final FontConfig.FontFamily xmlFamily : xmlFamilies) { - final String familyName = xmlFamily.getName(); - if (familyName == null) { - continue; - } - appendNamedFamily(xmlFamily, outBufferCache, fallbackListMap); + final ArrayMap fallbackListMap = new ArrayMap<>(); + + final List namedFamilies = fontConfig.getNamedFamilyLists(); + for (int i = 0; i < namedFamilies.size(); ++i) { + FontConfig.NamedFamilyList namedFamilyList = namedFamilies.get(i); + appendNamedFamilyList(namedFamilyList, outBufferCache, fallbackListMap); } - // Then, add fallback fonts to the each fallback map. + // Then, add fallback fonts to the fallback map. + final List xmlFamilies = fontConfig.getFontFamilies(); for (int i = 0; i < xmlFamilies.size(); i++) { final FontConfig.FontFamily xmlFamily = xmlFamilies.get(i); - // The first family (usually the sans-serif family) is always placed immediately - // after the primary family in the fallback. - if (i == 0 || xmlFamily.getName() == null) { - pushFamilyToFallback(xmlFamily, fallbackListMap, outBufferCache); - } + pushFamilyToFallback(xmlFamily, fallbackListMap, outBufferCache); } // Build the font map and fallback map. + final Map fallbackMap = new ArrayMap<>(); for (int i = 0; i < fallbackListMap.size(); i++) { final String fallbackName = fallbackListMap.keyAt(i); - final List familyList = fallbackListMap.valueAt(i); + final List familyList = fallbackListMap.valueAt(i).familyList; fallbackMap.put(fallbackName, familyList.toArray(new FontFamily[0])); } diff --git a/libs/hwui/jni/FontFamily.cpp b/libs/hwui/jni/FontFamily.cpp index c146adac6b699..28e71d74e5b9a 100644 --- a/libs/hwui/jni/FontFamily.cpp +++ b/libs/hwui/jni/FontFamily.cpp @@ -85,9 +85,9 @@ static jlong FontFamily_create(CRITICAL_JNI_PARAMS_COMMA jlong builderPtr) { if (builder->fonts.empty()) { return 0; } - std::shared_ptr family = - minikin::FontFamily::create(builder->langId, builder->variant, - std::move(builder->fonts), true /* isCustomFallback */); + std::shared_ptr family = minikin::FontFamily::create( + builder->langId, builder->variant, std::move(builder->fonts), + true /* isCustomFallback */, false /* isDefaultFallback */); if (family->getCoverage().length() == 0) { return 0; } diff --git a/libs/hwui/jni/fonts/FontFamily.cpp b/libs/hwui/jni/fonts/FontFamily.cpp index fbfc07e1f89db..897c4d71c0d59 100644 --- a/libs/hwui/jni/fonts/FontFamily.cpp +++ b/libs/hwui/jni/fonts/FontFamily.cpp @@ -57,7 +57,8 @@ static void FontFamily_Builder_addFont(CRITICAL_JNI_PARAMS_COMMA jlong builderPt // Regular JNI static jlong FontFamily_Builder_build(JNIEnv* env, jobject clazz, jlong builderPtr, - jstring langTags, jint variant, jboolean isCustomFallback) { + jstring langTags, jint variant, jboolean isCustomFallback, + jboolean isDefaultFallback) { std::unique_ptr builder(toBuilder(builderPtr)); uint32_t localeId; if (langTags == nullptr) { @@ -66,9 +67,9 @@ static jlong FontFamily_Builder_build(JNIEnv* env, jobject clazz, jlong builderP ScopedUtfChars str(env, langTags); localeId = minikin::registerLocaleList(str.c_str()); } - std::shared_ptr family = - minikin::FontFamily::create(localeId, static_cast(variant), - std::move(builder->fonts), isCustomFallback); + std::shared_ptr family = minikin::FontFamily::create( + localeId, static_cast(variant), std::move(builder->fonts), + isCustomFallback, isDefaultFallback); if (family->getCoverage().length() == 0) { // No coverage means minikin rejected given font for some reasons. jniThrowException(env, "java/lang/IllegalArgumentException", @@ -116,10 +117,10 @@ static jlong FontFamily_getFont(CRITICAL_JNI_PARAMS_COMMA jlong familyPtr, jint /////////////////////////////////////////////////////////////////////////////// static const JNINativeMethod gFontFamilyBuilderMethods[] = { - { "nInitBuilder", "()J", (void*) FontFamily_Builder_initBuilder }, - { "nAddFont", "(JJ)V", (void*) FontFamily_Builder_addFont }, - { "nBuild", "(JLjava/lang/String;IZ)J", (void*) FontFamily_Builder_build }, - { "nGetReleaseNativeFamily", "()J", (void*) FontFamily_Builder_GetReleaseFunc }, + {"nInitBuilder", "()J", (void*)FontFamily_Builder_initBuilder}, + {"nAddFont", "(JJ)V", (void*)FontFamily_Builder_addFont}, + {"nBuild", "(JLjava/lang/String;IZZ)J", (void*)FontFamily_Builder_build}, + {"nGetReleaseNativeFamily", "()J", (void*)FontFamily_Builder_GetReleaseFunc}, }; static const JNINativeMethod gFontFamilyMethods[] = { diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java b/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java index 4cd0d6e42121a..2ac283370886b 100644 --- a/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java +++ b/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java @@ -163,19 +163,25 @@ public class FontManagerShellCommand extends ShellCommand { // Dump named font family first. List families = fontConfig.getFontFamilies(); - w.println("Named Font Families"); + // Dump FontFamilyList + w.println("Named Family List"); w.increaseIndent(); - for (int i = 0; i < families.size(); ++i) { - final FontConfig.FontFamily family = families.get(i); - - // Here, only dump the named family only. - if (family.getName() == null) continue; - - w.println("Named Family (" + family.getName() + ")"); - final List fonts = family.getFontList(); + List namedFamilyLists = fontConfig.getNamedFamilyLists(); + for (int i = 0; i < namedFamilyLists.size(); ++i) { + final FontConfig.NamedFamilyList namedFamilyList = namedFamilyLists.get(i); + w.println("Named Family (" + namedFamilyList.getName() + ")"); w.increaseIndent(); - for (int j = 0; j < fonts.size(); ++j) { - dumpSingleFontConfig(w, fonts.get(j)); + final List namedFamilies = namedFamilyList.getFamilies(); + for (int j = 0; j < namedFamilies.size(); ++j) { + final FontConfig.FontFamily family = namedFamilies.get(j); + + w.println("Family"); + final List fonts = family.getFontList(); + w.increaseIndent(); + for (int k = 0; k < fonts.size(); ++k) { + dumpSingleFontConfig(w, fonts.get(k)); + } + w.decreaseIndent(); } w.decreaseIndent(); } 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 6f93608445421..a680f50014790 100644 --- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java +++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java @@ -19,7 +19,6 @@ package com.android.server.graphics.fonts; import static com.android.server.graphics.fonts.FontManagerService.SystemFontException; import android.annotation.NonNull; -import android.annotation.Nullable; import android.graphics.fonts.FontManager; import android.graphics.fonts.FontUpdateRequest; import android.graphics.fonts.SystemFonts; @@ -44,6 +43,7 @@ import java.nio.file.Files; import java.nio.file.Paths; import java.security.SecureRandom; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -286,7 +286,7 @@ final class UpdatableFontDir { // Before processing font family update, check all family points the available fonts. for (FontUpdateRequest.Family family : familyMap.values()) { - if (resolveFontFiles(family) == null) { + if (resolveFontFilesForNamedFamily(family) == null) { throw new SystemFontException( FontManager.RESULT_ERROR_FONT_NOT_FOUND, "Required fonts are not available"); @@ -498,6 +498,19 @@ final class UpdatableFontDir { } } } + for (int i = 0; i < fontConfig.getNamedFamilyLists().size(); ++i) { + FontConfig.NamedFamilyList namedFamilyList = fontConfig.getNamedFamilyLists().get(i); + for (int j = 0; j < namedFamilyList.getFamilies().size(); ++j) { + FontConfig.FontFamily family = namedFamilyList.getFamilies().get(j); + for (int k = 0; k < family.getFontList().size(); ++k) { + FontConfig.Font font = family.getFontList().get(k); + if (font.getPostScriptName().equals(psName)) { + targetFont = font; + break; + } + } + } + } if (targetFont == null) { return -1; } @@ -553,8 +566,8 @@ final class UpdatableFontDir { } } - @Nullable - private FontConfig.FontFamily resolveFontFiles(FontUpdateRequest.Family fontFamily) { + private FontConfig.NamedFamilyList resolveFontFilesForNamedFamily( + FontUpdateRequest.Family fontFamily) { List fontList = fontFamily.getFonts(); List resolvedFonts = new ArrayList<>(fontList.size()); for (int i = 0; i < fontList.size(); i++) { @@ -567,8 +580,10 @@ final class UpdatableFontDir { resolvedFonts.add(new FontConfig.Font(info.mFile, null, info.getPostScriptName(), font.getFontStyle(), font.getIndex(), font.getFontVariationSettings(), null)); } - return new FontConfig.FontFamily(resolvedFonts, fontFamily.getName(), + FontConfig.FontFamily family = new FontConfig.FontFamily(resolvedFonts, LocaleList.getEmptyLocaleList(), FontConfig.FontFamily.VARIANT_DEFAULT); + return new FontConfig.NamedFamilyList(Collections.singletonList(family), + fontFamily.getName()); } Map getPostScriptMap() { @@ -585,23 +600,24 @@ final class UpdatableFontDir { PersistentSystemFontConfig.Config persistentConfig = readPersistentConfig(); List families = persistentConfig.fontFamilies; - List mergedFamilies = - new ArrayList<>(config.getFontFamilies().size() + families.size()); + List mergedFamilies = + new ArrayList<>(config.getNamedFamilyLists().size() + families.size()); // We should keep the first font family (config.getFontFamilies().get(0)) because it's used // as a fallback font. See SystemFonts.java. - mergedFamilies.addAll(config.getFontFamilies()); + mergedFamilies.addAll(config.getNamedFamilyLists()); // When building Typeface, a latter font family definition will override the previous font // family definition with the same name. An exception is config.getFontFamilies.get(0), // which will be used as a fallback font without being overridden. for (int i = 0; i < families.size(); ++i) { - FontConfig.FontFamily family = resolveFontFiles(families.get(i)); + FontConfig.NamedFamilyList family = resolveFontFilesForNamedFamily(families.get(i)); if (family != null) { mergedFamilies.add(family); } } return new FontConfig( - mergedFamilies, config.getAliases(), mLastModifiedMillis, mConfigVersion); + config.getFontFamilies(), config.getAliases(), mergedFamilies, mLastModifiedMillis, + mConfigVersion); } private PersistentSystemFontConfig.Config readPersistentConfig() { @@ -635,12 +651,12 @@ final class UpdatableFontDir { return mConfigVersion; } - public Map getFontFamilyMap() { + public Map getFontFamilyMap() { PersistentSystemFontConfig.Config curConfig = readPersistentConfig(); - Map familyMap = new HashMap<>(); + Map familyMap = new HashMap<>(); for (int i = 0; i < curConfig.fontFamilies.size(); ++i) { FontUpdateRequest.Family family = curConfig.fontFamilies.get(i); - FontConfig.FontFamily resolvedFamily = resolveFontFiles(family); + FontConfig.NamedFamilyList resolvedFamily = resolveFontFilesForNamedFamily(family); if (resolvedFamily != null) { familyMap.put(family.getName(), resolvedFamily); } 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 68e5ebf027adc..e9a7d85ae7557 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 @@ -210,7 +210,8 @@ public final class UpdatableFontDirTest { assertThat(mUpdatableFontFilesDir.list()).hasLength(2); assertNamedFamilyExists(dir.getSystemFontConfig(), "foobar"); assertThat(dir.getFontFamilyMap()).containsKey("foobar"); - FontConfig.FontFamily foobar = dir.getFontFamilyMap().get("foobar"); + assertThat(dir.getFontFamilyMap().get("foobar").getFamilies().size()).isEqualTo(1); + FontConfig.FontFamily foobar = dir.getFontFamilyMap().get("foobar").getFamilies().get(0); assertThat(foobar.getFontList()).hasSize(2); assertThat(foobar.getFontList().get(0).getFile()) .isEqualTo(dir.getPostScriptMap().get("foo")); @@ -326,10 +327,12 @@ public final class UpdatableFontDirTest { new FontStyle(400, FontStyle.FONT_SLANT_UPRIGHT), 0, null, null); FontConfig.FontFamily family = new FontConfig.FontFamily( - Arrays.asList(fooFont, barFont), "sans-serif", null, + Arrays.asList(fooFont, barFont), null, FontConfig.FontFamily.VARIANT_DEFAULT); - return new FontConfig(Collections.singletonList(family), - Collections.emptyList(), 0, 1); + return new FontConfig(Collections.emptyList(), + Collections.emptyList(), + Collections.singletonList(new FontConfig.NamedFamilyList( + Collections.singletonList(family), "sans-serif")), 0, 1); }; UpdatableFontDir dirForPreparation = new UpdatableFontDir( @@ -411,7 +414,8 @@ public final class UpdatableFontDirTest { assertThat(dir.getPostScriptMap()).containsKey("foo"); assertThat(mParser.getRevision(dir.getPostScriptMap().get("foo"))).isEqualTo(1); assertThat(dir.getFontFamilyMap()).containsKey("foobar"); - FontConfig.FontFamily foobar = dir.getFontFamilyMap().get("foobar"); + assertThat(dir.getFontFamilyMap().get("foobar").getFamilies().size()).isEqualTo(1); + FontConfig.FontFamily foobar = dir.getFontFamilyMap().get("foobar").getFamilies().get(0); assertThat(foobar.getFontList()).hasSize(1); assertThat(foobar.getFontList().get(0).getFile()) .isEqualTo(dir.getPostScriptMap().get("foo")); @@ -485,10 +489,12 @@ public final class UpdatableFontDirTest { file, null, "bar", new FontStyle(400, FontStyle.FONT_SLANT_UPRIGHT), 0, null, null); FontConfig.FontFamily family = new FontConfig.FontFamily( - Collections.singletonList(font), "sans-serif", null, - FontConfig.FontFamily.VARIANT_DEFAULT); - return new FontConfig(Collections.singletonList(family), - Collections.emptyList(), 0, 1); + Collections.singletonList(font), null, FontConfig.FontFamily.VARIANT_DEFAULT); + return new FontConfig( + Collections.emptyList(), + Collections.emptyList(), + Collections.singletonList(new FontConfig.NamedFamilyList( + Collections.singletonList(family), "sans-serif")), 0, 1); }); dir.loadFontFileMap(); @@ -636,9 +642,10 @@ public final class UpdatableFontDirTest { file, null, "test", new FontStyle(400, FontStyle.FONT_SLANT_UPRIGHT), 0, null, null); FontConfig.FontFamily family = new FontConfig.FontFamily( - Collections.singletonList(font), "sans-serif", null, - FontConfig.FontFamily.VARIANT_DEFAULT); - return new FontConfig(Collections.singletonList(family), Collections.emptyList(), 0, 1); + Collections.singletonList(font), null, FontConfig.FontFamily.VARIANT_DEFAULT); + return new FontConfig(Collections.emptyList(), Collections.emptyList(), + Collections.singletonList(new FontConfig.NamedFamilyList( + Collections.singletonList(family), "sans-serif")), 0, 1); }); dir.loadFontFileMap(); @@ -873,13 +880,14 @@ public final class UpdatableFontDirTest { + ""))); assertThat(dir.getPostScriptMap()).containsKey("test"); assertThat(dir.getFontFamilyMap()).containsKey("test"); - FontConfig.FontFamily test = dir.getFontFamilyMap().get("test"); + assertThat(dir.getFontFamilyMap().get("test").getFamilies().size()).isEqualTo(1); + FontConfig.FontFamily test = dir.getFontFamilyMap().get("test").getFamilies().get(0); assertThat(test.getFontList()).hasSize(1); assertThat(test.getFontList().get(0).getFile()) .isEqualTo(dir.getPostScriptMap().get("test")); } - @Test + @Test(expected = IllegalArgumentException.class) public void addFontFamily_noName() throws Exception { UpdatableFontDir dir = new UpdatableFontDir( mUpdatableFontFilesDir, mParser, mFakeFsverityUtil, @@ -891,12 +899,7 @@ public final class UpdatableFontDirTest { newAddFontFamilyRequest("" + " test.ttf" + "")); - try { - dir.update(requests); - fail("Expect NullPointerException"); - } catch (NullPointerException e) { - // Expect - } + dir.update(requests); } @Test @@ -955,17 +958,16 @@ public final class UpdatableFontDirTest { dir.loadFontFileMap(); assertThat(dir.getSystemFontConfig().getFontFamilies()).isNotEmpty(); FontConfig.FontFamily firstFontFamily = dir.getSystemFontConfig().getFontFamilies().get(0); - assertThat(firstFontFamily.getName()).isNotEmpty(); dir.update(Arrays.asList( newFontUpdateRequest("test.ttf,1,test", GOOD_SIGNATURE), - newAddFontFamilyRequest("" + newAddFontFamilyRequest("" + " test.ttf" + ""))); FontConfig fontConfig = dir.getSystemFontConfig(); assertThat(dir.getSystemFontConfig().getFontFamilies()).isNotEmpty(); assertThat(fontConfig.getFontFamilies().get(0)).isEqualTo(firstFontFamily); - FontConfig.FontFamily updated = getLastFamily(fontConfig, firstFontFamily.getName()); + FontConfig.FontFamily updated = getLastFamily(fontConfig, "sans-serif"); assertThat(updated.getFontList()).hasSize(1); assertThat(updated.getFontList().get(0).getFile()) .isEqualTo(dir.getPostScriptMap().get("test")); @@ -1005,7 +1007,9 @@ public final class UpdatableFontDirTest { mParser.setInput(is, "UTF-8"); mParser.nextTag(); - FontConfig.FontFamily fontFamily = FontListParser.readFamily(mParser, "", null, true); + FontConfig.NamedFamilyList namedFamilyList = FontListParser.readNamedFamily( + mParser, "", null, true); + FontConfig.FontFamily fontFamily = namedFamilyList.getFamilies().get(0); List fonts = new ArrayList<>(); for (FontConfig.Font font : fontFamily.getFontList()) { String name = font.getFile().getName(); @@ -1014,7 +1018,8 @@ public final class UpdatableFontDirTest { psName, font.getStyle(), font.getTtcIndex(), font.getFontVariationSettings()); fonts.add(updateFont); } - FontUpdateRequest.Family family = new FontUpdateRequest.Family(fontFamily.getName(), fonts); + FontUpdateRequest.Family family = new FontUpdateRequest.Family( + namedFamilyList.getName(), fonts); return new FontUpdateRequest(family); } @@ -1035,18 +1040,18 @@ public final class UpdatableFontDirTest { // Returns the last family with the given name, which will be used for creating Typeface. private static FontConfig.FontFamily getLastFamily(FontConfig fontConfig, String familyName) { - List fontFamilies = fontConfig.getFontFamilies(); - for (int i = fontFamilies.size() - 1; i >= 0; i--) { - if (familyName.equals(fontFamilies.get(i).getName())) { - return fontFamilies.get(i); + List namedFamilyLists = fontConfig.getNamedFamilyLists(); + for (int i = namedFamilyLists.size() - 1; i >= 0; i--) { + if (familyName.equals(namedFamilyLists.get(i).getName())) { + return namedFamilyLists.get(i).getFamilies().get(0); } } return null; } private static void assertNamedFamilyExists(FontConfig fontConfig, String familyName) { - assertThat(fontConfig.getFontFamilies().stream() - .map(FontConfig.FontFamily::getName) + assertThat(fontConfig.getNamedFamilyLists().stream() + .map(FontConfig.NamedFamilyList::getName) .collect(Collectors.toSet())).contains(familyName); } } diff --git a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java index 650686ff3b85d..fa5b7c15a6fe8 100644 --- a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java +++ b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java @@ -69,6 +69,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.regex.Pattern; +import java.util.stream.Stream; /** * Tests if fonts can be updated by {@link FontManager} API. @@ -246,7 +247,10 @@ public class UpdatableSystemFontTest { @Test public void updateFontFamily() throws Exception { assertThat(updateNotoSerifAs("serif")).isEqualTo(FontManager.RESULT_SUCCESS); - FontConfig.FontFamily family = findFontFamilyOrThrow("serif"); + final FontConfig.NamedFamilyList namedFamilyList = findFontFamilyOrThrow("serif"); + assertThat(namedFamilyList.getFamilies().size()).isEqualTo(1); + final FontConfig.FontFamily family = namedFamilyList.getFamilies().get(0); + assertThat(family.getFontList()).hasSize(2); assertThat(family.getFontList().get(0).getPostScriptName()) .isEqualTo(NOTO_SERIF_REGULAR_POSTSCRIPT_NAME); @@ -265,7 +269,10 @@ public class UpdatableSystemFontTest { public void updateFontFamily_asNewFont() throws Exception { assertThat(updateNotoSerifAs("UpdatableSystemFontTest-serif")) .isEqualTo(FontManager.RESULT_SUCCESS); - FontConfig.FontFamily family = findFontFamilyOrThrow("UpdatableSystemFontTest-serif"); + final FontConfig.NamedFamilyList namedFamilyList = + findFontFamilyOrThrow("UpdatableSystemFontTest-serif"); + assertThat(namedFamilyList.getFamilies().size()).isEqualTo(1); + final FontConfig.FontFamily family = namedFamilyList.getFamilies().get(0); assertThat(family.getFontList()).hasSize(2); assertThat(family.getFontList().get(0).getPostScriptName()) .isEqualTo(NOTO_SERIF_REGULAR_POSTSCRIPT_NAME); @@ -434,9 +441,15 @@ public class UpdatableSystemFontTest { private String getFontPath(String psName) { FontConfig fontConfig = SystemUtil.runWithShellPermissionIdentity(mFontManager::getFontConfig); - return fontConfig.getFontFamilies().stream() + final List namedFamilies = fontConfig.getNamedFamilyLists().stream() + .flatMap(namedFamily -> namedFamily.getFamilies().stream()).toList(); + + return Stream.concat(fontConfig.getFontFamilies().stream(), namedFamilies.stream()) .flatMap(family -> family.getFontList().stream()) - .filter(font -> psName.equals(font.getPostScriptName())) + .filter(font -> { + Log.e("Debug", "PsName = " + font.getPostScriptName()); + return psName.equals(font.getPostScriptName()); + }) // Return the last match, because the latter family takes precedence if two families // have the same name. .reduce((first, second) -> second) @@ -445,10 +458,10 @@ public class UpdatableSystemFontTest { .getAbsolutePath(); } - private FontConfig.FontFamily findFontFamilyOrThrow(String familyName) { + private FontConfig.NamedFamilyList findFontFamilyOrThrow(String familyName) { FontConfig fontConfig = SystemUtil.runWithShellPermissionIdentity(mFontManager::getFontConfig); - return fontConfig.getFontFamilies().stream() + return fontConfig.getNamedFamilyLists().stream() .filter(family -> familyName.equals(family.getName())) // Return the last match, because the latter family takes precedence if two families // have the same name.