1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.graphics.fonts; 18 19 import static android.text.FontConfig.Alias; 20 import static android.text.FontConfig.NamedFamilyList; 21 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.graphics.FontListParser; 25 import android.text.FontConfig; 26 import android.util.Xml; 27 28 import org.xmlpull.v1.XmlPullParser; 29 import org.xmlpull.v1.XmlPullParserException; 30 31 import java.io.File; 32 import java.io.IOException; 33 import java.io.InputStream; 34 import java.util.ArrayList; 35 import java.util.Collections; 36 import java.util.HashMap; 37 import java.util.List; 38 import java.util.Locale; 39 import java.util.Map; 40 41 /** 42 * Parser for font customization 43 * 44 * @hide 45 */ 46 public class FontCustomizationParser { 47 private static final String TAG = "FontCustomizationParser"; 48 49 /** 50 * Represents a customization XML 51 */ 52 public static class Result { 53 private final Map<String, NamedFamilyList> mAdditionalNamedFamilies; 54 55 private final List<Alias> mAdditionalAliases; 56 57 private final List<FontConfig.Customization.LocaleFallback> mLocaleFamilyCustomizations; 58 Result()59 public Result() { 60 mAdditionalNamedFamilies = Collections.emptyMap(); 61 mLocaleFamilyCustomizations = Collections.emptyList(); 62 mAdditionalAliases = Collections.emptyList(); 63 } 64 Result(Map<String, NamedFamilyList> additionalNamedFamilies, List<FontConfig.Customization.LocaleFallback> localeFamilyCustomizations, List<Alias> additionalAliases)65 public Result(Map<String, NamedFamilyList> additionalNamedFamilies, 66 List<FontConfig.Customization.LocaleFallback> localeFamilyCustomizations, 67 List<Alias> additionalAliases) { 68 mAdditionalNamedFamilies = additionalNamedFamilies; 69 mLocaleFamilyCustomizations = localeFamilyCustomizations; 70 mAdditionalAliases = additionalAliases; 71 } 72 getAdditionalNamedFamilies()73 public Map<String, NamedFamilyList> getAdditionalNamedFamilies() { 74 return mAdditionalNamedFamilies; 75 } 76 getAdditionalAliases()77 public List<Alias> getAdditionalAliases() { 78 return mAdditionalAliases; 79 } 80 getLocaleFamilyCustomizations()81 public List<FontConfig.Customization.LocaleFallback> getLocaleFamilyCustomizations() { 82 return mLocaleFamilyCustomizations; 83 } 84 } 85 86 /** 87 * Parses the customization XML 88 * 89 * Caller must close the input stream 90 */ parse( @onNull InputStream in, @NonNull String fontDir, @Nullable Map<String, File> updatableFontMap )91 public static Result parse( 92 @NonNull InputStream in, 93 @NonNull String fontDir, 94 @Nullable Map<String, File> updatableFontMap 95 ) throws XmlPullParserException, IOException { 96 XmlPullParser parser = Xml.newPullParser(); 97 parser.setInput(in, null); 98 parser.nextTag(); 99 return readFamilies(parser, fontDir, updatableFontMap); 100 } 101 validateAndTransformToResult( List<NamedFamilyList> families, List<FontConfig.Customization.LocaleFallback> outLocaleFamilies, List<Alias> aliases)102 private static Result validateAndTransformToResult( 103 List<NamedFamilyList> families, 104 List<FontConfig.Customization.LocaleFallback> outLocaleFamilies, 105 List<Alias> aliases) { 106 HashMap<String, NamedFamilyList> namedFamily = new HashMap<>(); 107 for (int i = 0; i < families.size(); ++i) { 108 final NamedFamilyList family = families.get(i); 109 final String name = family.getName(); 110 if (name != null) { 111 if (namedFamily.put(name, family) != null) { 112 throw new IllegalArgumentException( 113 "new-named-family requires unique name attribute"); 114 } 115 } else { 116 throw new IllegalArgumentException( 117 "new-named-family requires name attribute or new-default-fallback-family" 118 + "requires fallackTarget attribute"); 119 } 120 } 121 return new Result(namedFamily, outLocaleFamilies, aliases); 122 } 123 readFamilies( @onNull XmlPullParser parser, @NonNull String fontDir, @Nullable Map<String, File> updatableFontMap )124 private static Result readFamilies( 125 @NonNull XmlPullParser parser, 126 @NonNull String fontDir, 127 @Nullable Map<String, File> updatableFontMap 128 ) throws XmlPullParserException, IOException { 129 List<NamedFamilyList> families = new ArrayList<>(); 130 List<Alias> aliases = new ArrayList<>(); 131 List<FontConfig.Customization.LocaleFallback> outLocaleFamilies = new ArrayList<>(); 132 parser.require(XmlPullParser.START_TAG, null, "fonts-modification"); 133 while (parser.next() != XmlPullParser.END_TAG) { 134 if (parser.getEventType() != XmlPullParser.START_TAG) continue; 135 String tag = parser.getName(); 136 if (tag.equals("family")) { 137 readFamily(parser, fontDir, families, outLocaleFamilies, updatableFontMap); 138 } else if (tag.equals("family-list")) { 139 readFamilyList(parser, fontDir, families, updatableFontMap); 140 } else if (tag.equals("alias")) { 141 aliases.add(FontListParser.readAlias(parser)); 142 } else { 143 FontListParser.skip(parser); 144 } 145 } 146 return validateAndTransformToResult(families, outLocaleFamilies, aliases); 147 } 148 readFamily( @onNull XmlPullParser parser, @NonNull String fontDir, @NonNull List<NamedFamilyList> out, @NonNull List<FontConfig.Customization.LocaleFallback> outCustomization, @Nullable Map<String, File> updatableFontMap)149 private static void readFamily( 150 @NonNull XmlPullParser parser, 151 @NonNull String fontDir, 152 @NonNull List<NamedFamilyList> out, 153 @NonNull List<FontConfig.Customization.LocaleFallback> outCustomization, 154 @Nullable Map<String, File> updatableFontMap) 155 throws XmlPullParserException, IOException { 156 final String customizationType = parser.getAttributeValue(null, "customizationType"); 157 if (customizationType == null) { 158 throw new IllegalArgumentException("customizationType must be specified"); 159 } 160 if (customizationType.equals("new-named-family")) { 161 NamedFamilyList fontFamily = FontListParser.readNamedFamily( 162 parser, fontDir, updatableFontMap, false); 163 if (fontFamily != null) { 164 out.add(fontFamily); 165 } 166 } else if (customizationType.equals("new-locale-family")) { 167 final String lang = parser.getAttributeValue(null, "lang"); 168 final String op = parser.getAttributeValue(null, "operation"); 169 final int intOp; 170 if (op.equals("append")) { 171 intOp = FontConfig.Customization.LocaleFallback.OPERATION_APPEND; 172 } else if (op.equals("prepend")) { 173 intOp = FontConfig.Customization.LocaleFallback.OPERATION_PREPEND; 174 } else if (op.equals("replace")) { 175 intOp = FontConfig.Customization.LocaleFallback.OPERATION_REPLACE; 176 } else { 177 throw new IllegalArgumentException("Unknown operation=" + op); 178 } 179 180 final FontConfig.FontFamily family = FontListParser.readFamily( 181 parser, fontDir, updatableFontMap, false); 182 183 // For ignoring the customization, consume the new-locale-family element but don't 184 // register any customizations. 185 if (com.android.text.flags.Flags.vendorCustomLocaleFallback()) { 186 outCustomization.add(new FontConfig.Customization.LocaleFallback( 187 Locale.forLanguageTag(lang), intOp, family)); 188 } 189 } else { 190 throw new IllegalArgumentException("Unknown customizationType=" + customizationType); 191 } 192 } 193 readFamilyList( @onNull XmlPullParser parser, @NonNull String fontDir, @NonNull List<NamedFamilyList> out, @Nullable Map<String, File> updatableFontMap)194 private static void readFamilyList( 195 @NonNull XmlPullParser parser, 196 @NonNull String fontDir, 197 @NonNull List<NamedFamilyList> out, 198 @Nullable Map<String, File> updatableFontMap) 199 throws XmlPullParserException, IOException { 200 final String customizationType = parser.getAttributeValue(null, "customizationType"); 201 if (customizationType == null) { 202 throw new IllegalArgumentException("customizationType must be specified"); 203 } 204 if (customizationType.equals("new-named-family")) { 205 NamedFamilyList fontFamily = FontListParser.readNamedFamilyList( 206 parser, fontDir, updatableFontMap, false); 207 if (fontFamily != null) { 208 out.add(fontFamily); 209 } 210 } else { 211 throw new IllegalArgumentException("Unknown customizationType=" + customizationType); 212 } 213 } 214 } 215