1 /*
2  * Copyright (C) 2014 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;
18 
19 import android.graphics.fonts.FontVariationAxis;
20 import android.text.FontConfig;
21 import android.util.Xml;
22 
23 import org.xmlpull.v1.XmlPullParser;
24 import org.xmlpull.v1.XmlPullParserException;
25 
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.util.ArrayList;
29 import java.util.List;
30 import java.util.regex.Pattern;
31 
32 /**
33  * Parser for font config files.
34  *
35  * @hide
36  */
37 public class FontListParser {
38 
39     /* Parse fallback list (no names) */
parse(InputStream in)40     public static FontConfig parse(InputStream in) throws XmlPullParserException, IOException {
41         try {
42             XmlPullParser parser = Xml.newPullParser();
43             parser.setInput(in, null);
44             parser.nextTag();
45             return readFamilies(parser);
46         } finally {
47             in.close();
48         }
49     }
50 
readFamilies(XmlPullParser parser)51     private static FontConfig readFamilies(XmlPullParser parser)
52             throws XmlPullParserException, IOException {
53         List<FontConfig.Family> families = new ArrayList<>();
54         List<FontConfig.Alias> aliases = new ArrayList<>();
55 
56         parser.require(XmlPullParser.START_TAG, null, "familyset");
57         while (parser.next() != XmlPullParser.END_TAG) {
58             if (parser.getEventType() != XmlPullParser.START_TAG) continue;
59             String tag = parser.getName();
60             if (tag.equals("family")) {
61                 families.add(readFamily(parser));
62             } else if (tag.equals("alias")) {
63                 aliases.add(readAlias(parser));
64             } else {
65                 skip(parser);
66             }
67         }
68         return new FontConfig(families.toArray(new FontConfig.Family[families.size()]),
69                 aliases.toArray(new FontConfig.Alias[aliases.size()]));
70     }
71 
readFamily(XmlPullParser parser)72     private static FontConfig.Family readFamily(XmlPullParser parser)
73             throws XmlPullParserException, IOException {
74         final String name = parser.getAttributeValue(null, "name");
75         final String lang = parser.getAttributeValue(null, "lang");
76         final String[] langs = lang == null ? null : lang.split("\\s+");
77         final String variant = parser.getAttributeValue(null, "variant");
78         final List<FontConfig.Font> fonts = new ArrayList<FontConfig.Font>();
79         while (parser.next() != XmlPullParser.END_TAG) {
80             if (parser.getEventType() != XmlPullParser.START_TAG) continue;
81             final String tag = parser.getName();
82             if (tag.equals("font")) {
83                 fonts.add(readFont(parser));
84             } else {
85                 skip(parser);
86             }
87         }
88         int intVariant = FontConfig.Family.VARIANT_DEFAULT;
89         if (variant != null) {
90             if (variant.equals("compact")) {
91                 intVariant = FontConfig.Family.VARIANT_COMPACT;
92             } else if (variant.equals("elegant")) {
93                 intVariant = FontConfig.Family.VARIANT_ELEGANT;
94             }
95         }
96         return new FontConfig.Family(name, fonts.toArray(new FontConfig.Font[fonts.size()]), langs,
97                 intVariant);
98     }
99 
100     /** Matches leading and trailing XML whitespace. */
101     private static final Pattern FILENAME_WHITESPACE_PATTERN =
102             Pattern.compile("^[ \\n\\r\\t]+|[ \\n\\r\\t]+$");
103 
readFont(XmlPullParser parser)104     private static FontConfig.Font readFont(XmlPullParser parser)
105             throws XmlPullParserException, IOException {
106         String indexStr = parser.getAttributeValue(null, "index");
107         int index = indexStr == null ? 0 : Integer.parseInt(indexStr);
108         List<FontVariationAxis> axes = new ArrayList<FontVariationAxis>();
109         String weightStr = parser.getAttributeValue(null, "weight");
110         int weight = weightStr == null ? 400 : Integer.parseInt(weightStr);
111         boolean isItalic = "italic".equals(parser.getAttributeValue(null, "style"));
112         String fallbackFor = parser.getAttributeValue(null, "fallbackFor");
113         StringBuilder filename = new StringBuilder();
114         while (parser.next() != XmlPullParser.END_TAG) {
115             if (parser.getEventType() == XmlPullParser.TEXT) {
116                 filename.append(parser.getText());
117             }
118             if (parser.getEventType() != XmlPullParser.START_TAG) continue;
119             String tag = parser.getName();
120             if (tag.equals("axis")) {
121                 axes.add(readAxis(parser));
122             } else {
123                 skip(parser);
124             }
125         }
126         String sanitizedName = FILENAME_WHITESPACE_PATTERN.matcher(filename).replaceAll("");
127         return new FontConfig.Font(sanitizedName, index,
128                 axes.toArray(new FontVariationAxis[axes.size()]), weight, isItalic, fallbackFor);
129     }
130 
readAxis(XmlPullParser parser)131     private static FontVariationAxis readAxis(XmlPullParser parser)
132             throws XmlPullParserException, IOException {
133         String tagStr = parser.getAttributeValue(null, "tag");
134         String styleValueStr = parser.getAttributeValue(null, "stylevalue");
135         skip(parser);  // axis tag is empty, ignore any contents and consume end tag
136         return new FontVariationAxis(tagStr, Float.parseFloat(styleValueStr));
137     }
138 
readAlias(XmlPullParser parser)139     private static FontConfig.Alias readAlias(XmlPullParser parser)
140             throws XmlPullParserException, IOException {
141         String name = parser.getAttributeValue(null, "name");
142         String toName = parser.getAttributeValue(null, "to");
143         String weightStr = parser.getAttributeValue(null, "weight");
144         int weight;
145         if (weightStr == null) {
146             weight = 400;
147         } else {
148             weight = Integer.parseInt(weightStr);
149         }
150         skip(parser);  // alias tag is empty, ignore any contents and consume end tag
151         return new FontConfig.Alias(name, toName, weight);
152     }
153 
skip(XmlPullParser parser)154     private static void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
155         int depth = 1;
156         while (depth > 0) {
157             switch (parser.next()) {
158             case XmlPullParser.START_TAG:
159                 depth++;
160                 break;
161             case XmlPullParser.END_TAG:
162                 depth--;
163                 break;
164             }
165         }
166     }
167 }
168