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 @android.ravenwood.annotation.RavenwoodKeepWholeClass 47 public class FontCustomizationParser { 48 private static final String TAG = "FontCustomizationParser"; 49 50 /** 51 * Represents a customization XML 52 */ 53 public static class Result { 54 private final Map<String, NamedFamilyList> mAdditionalNamedFamilies; 55 56 private final List<Alias> mAdditionalAliases; 57 58 private final List<FontConfig.Customization.LocaleFallback> mLocaleFamilyCustomizations; 59 Result()60 public Result() { 61 mAdditionalNamedFamilies = Collections.emptyMap(); 62 mLocaleFamilyCustomizations = Collections.emptyList(); 63 mAdditionalAliases = Collections.emptyList(); 64 } 65 Result(Map<String, NamedFamilyList> additionalNamedFamilies, List<FontConfig.Customization.LocaleFallback> localeFamilyCustomizations, List<Alias> additionalAliases)66 public Result(Map<String, NamedFamilyList> additionalNamedFamilies, 67 List<FontConfig.Customization.LocaleFallback> localeFamilyCustomizations, 68 List<Alias> additionalAliases) { 69 mAdditionalNamedFamilies = additionalNamedFamilies; 70 mLocaleFamilyCustomizations = localeFamilyCustomizations; 71 mAdditionalAliases = additionalAliases; 72 } 73 getAdditionalNamedFamilies()74 public Map<String, NamedFamilyList> getAdditionalNamedFamilies() { 75 return mAdditionalNamedFamilies; 76 } 77 getAdditionalAliases()78 public List<Alias> getAdditionalAliases() { 79 return mAdditionalAliases; 80 } 81 getLocaleFamilyCustomizations()82 public List<FontConfig.Customization.LocaleFallback> getLocaleFamilyCustomizations() { 83 return mLocaleFamilyCustomizations; 84 } 85 } 86 87 /** 88 * Parses the customization XML 89 * 90 * Caller must close the input stream 91 */ parse( @onNull InputStream in, @NonNull String fontDir, @Nullable Map<String, File> updatableFontMap )92 public static Result parse( 93 @NonNull InputStream in, 94 @NonNull String fontDir, 95 @Nullable Map<String, File> updatableFontMap 96 ) throws XmlPullParserException, IOException { 97 XmlPullParser parser = Xml.newPullParser(); 98 parser.setInput(in, null); 99 parser.nextTag(); 100 return readFamilies(parser, fontDir, updatableFontMap); 101 } 102 validateAndTransformToResult( List<NamedFamilyList> families, List<FontConfig.Customization.LocaleFallback> outLocaleFamilies, List<Alias> aliases)103 private static Result validateAndTransformToResult( 104 List<NamedFamilyList> families, 105 List<FontConfig.Customization.LocaleFallback> outLocaleFamilies, 106 List<Alias> aliases) { 107 HashMap<String, NamedFamilyList> namedFamily = new HashMap<>(); 108 for (int i = 0; i < families.size(); ++i) { 109 final NamedFamilyList family = families.get(i); 110 final String name = family.getName(); 111 if (name != null) { 112 if (namedFamily.put(name, family) != null) { 113 throw new IllegalArgumentException( 114 "new-named-family requires unique name attribute"); 115 } 116 } else { 117 throw new IllegalArgumentException( 118 "new-named-family requires name attribute or new-default-fallback-family" 119 + "requires fallackTarget attribute"); 120 } 121 } 122 return new Result(namedFamily, outLocaleFamilies, aliases); 123 } 124 readFamilies( @onNull XmlPullParser parser, @NonNull String fontDir, @Nullable Map<String, File> updatableFontMap )125 private static Result readFamilies( 126 @NonNull XmlPullParser parser, 127 @NonNull String fontDir, 128 @Nullable Map<String, File> updatableFontMap 129 ) throws XmlPullParserException, IOException { 130 List<NamedFamilyList> families = new ArrayList<>(); 131 List<Alias> aliases = new ArrayList<>(); 132 List<FontConfig.Customization.LocaleFallback> outLocaleFamilies = new ArrayList<>(); 133 parser.require(XmlPullParser.START_TAG, null, "fonts-modification"); 134 while (parser.next() != XmlPullParser.END_TAG) { 135 if (parser.getEventType() != XmlPullParser.START_TAG) continue; 136 String tag = parser.getName(); 137 if (tag.equals("family")) { 138 readFamily(parser, fontDir, families, outLocaleFamilies, updatableFontMap); 139 } else if (tag.equals("family-list")) { 140 readFamilyList(parser, fontDir, families, updatableFontMap); 141 } else if (tag.equals("alias")) { 142 aliases.add(FontListParser.readAlias(parser)); 143 } else { 144 FontListParser.skip(parser); 145 } 146 } 147 return validateAndTransformToResult(families, outLocaleFamilies, aliases); 148 } 149 readFamily( @onNull XmlPullParser parser, @NonNull String fontDir, @NonNull List<NamedFamilyList> out, @NonNull List<FontConfig.Customization.LocaleFallback> outCustomization, @Nullable Map<String, File> updatableFontMap)150 private static void readFamily( 151 @NonNull XmlPullParser parser, 152 @NonNull String fontDir, 153 @NonNull List<NamedFamilyList> out, 154 @NonNull List<FontConfig.Customization.LocaleFallback> outCustomization, 155 @Nullable Map<String, File> updatableFontMap) 156 throws XmlPullParserException, IOException { 157 final String customizationType = parser.getAttributeValue(null, "customizationType"); 158 if (customizationType == null) { 159 throw new IllegalArgumentException("customizationType must be specified"); 160 } 161 if (customizationType.equals("new-named-family")) { 162 NamedFamilyList fontFamily = FontListParser.readNamedFamily( 163 parser, fontDir, updatableFontMap, false); 164 if (fontFamily != null) { 165 out.add(fontFamily); 166 } 167 } else if (customizationType.equals("new-locale-family")) { 168 final String lang = parser.getAttributeValue(null, "lang"); 169 final String op = parser.getAttributeValue(null, "operation"); 170 final int intOp; 171 if (op.equals("append")) { 172 intOp = FontConfig.Customization.LocaleFallback.OPERATION_APPEND; 173 } else if (op.equals("prepend")) { 174 intOp = FontConfig.Customization.LocaleFallback.OPERATION_PREPEND; 175 } else if (op.equals("replace")) { 176 intOp = FontConfig.Customization.LocaleFallback.OPERATION_REPLACE; 177 } else { 178 throw new IllegalArgumentException("Unknown operation=" + op); 179 } 180 181 final FontConfig.FontFamily family = FontListParser.readFamily( 182 parser, fontDir, updatableFontMap, false); 183 184 // For ignoring the customization, consume the new-locale-family element but don't 185 // register any customizations. 186 outCustomization.add(new FontConfig.Customization.LocaleFallback( 187 Locale.forLanguageTag(lang), intOp, family)); 188 } else { 189 throw new IllegalArgumentException("Unknown customizationType=" + customizationType); 190 } 191 } 192 readFamilyList( @onNull XmlPullParser parser, @NonNull String fontDir, @NonNull List<NamedFamilyList> out, @Nullable Map<String, File> updatableFontMap)193 private static void readFamilyList( 194 @NonNull XmlPullParser parser, 195 @NonNull String fontDir, 196 @NonNull List<NamedFamilyList> out, 197 @Nullable Map<String, File> updatableFontMap) 198 throws XmlPullParserException, IOException { 199 final String customizationType = parser.getAttributeValue(null, "customizationType"); 200 if (customizationType == null) { 201 throw new IllegalArgumentException("customizationType must be specified"); 202 } 203 if (customizationType.equals("new-named-family")) { 204 NamedFamilyList fontFamily = FontListParser.readNamedFamilyList( 205 parser, fontDir, updatableFontMap, false); 206 if (fontFamily != null) { 207 out.add(fontFamily); 208 } 209 } else { 210 throw new IllegalArgumentException("Unknown customizationType=" + customizationType); 211 } 212 } 213 } 214