diff options
author | 2022-10-24 14:40:41 +0900 | |
---|---|---|
committer | 2022-12-15 23:50:13 +0900 | |
commit | 9e233cd563fbb29234504ffe00cfc7faa76a6079 (patch) | |
tree | ef650d7898cd63becc71e69db5dbbe6ebbb5eab6 | |
parent | 3d43d701243f8bc99a2a6355541eaa318e3d7ce8 (diff) |
Add family-list tag element to fonts_customization.xml
family-list can be used as a named font family definition.
Multiple families can be used as a fallback for the named
family.
Bug: 249787583
Test: atest TypefaceSystemFallbackTest FontListParserTest
Test: atest UpdatableFontDirTest UpdatableSystemFontTest
Test: atest GtsFontHostTestCases FontManagerTest
Change-Id: Ic459a533ac4b5081660c0a4a7519ef7e87a6b628
25 files changed, 1001 insertions, 221 deletions
diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 566ac4535b0d..c5b7a712bfa2 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<android.text.FontConfig.FontFamily> getFontFamilies(); method public long getLastModifiedTimeMillis(); + method @NonNull public java.util.List<android.text.FontConfig.NamedFamilyList> getNamedFamilyLists(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.text.FontConfig> CREATOR; } @@ -16207,7 +16208,7 @@ package android.text { method public int describeContents(); method @NonNull public java.util.List<android.text.FontConfig.Font> 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<android.text.FontConfig.FontFamily> 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<android.text.FontConfig.FontFamily> getFamilies(); + method @NonNull public String getName(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.text.FontConfig.NamedFamilyList> CREATOR; + } + } package android.util { diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 5e02e72f2088..5238c5da244e 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<android.text.FontConfig.FontFamily> getFontFamilies(); method public long getLastModifiedTimeMillis(); + method @NonNull public java.util.List<android.text.FontConfig.NamedFamilyList> getNamedFamilyLists(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.text.FontConfig> CREATOR; } @@ -2735,7 +2736,7 @@ package android.text { method public int describeContents(); method @NonNull public java.util.List<android.text.FontConfig.Font> 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<android.text.FontConfig.FontFamily> 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<android.text.FontConfig.FontFamily> getFamilies(); + method @NonNull public String getName(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.text.FontConfig.NamedFamilyList> 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 32b3bc62a8cd..cb488b08456b 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<FontFamily> mFamilies; private final @NonNull List<Alias> mAliases; + private final @NonNull List<NamedFamilyList> mNamedFamilyLists; private final long mLastModifiedTimeMillis; private final int mConfigVersion; @@ -67,14 +69,25 @@ public final class FontConfig implements Parcelable { * @hide Only system server can create this instance and passed via IPC. */ public FontConfig(@NonNull List<FontFamily> families, @NonNull List<Alias> aliases, + @NonNull List<NamedFamilyList> 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<FontFamily> families, @NonNull List<Alias> 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. * * @return a list of font families. @@ -94,6 +107,10 @@ public final class FontConfig implements Parcelable { return mAliases; } + public @NonNull List<NamedFamilyList> 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<FontConfig> CREATOR = new Creator<FontConfig>() { @Override public FontConfig createFromParcel(Parcel source) { - List<FontFamily> families = source.readParcelableList(new ArrayList<>(), - FontFamily.class.getClassLoader(), android.text.FontConfig.FontFamily.class); - List<Alias> aliases = source.readParcelableList(new ArrayList<>(), - Alias.class.getClassLoader(), android.text.FontConfig.Alias.class); + final List<FontFamily> families = new ArrayList<>(); + source.readTypedList(families, FontFamily.CREATOR); + final List<Alias> aliases = new ArrayList<>(); + source.readTypedList(aliases, Alias.CREATOR); + final List<NamedFamilyList> 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<Font> 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<Font> fonts, @Nullable String name, - @NonNull LocaleList localeList, @Variant int variant) { + public FontFamily(@NonNull List<Font> 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<Font> fonts = source.readParcelableList( - new ArrayList<>(), Font.class.getClassLoader(), android.text.FontConfig.Font.class); - String name = source.readString8(); + List<Font> 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<FontFamily> mFamilies; + private final String mName; + + /** @hide */ + public NamedFamilyList(@NonNull List<FontFamily> 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<FontFamily> 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<NamedFamilyList> CREATOR = new Creator<>() { + + @Override + public NamedFamilyList createFromParcel(Parcel source) { + final List<FontFamily> 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 Binary files differindex a601ce2ed932..4c1ad1be814d 100644 --- a/core/tests/coretests/assets/fonts/a3em.ttf +++ b/core/tests/coretests/assets/fonts/a3em.ttf diff --git a/core/tests/coretests/assets/fonts/a3em.ttx b/core/tests/coretests/assets/fonts/a3em.ttx index d3b9e1603764..d7da0da2b0ab 100644 --- a/core/tests/coretests/assets/fonts/a3em.ttx +++ b/core/tests/coretests/assets/fonts/a3em.ttx @@ -156,7 +156,7 @@ Sample Font </namerecord> <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409"> - SampleFont-Regular + a3em </namerecord> <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409"> 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 Binary files differindex 63948a22c113..b18d4bb4d8f7 100644 --- a/core/tests/coretests/assets/fonts/b3em.ttf +++ b/core/tests/coretests/assets/fonts/b3em.ttf diff --git a/core/tests/coretests/assets/fonts/b3em.ttx b/core/tests/coretests/assets/fonts/b3em.ttx index b5a77ef09bd3..217393a6b852 100644 --- a/core/tests/coretests/assets/fonts/b3em.ttx +++ b/core/tests/coretests/assets/fonts/b3em.ttx @@ -156,7 +156,7 @@ Sample Font </namerecord> <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409"> - SampleFont-Regular + b3em </namerecord> <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409"> 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 Binary files differindex badc3e29868f..83a5db28b991 100644 --- a/core/tests/coretests/assets/fonts/c3em.ttf +++ b/core/tests/coretests/assets/fonts/c3em.ttf diff --git a/core/tests/coretests/assets/fonts/c3em.ttx b/core/tests/coretests/assets/fonts/c3em.ttx index f5ed8e556332..7535bea979db 100644 --- a/core/tests/coretests/assets/fonts/c3em.ttx +++ b/core/tests/coretests/assets/fonts/c3em.ttx @@ -156,7 +156,7 @@ Sample Font </namerecord> <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409"> - SampleFont-Regular + c3em </namerecord> <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409"> 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 Binary files differnew file mode 100644 index 000000000000..1ba8639c07ab --- /dev/null +++ b/core/tests/coretests/assets/fonts/fallback.ttf diff --git a/core/tests/coretests/assets/fonts/fallback.ttx b/core/tests/coretests/assets/fonts/fallback.ttx new file mode 100644 index 000000000000..8e4b3e6712d3 --- /dev/null +++ b/core/tests/coretests/assets/fonts/fallback.ttx @@ -0,0 +1,205 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright (C) 2017 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + 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. +--> +<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0"> + + <GlyphOrder> + <GlyphID id="0" name=".notdef"/> + <GlyphID id="1" name="1em"/> + </GlyphOrder> + + <head> + <tableVersion value="1.0"/> + <fontRevision value="1.0"/> + <checkSumAdjustment value="0x640cdb2f"/> + <magicNumber value="0x5f0f3cf5"/> + <flags value="00000000 00000011"/> + <unitsPerEm value="1000"/> + <created value="Fri Mar 17 07:26:00 2017"/> + <macStyle value="00000000 00000000"/> + <lowestRecPPEM value="7"/> + <fontDirectionHint value="2"/> + <glyphDataFormat value="0"/> + </head> + + <hhea> + <tableVersion value="1.0"/> + <ascent value="1000"/> + <descent value="-200"/> + <lineGap value="0"/> + <caretSlopeRise value="1"/> + <caretSlopeRun value="0"/> + <caretOffset value="0"/> + <reserved0 value="0"/> + <reserved1 value="0"/> + <reserved2 value="0"/> + <reserved3 value="0"/> + <metricDataFormat value="0"/> + </hhea> + + <maxp> + <tableVersion value="0x10000"/> + <maxZones value="0"/> + <maxTwilightPoints value="0"/> + <maxStorage value="0"/> + <maxFunctionDefs value="0"/> + <maxInstructionDefs value="0"/> + <maxStackElements value="0"/> + <maxSizeOfInstructions value="0"/> + <maxComponentElements value="0"/> + </maxp> + + <OS_2> + <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex' + will be recalculated by the compiler --> + <version value="3"/> + <xAvgCharWidth value="594"/> + <usWeightClass value="400"/> + <usWidthClass value="5"/> + <fsType value="00000000 00001000"/> + <ySubscriptXSize value="650"/> + <ySubscriptYSize value="600"/> + <ySubscriptXOffset value="0"/> + <ySubscriptYOffset value="75"/> + <ySuperscriptXSize value="650"/> + <ySuperscriptYSize value="600"/> + <ySuperscriptXOffset value="0"/> + <ySuperscriptYOffset value="350"/> + <yStrikeoutSize value="50"/> + <yStrikeoutPosition value="300"/> + <sFamilyClass value="0"/> + <panose> + <bFamilyType value="0"/> + <bSerifStyle value="0"/> + <bWeight value="5"/> + <bProportion value="0"/> + <bContrast value="0"/> + <bStrokeVariation value="0"/> + <bArmStyle value="0"/> + <bLetterForm value="0"/> + <bMidline value="0"/> + <bXHeight value="0"/> + </panose> + <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/> + <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/> + <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/> + <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/> + <achVendID value="UKWN"/> + <fsSelection value="00000000 01000000"/> + <usFirstCharIndex value="32"/> + <usLastCharIndex value="122"/> + <sTypoAscender value="800"/> + <sTypoDescender value="-200"/> + <sTypoLineGap value="200"/> + <usWinAscent value="1000"/> + <usWinDescent value="200"/> + <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/> + <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/> + <sxHeight value="500"/> + <sCapHeight value="700"/> + <usDefaultChar value="0"/> + <usBreakChar value="32"/> + <usMaxContext value="0"/> + </OS_2> + + <hmtx> + <mtx name=".notdef" width="500" lsb="93"/> + <mtx name="1em" width="1000" lsb="93"/> + </hmtx> + + <cmap> + <tableVersion version="0"/> + <cmap_format_4 platformID="3" platEncID="10" language="0"> + <map code="0x0061" name="1em" /> <!-- "a" --> + <map code="0x0062" name="1em" /> <!-- "b" --> + <map code="0x0063" name="1em" /> <!-- "c" --> + <map code="0x0064" name="1em" /> <!-- "d" --> + <map code="0x0065" name="1em" /> <!-- "e" --> + <map code="0x0066" name="1em" /> <!-- "f" --> + <map code="0x0067" name="1em" /> <!-- "g" --> + <map code="0x0068" name="1em" /> <!-- "h" --> + <map code="0x0069" name="1em" /> <!-- "i" --> + <map code="0x006a" name="1em" /> <!-- "j" --> + <map code="0x006b" name="1em" /> <!-- "k" --> + <map code="0x006c" name="1em" /> <!-- "l" --> + <map code="0x006d" name="1em" /> <!-- "m" --> + <map code="0x006e" name="1em" /> <!-- "n" --> + <map code="0x006f" name="1em" /> <!-- "o" --> + <map code="0x0070" name="1em" /> <!-- "p" --> + <map code="0x0071" name="1em" /> <!-- "q" --> + <map code="0x0072" name="1em" /> <!-- "r" --> + <map code="0x0073" name="1em" /> <!-- "s" --> + <map code="0x0074" name="1em" /> <!-- "t" --> + <map code="0x0075" name="1em" /> <!-- "u" --> + <map code="0x0076" name="1em" /> <!-- "v" --> + <map code="0x0077" name="1em" /> <!-- "w" --> + <map code="0x0078" name="1em" /> <!-- "x" --> + <map code="0x0079" name="1em" /> <!-- "y" --> + <map code="0x007a" name="1em" /> <!-- "z" --> + </cmap_format_4> + </cmap> + + <loca> + <!-- The 'loca' table will be calculated by the compiler --> + </loca> + + <glyf> + <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" /> + <TTGlyph name="1em" xMin="0" yMin="0" xMax="0" yMax="0" /> + </glyf> + + <name> + <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409"> + Copyright (C) 2017 The Android Open Source Project + </namerecord> + <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409"> + Sample Font + </namerecord> + <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409"> + Regular + </namerecord> + <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409"> + Sample Font + </namerecord> + <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409"> + fallback + </namerecord> + <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409"> + 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. + </namerecord> + <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409"> + http://www.apache.org/licenses/LICENSE-2.0 + </namerecord> + </name> + + <post> + <formatType value="3.0"/> + <italicAngle value="0.0"/> + <underlinePosition value="-75"/> + <underlineThickness value="50"/> + <isFixedPitch value="0"/> + <minMemType42 value="0"/> + <maxMemType42 value="0"/> + <minMemType1 value="0"/> + <maxMemType1 value="0"/> + </post> + +</ttFont> diff --git a/core/tests/coretests/assets/fonts/fallback_capital.ttf b/core/tests/coretests/assets/fonts/fallback_capital.ttf Binary files differnew file mode 100644 index 000000000000..073761f08341 --- /dev/null +++ b/core/tests/coretests/assets/fonts/fallback_capital.ttf 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 000000000000..710c95e091a7 --- /dev/null +++ b/core/tests/coretests/assets/fonts/fallback_capital.ttx @@ -0,0 +1,205 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright (C) 2017 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + 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. +--> +<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0"> + + <GlyphOrder> + <GlyphID id="0" name=".notdef"/> + <GlyphID id="1" name="1em"/> + </GlyphOrder> + + <head> + <tableVersion value="1.0"/> + <fontRevision value="1.0"/> + <checkSumAdjustment value="0x640cdb2f"/> + <magicNumber value="0x5f0f3cf5"/> + <flags value="00000000 00000011"/> + <unitsPerEm value="1000"/> + <created value="Fri Mar 17 07:26:00 2017"/> + <macStyle value="00000000 00000000"/> + <lowestRecPPEM value="7"/> + <fontDirectionHint value="2"/> + <glyphDataFormat value="0"/> + </head> + + <hhea> + <tableVersion value="1.0"/> + <ascent value="1000"/> + <descent value="-200"/> + <lineGap value="0"/> + <caretSlopeRise value="1"/> + <caretSlopeRun value="0"/> + <caretOffset value="0"/> + <reserved0 value="0"/> + <reserved1 value="0"/> + <reserved2 value="0"/> + <reserved3 value="0"/> + <metricDataFormat value="0"/> + </hhea> + + <maxp> + <tableVersion value="0x10000"/> + <maxZones value="0"/> + <maxTwilightPoints value="0"/> + <maxStorage value="0"/> + <maxFunctionDefs value="0"/> + <maxInstructionDefs value="0"/> + <maxStackElements value="0"/> + <maxSizeOfInstructions value="0"/> + <maxComponentElements value="0"/> + </maxp> + + <OS_2> + <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex' + will be recalculated by the compiler --> + <version value="3"/> + <xAvgCharWidth value="594"/> + <usWeightClass value="400"/> + <usWidthClass value="5"/> + <fsType value="00000000 00001000"/> + <ySubscriptXSize value="650"/> + <ySubscriptYSize value="600"/> + <ySubscriptXOffset value="0"/> + <ySubscriptYOffset value="75"/> + <ySuperscriptXSize value="650"/> + <ySuperscriptYSize value="600"/> + <ySuperscriptXOffset value="0"/> + <ySuperscriptYOffset value="350"/> + <yStrikeoutSize value="50"/> + <yStrikeoutPosition value="300"/> + <sFamilyClass value="0"/> + <panose> + <bFamilyType value="0"/> + <bSerifStyle value="0"/> + <bWeight value="5"/> + <bProportion value="0"/> + <bContrast value="0"/> + <bStrokeVariation value="0"/> + <bArmStyle value="0"/> + <bLetterForm value="0"/> + <bMidline value="0"/> + <bXHeight value="0"/> + </panose> + <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/> + <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/> + <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/> + <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/> + <achVendID value="UKWN"/> + <fsSelection value="00000000 01000000"/> + <usFirstCharIndex value="32"/> + <usLastCharIndex value="122"/> + <sTypoAscender value="800"/> + <sTypoDescender value="-200"/> + <sTypoLineGap value="200"/> + <usWinAscent value="1000"/> + <usWinDescent value="200"/> + <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/> + <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/> + <sxHeight value="500"/> + <sCapHeight value="700"/> + <usDefaultChar value="0"/> + <usBreakChar value="32"/> + <usMaxContext value="0"/> + </OS_2> + + <hmtx> + <mtx name=".notdef" width="500" lsb="93"/> + <mtx name="1em" width="1000" lsb="93"/> + </hmtx> + + <cmap> + <tableVersion version="0"/> + <cmap_format_4 platformID="3" platEncID="10" language="0"> + <map code="0x0041" name="1em" /> <!-- "A" --> + <map code="0x0042" name="1em" /> <!-- "B" --> + <map code="0x0043" name="1em" /> <!-- "C" --> + <map code="0x0044" name="1em" /> <!-- "D" --> + <map code="0x0045" name="1em" /> <!-- "E" --> + <map code="0x0046" name="1em" /> <!-- "F" --> + <map code="0x0047" name="1em" /> <!-- "G" --> + <map code="0x0048" name="1em" /> <!-- "H" --> + <map code="0x0049" name="1em" /> <!-- "I" --> + <map code="0x004a" name="1em" /> <!-- "J" --> + <map code="0x004b" name="1em" /> <!-- "K" --> + <map code="0x004c" name="1em" /> <!-- "L" --> + <map code="0x004d" name="1em" /> <!-- "M" --> + <map code="0x004e" name="1em" /> <!-- "N" --> + <map code="0x004f" name="1em" /> <!-- "O" --> + <map code="0x0050" name="1em" /> <!-- "P" --> + <map code="0x0051" name="1em" /> <!-- "Q" --> + <map code="0x0052" name="1em" /> <!-- "R" --> + <map code="0x0053" name="1em" /> <!-- "S" --> + <map code="0x0054" name="1em" /> <!-- "T" --> + <map code="0x0055" name="1em" /> <!-- "U" --> + <map code="0x0056" name="1em" /> <!-- "V" --> + <map code="0x0057" name="1em" /> <!-- "W" --> + <map code="0x0058" name="1em" /> <!-- "X" --> + <map code="0x0059" name="1em" /> <!-- "Y" --> + <map code="0x005a" name="1em" /> <!-- "Z" --> + </cmap_format_4> + </cmap> + + <loca> + <!-- The 'loca' table will be calculated by the compiler --> + </loca> + + <glyf> + <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" /> + <TTGlyph name="1em" xMin="0" yMin="0" xMax="0" yMax="0" /> + </glyf> + + <name> + <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409"> + Copyright (C) 2017 The Android Open Source Project + </namerecord> + <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409"> + Sample Font + </namerecord> + <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409"> + Regular + </namerecord> + <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409"> + Sample Font + </namerecord> + <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409"> + fallback_capital + </namerecord> + <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409"> + 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. + </namerecord> + <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409"> + http://www.apache.org/licenses/LICENSE-2.0 + </namerecord> + </name> + + <post> + <formatType value="3.0"/> + <italicAngle value="0.0"/> + <underlinePosition value="-75"/> + <underlineThickness value="50"/> + <isFixedPitch value="0"/> + <minMemType42 value="0"/> + <maxMemType42 value="0"/> + <minMemType1 value="0"/> + <maxMemType1 value="0"/> + </post> + +</ttFont> diff --git a/core/tests/coretests/src/android/graphics/FontListParserTest.java b/core/tests/coretests/src/android/graphics/FontListParserTest.java index 479e52aab029..d46f7625b596 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 { + "<family name='sans-serif'>" + " <font>test.ttf</font>" + "</family>"; - 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 { + " <font weight='100'>weight.ttf</font>" + " <font style='italic'>italic.ttf</font>" + "</family>"; - 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 { + " <axis tag='wght' stylevalue='700' />" + " </font>" + "</family>"; - 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 { + " <font index='0'>test.ttc</font>" + " <font index='1'>test.ttc</font>" + "</family>"; - 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 { + " <font index='0' postScriptName='foo'>test.ttc</font>" + " <font index='1'>test.ttc</font>" + "</family>"; - 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 3df2e90241ff..a8a5059eea20 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<String, File> 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 = "<?xml version='1.0' encoding='UTF-8'?>" + + "<familyset>" + + " <family name='sans-serif'>" + + " <font weight='400' style='normal'>fallback_capital.ttf</font>" + + " </family>" + + "</familyset>"; + final String oemXml = "<?xml version='1.0' encoding='UTF-8'?>" + + "<fonts-modification version='1'>" + + " <family-list customizationType='new-named-family' name='google-sans'>" + + " <family>" + + " <font weight='400' style='normal'>b3em.ttf</font>" + + " </family>" + + " <family>" + + " <font weight='400' style='normal'>fallback.ttf</font>" + + " </family>" + + " </family-list>" + + "</fonts-modification>"; + final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); + final ArrayMap<String, FontFamily[]> 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 = "<?xml version='1.0' encoding='UTF-8'?>" diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java index 4bb16c6b8186..674246acafef 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<FontConfig.FontFamily> families = new ArrayList<>(); + List<FontConfig.NamedFamilyList> resultNamedFamilies = new ArrayList<>(); List<FontConfig.Alias> aliases = new ArrayList<>(customization.getAdditionalAliases()); - Map<String, FontConfig.FontFamily> oemNamedFamilies = + Map<String, NamedFamilyList> 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<String> 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<String, File> 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<String, File> 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<String, File> updatableFontMap, boolean allowNonExistingFile) + throws XmlPullParserException, IOException { + final String name = parser.getAttributeValue(null, "name"); + final List<FontConfig.FontFamily> 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 df47f73eb04a..b458dd9021d0 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<String, FontFamily> mAdditionalNamedFamilies; + private final Map<String, NamedFamilyList> mAdditionalNamedFamilies; + private final List<Alias> mAdditionalAliases; public Result() { @@ -54,13 +57,13 @@ public class FontCustomizationParser { mAdditionalAliases = Collections.emptyList(); } - public Result(Map<String, FontFamily> additionalNamedFamilies, + public Result(Map<String, NamedFamilyList> additionalNamedFamilies, List<Alias> additionalAliases) { mAdditionalNamedFamilies = additionalNamedFamilies; mAdditionalAliases = additionalAliases; } - public Map<String, FontFamily> getAdditionalNamedFamilies() { + public Map<String, NamedFamilyList> getAdditionalNamedFamilies() { return mAdditionalNamedFamilies; } @@ -85,20 +88,24 @@ public class FontCustomizationParser { return readFamilies(parser, fontDir, updatableFontMap); } - private static Map<String, FontFamily> validateAndTransformToMap(List<FontFamily> families) { - HashMap<String, FontFamily> namedFamily = new HashMap<>(); + private static Result validateAndTransformToResult( + List<NamedFamilyList> families, List<Alias> aliases) { + HashMap<String, NamedFamilyList> 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<String, File> updatableFontMap ) throws XmlPullParserException, IOException { - List<FontFamily> families = new ArrayList<>(); + List<NamedFamilyList> families = new ArrayList<>(); List<Alias> aliases = new ArrayList<>(); parser.require(XmlPullParser.START_TAG, null, "fonts-modification"); while (parser.next() != XmlPullParser.END_TAG) { @@ -114,19 +121,42 @@ 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<FontFamily> out, + @NonNull List<NamedFamilyList> out, + @Nullable Map<String, File> 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.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<NamedFamilyList> out, @Nullable Map<String, File> updatableFontMap) throws XmlPullParserException, IOException { final String customizationType = parser.getAttributeValue(null, "customizationType"); @@ -134,7 +164,7 @@ public class FontCustomizationParser { throw new IllegalArgumentException("customizationType must be specified"); } if (customizationType.equals("new-named-family")) { - FontFamily fontFamily = FontListParser.readFamily( + 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 a771a6e35345..bf79b1bedd8e 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 6278c0e23f27..ec8b2d66da7f 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<String, ArrayList<FontFamily>> fallbackMap, + @NonNull ArrayMap<String, NativeFamilyListSet> fallbackMap, @NonNull Map<String, ByteBuffer> 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<FontConfig.Font> 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<FontConfig.Font> fonts, @NonNull String languageTags, @FontConfig.FontFamily.Variant int variant, + boolean isDefaultFallback, @NonNull Map<String, ByteBuffer> 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<String, ByteBuffer> bufferCache, - @NonNull ArrayMap<String, ArrayList<FontFamily>> 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<String, NativeFamilyListSet> fallbackListMap) { + final String familyName = namedFamilyList.getName(); + final NativeFamilyListSet familyListSet = new NativeFamilyListSet(); + final List<FontConfig.FontFamily> 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<FontFamily> 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<FontFamily> familyList = new ArrayList<>(); + public Set<FontConfig.FontFamily> seenXmlFamilies = new ArraySet<>(); + } + /** @hide */ @VisibleForTesting public static Map<String, FontFamily[]> buildSystemFallback(FontConfig fontConfig, ArrayMap<String, ByteBuffer> outBufferCache) { - final Map<String, FontFamily[]> fallbackMap = new ArrayMap<>(); - final List<FontConfig.FontFamily> xmlFamilies = fontConfig.getFontFamilies(); - final ArrayMap<String, ArrayList<FontFamily>> 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<String, NativeFamilyListSet> fallbackListMap = new ArrayMap<>(); + + final List<FontConfig.NamedFamilyList> 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<FontConfig.FontFamily> 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<String, FontFamily[]> fallbackMap = new ArrayMap<>(); for (int i = 0; i < fallbackListMap.size(); i++) { final String fallbackName = fallbackListMap.keyAt(i); - final List<FontFamily> familyList = fallbackListMap.valueAt(i); + final List<FontFamily> 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 c146adac6b69..28e71d74e5b9 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<minikin::FontFamily> family = - minikin::FontFamily::create(builder->langId, builder->variant, - std::move(builder->fonts), true /* isCustomFallback */); + std::shared_ptr<minikin::FontFamily> 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 fbfc07e1f89d..897c4d71c0d5 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<NativeFamilyBuilder> 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<minikin::FontFamily> family = - minikin::FontFamily::create(localeId, static_cast<minikin::FamilyVariant>(variant), - std::move(builder->fonts), isCustomFallback); + std::shared_ptr<minikin::FontFamily> family = minikin::FontFamily::create( + localeId, static_cast<minikin::FamilyVariant>(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 4cd0d6e42121..2ac283370886 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<FontConfig.FontFamily> 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<FontConfig.Font> fonts = family.getFontList(); + List<FontConfig.NamedFamilyList> 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<FontConfig.FontFamily> namedFamilies = namedFamilyList.getFamilies(); + for (int j = 0; j < namedFamilies.size(); ++j) { + final FontConfig.FontFamily family = namedFamilies.get(j); + + w.println("Family"); + final List<FontConfig.Font> 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 6f9360844542..a680f5001479 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<FontUpdateRequest.Font> fontList = fontFamily.getFonts(); List<FontConfig.Font> 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<String, File> getPostScriptMap() { @@ -585,23 +600,24 @@ final class UpdatableFontDir { PersistentSystemFontConfig.Config persistentConfig = readPersistentConfig(); List<FontUpdateRequest.Family> families = persistentConfig.fontFamilies; - List<FontConfig.FontFamily> mergedFamilies = - new ArrayList<>(config.getFontFamilies().size() + families.size()); + List<FontConfig.NamedFamilyList> 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<String, FontConfig.FontFamily> getFontFamilyMap() { + public Map<String, FontConfig.NamedFamilyList> getFontFamilyMap() { PersistentSystemFontConfig.Config curConfig = readPersistentConfig(); - Map<String, FontConfig.FontFamily> familyMap = new HashMap<>(); + Map<String, FontConfig.NamedFamilyList> 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 68e5ebf027ad..e9a7d85ae755 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 { + "</family>"))); 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("<family lang='en'>" + " <font>test.ttf</font>" + "</family>")); - 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("<family name='" + firstFontFamily.getName() + "'>" + newAddFontFamilyRequest("<family name='sans-serif'>" + " <font>test.ttf</font>" + "</family>"))); 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<FontUpdateRequest.Font> 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<FontConfig.FontFamily> fontFamilies = fontConfig.getFontFamilies(); - for (int i = fontFamilies.size() - 1; i >= 0; i--) { - if (familyName.equals(fontFamilies.get(i).getName())) { - return fontFamilies.get(i); + List<FontConfig.NamedFamilyList> 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 650686ff3b85..fa5b7c15a6fe 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<FontConfig.FontFamily> 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. |