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
This commit is contained in:
Seigo Nonaka
2022-10-24 14:40:41 +09:00
parent 3d43d70124
commit 9e233cd563
25 changed files with 1001 additions and 221 deletions

View File

@@ -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 {

View File

@@ -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);

View File

@@ -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,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<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.
*
@@ -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 + '\''
+ '}';
}
}
}

View File

@@ -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");

View File

@@ -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");

View File

@@ -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");

Binary file not shown.

View File

@@ -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>

Binary file not shown.

View File

@@ -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>

View File

@@ -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);
}
}

View File

@@ -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'?>"

View File

@@ -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. */

View File

@@ -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,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<FontFamily> out,
@NonNull List<NamedFamilyList> out,
@Nullable Map<String, File> 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<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.readNamedFamilyList(
parser, fontDir, updatableFontMap, false);
if (fontFamily != null) {
out.add(fontFamily);

View File

@@ -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();
}

View File

@@ -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]));
}

View File

@@ -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;
}

View File

@@ -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[] = {

View File

@@ -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();
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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.