• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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