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.util.Xml;
26 
27 import org.xmlpull.v1.XmlPullParser;
28 import org.xmlpull.v1.XmlPullParserException;
29 
30 import java.io.File;
31 import java.io.IOException;
32 import java.io.InputStream;
33 import java.util.ArrayList;
34 import java.util.Collections;
35 import java.util.HashMap;
36 import java.util.List;
37 import java.util.Map;
38 
39 /**
40  * Parser for font customization
41  *
42  * @hide
43  */
44 public class FontCustomizationParser {
45     private static final String TAG = "FontCustomizationParser";
46 
47     /**
48      * Represents a customization XML
49      */
50     public static class Result {
51         private final Map<String, NamedFamilyList> mAdditionalNamedFamilies;
52 
53         private final List<Alias> mAdditionalAliases;
54 
Result()55         public Result() {
56             mAdditionalNamedFamilies = Collections.emptyMap();
57             mAdditionalAliases = Collections.emptyList();
58         }
59 
Result(Map<String, NamedFamilyList> additionalNamedFamilies, List<Alias> additionalAliases)60         public Result(Map<String, NamedFamilyList> additionalNamedFamilies,
61                 List<Alias> additionalAliases) {
62             mAdditionalNamedFamilies = additionalNamedFamilies;
63             mAdditionalAliases = additionalAliases;
64         }
65 
getAdditionalNamedFamilies()66         public Map<String, NamedFamilyList> getAdditionalNamedFamilies() {
67             return mAdditionalNamedFamilies;
68         }
69 
getAdditionalAliases()70         public List<Alias> getAdditionalAliases() {
71             return mAdditionalAliases;
72         }
73     }
74 
75     /**
76      * Parses the customization XML
77      *
78      * Caller must close the input stream
79      */
parse( @onNull InputStream in, @NonNull String fontDir, @Nullable Map<String, File> updatableFontMap )80     public static Result parse(
81             @NonNull InputStream in,
82             @NonNull String fontDir,
83             @Nullable Map<String, File> updatableFontMap
84     ) throws XmlPullParserException, IOException {
85         XmlPullParser parser = Xml.newPullParser();
86         parser.setInput(in, null);
87         parser.nextTag();
88         return readFamilies(parser, fontDir, updatableFontMap);
89     }
90 
validateAndTransformToResult( List<NamedFamilyList> families, List<Alias> aliases)91     private static Result validateAndTransformToResult(
92             List<NamedFamilyList> families, List<Alias> aliases) {
93         HashMap<String, NamedFamilyList> namedFamily = new HashMap<>();
94         for (int i = 0; i < families.size(); ++i) {
95             final NamedFamilyList family = families.get(i);
96             final String name = family.getName();
97             if (name != null) {
98                 if (namedFamily.put(name, family) != null) {
99                     throw new IllegalArgumentException(
100                             "new-named-family requires unique name attribute");
101                 }
102             } else {
103                 throw new IllegalArgumentException(
104                         "new-named-family requires name attribute or new-default-fallback-family"
105                                 + "requires fallackTarget attribute");
106             }
107         }
108         return new Result(namedFamily, aliases);
109     }
110 
readFamilies( @onNull XmlPullParser parser, @NonNull String fontDir, @Nullable Map<String, File> updatableFontMap )111     private static Result readFamilies(
112             @NonNull XmlPullParser parser,
113             @NonNull String fontDir,
114             @Nullable Map<String, File> updatableFontMap
115     ) throws XmlPullParserException, IOException {
116         List<NamedFamilyList> families = new ArrayList<>();
117         List<Alias> aliases = new ArrayList<>();
118         parser.require(XmlPullParser.START_TAG, null, "fonts-modification");
119         while (parser.next() != XmlPullParser.END_TAG) {
120             if (parser.getEventType() != XmlPullParser.START_TAG) continue;
121             String tag = parser.getName();
122             if (tag.equals("family")) {
123                 readFamily(parser, fontDir, families, updatableFontMap);
124             } else if (tag.equals("family-list")) {
125                 readFamilyList(parser, fontDir, families, updatableFontMap);
126             } else if (tag.equals("alias")) {
127                 aliases.add(FontListParser.readAlias(parser));
128             } else {
129                 FontListParser.skip(parser);
130             }
131         }
132         return validateAndTransformToResult(families, aliases);
133     }
134 
readFamily( @onNull XmlPullParser parser, @NonNull String fontDir, @NonNull List<NamedFamilyList> out, @Nullable Map<String, File> updatableFontMap)135     private static void readFamily(
136             @NonNull XmlPullParser parser,
137             @NonNull String fontDir,
138             @NonNull List<NamedFamilyList> out,
139             @Nullable Map<String, File> updatableFontMap)
140             throws XmlPullParserException, IOException {
141         final String customizationType = parser.getAttributeValue(null, "customizationType");
142         if (customizationType == null) {
143             throw new IllegalArgumentException("customizationType must be specified");
144         }
145         if (customizationType.equals("new-named-family")) {
146             NamedFamilyList fontFamily = FontListParser.readNamedFamily(
147                     parser, fontDir, updatableFontMap, false);
148             if (fontFamily != null) {
149                 out.add(fontFamily);
150             }
151         } else {
152             throw new IllegalArgumentException("Unknown customizationType=" + customizationType);
153         }
154     }
155 
readFamilyList( @onNull XmlPullParser parser, @NonNull String fontDir, @NonNull List<NamedFamilyList> out, @Nullable Map<String, File> updatableFontMap)156     private static void readFamilyList(
157             @NonNull XmlPullParser parser,
158             @NonNull String fontDir,
159             @NonNull List<NamedFamilyList> out,
160             @Nullable Map<String, File> updatableFontMap)
161             throws XmlPullParserException, IOException {
162         final String customizationType = parser.getAttributeValue(null, "customizationType");
163         if (customizationType == null) {
164             throw new IllegalArgumentException("customizationType must be specified");
165         }
166         if (customizationType.equals("new-named-family")) {
167             NamedFamilyList fontFamily = FontListParser.readNamedFamilyList(
168                     parser, fontDir, updatableFontMap, false);
169             if (fontFamily != null) {
170                 out.add(fontFamily);
171             }
172         } else {
173             throw new IllegalArgumentException("Unknown customizationType=" + customizationType);
174         }
175     }
176 }
177