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.util.Xml;
20 
21 import org.xmlpull.v1.XmlPullParser;
22 import org.xmlpull.v1.XmlPullParserException;
23 
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.util.ArrayList;
27 import java.util.List;
28 import java.util.regex.Pattern;
29 
30 /**
31  * Parser for font config files.
32  *
33  * @hide
34  */
35 public class FontListParser {
36 
37     public static class Config {
Config()38         Config() {
39             families = new ArrayList<Family>();
40             aliases = new ArrayList<Alias>();
41         }
42         public List<Family> families;
43         public List<Alias> aliases;
44     }
45 
46     public static class Axis {
Axis(int tag, float styleValue)47         Axis(int tag, float styleValue) {
48             this.tag = tag;
49             this.styleValue = styleValue;
50         }
51         public final int tag;
52         public final float styleValue;
53     }
54 
55     public static class Font {
Font(String fontName, int ttcIndex, List<Axis> axes, int weight, boolean isItalic)56         Font(String fontName, int ttcIndex, List<Axis> axes, int weight, boolean isItalic) {
57             this.fontName = fontName;
58             this.ttcIndex = ttcIndex;
59             this.axes = axes;
60             this.weight = weight;
61             this.isItalic = isItalic;
62         }
63         public String fontName;
64         public int ttcIndex;
65         public final List<Axis> axes;
66         public int weight;
67         public boolean isItalic;
68     }
69 
70     public static class Alias {
71         public String name;
72         public String toName;
73         public int weight;
74     }
75 
76     public static class Family {
Family(String name, List<Font> fonts, String lang, String variant)77         public Family(String name, List<Font> fonts, String lang, String variant) {
78             this.name = name;
79             this.fonts = fonts;
80             this.lang = lang;
81             this.variant = variant;
82         }
83 
84         public String name;
85         public List<Font> fonts;
86         public String lang;
87         public String variant;
88     }
89 
90     /* Parse fallback list (no names) */
parse(InputStream in)91     public static Config parse(InputStream in) throws XmlPullParserException, IOException {
92         try {
93             XmlPullParser parser = Xml.newPullParser();
94             parser.setInput(in, null);
95             parser.nextTag();
96             return readFamilies(parser);
97         } finally {
98             in.close();
99         }
100     }
101 
readFamilies(XmlPullParser parser)102     private static Config readFamilies(XmlPullParser parser)
103             throws XmlPullParserException, IOException {
104         Config config = new Config();
105         parser.require(XmlPullParser.START_TAG, null, "familyset");
106         while (parser.next() != XmlPullParser.END_TAG) {
107             if (parser.getEventType() != XmlPullParser.START_TAG) continue;
108             String tag = parser.getName();
109             if (tag.equals("family")) {
110                 config.families.add(readFamily(parser));
111             } else if (tag.equals("alias")) {
112                 config.aliases.add(readAlias(parser));
113             } else {
114                 skip(parser);
115             }
116         }
117         return config;
118     }
119 
readFamily(XmlPullParser parser)120     private static Family readFamily(XmlPullParser parser)
121             throws XmlPullParserException, IOException {
122         String name = parser.getAttributeValue(null, "name");
123         String lang = parser.getAttributeValue(null, "lang");
124         String variant = parser.getAttributeValue(null, "variant");
125         List<Font> fonts = new ArrayList<Font>();
126         while (parser.next() != XmlPullParser.END_TAG) {
127             if (parser.getEventType() != XmlPullParser.START_TAG) continue;
128             String tag = parser.getName();
129             if (tag.equals("font")) {
130                 fonts.add(readFont(parser));
131             } else {
132                 skip(parser);
133             }
134         }
135         return new Family(name, fonts, lang, variant);
136     }
137 
138     /** Matches leading and trailing XML whitespace. */
139     private static final Pattern FILENAME_WHITESPACE_PATTERN =
140             Pattern.compile("^[ \\n\\r\\t]+|[ \\n\\r\\t]+$");
141 
readFont(XmlPullParser parser)142     private static Font readFont(XmlPullParser parser)
143             throws XmlPullParserException, IOException {
144         String indexStr = parser.getAttributeValue(null, "index");
145         int index = indexStr == null ? 0 : Integer.parseInt(indexStr);
146         List<Axis> axes = new ArrayList<Axis>();
147         String weightStr = parser.getAttributeValue(null, "weight");
148         int weight = weightStr == null ? 400 : Integer.parseInt(weightStr);
149         boolean isItalic = "italic".equals(parser.getAttributeValue(null, "style"));
150         StringBuilder filename = new StringBuilder();
151         while (parser.next() != XmlPullParser.END_TAG) {
152             if (parser.getEventType() == XmlPullParser.TEXT) {
153                 filename.append(parser.getText());
154             }
155             if (parser.getEventType() != XmlPullParser.START_TAG) continue;
156             String tag = parser.getName();
157             if (tag.equals("axis")) {
158                 axes.add(readAxis(parser));
159             } else {
160                 skip(parser);
161             }
162         }
163         String fullFilename = "/system/fonts/" +
164                 FILENAME_WHITESPACE_PATTERN.matcher(filename).replaceAll("");
165         return new Font(fullFilename, index, axes, weight, isItalic);
166     }
167 
168     /** The 'tag' attribute value is read as four character values between 0 and 255 inclusive. */
169     private static final Pattern TAG_PATTERN = Pattern.compile("[\\x00-\\xFF]{4}");
170 
171     /** The 'styleValue' attribute has an optional leading '-', followed by '<digits>',
172      *  '<digits>.<digits>', or '.<digits>' where '<digits>' is one or more of [0-9].
173      */
174     private static final Pattern STYLE_VALUE_PATTERN =
175             Pattern.compile("-?(([0-9]+(\\.[0-9]+)?)|(\\.[0-9]+))");
176 
readAxis(XmlPullParser parser)177     private static Axis readAxis(XmlPullParser parser)
178             throws XmlPullParserException, IOException {
179         int tag = 0;
180         String tagStr = parser.getAttributeValue(null, "tag");
181         if (tagStr != null && TAG_PATTERN.matcher(tagStr).matches()) {
182             tag = (tagStr.charAt(0) << 24) +
183                   (tagStr.charAt(1) << 16) +
184                   (tagStr.charAt(2) <<  8) +
185                   (tagStr.charAt(3)      );
186         } else {
187             throw new XmlPullParserException("Invalid tag attribute value.", parser, null);
188         }
189 
190         float styleValue = 0;
191         String styleValueStr = parser.getAttributeValue(null, "stylevalue");
192         if (styleValueStr != null && STYLE_VALUE_PATTERN.matcher(styleValueStr).matches()) {
193             styleValue = Float.parseFloat(styleValueStr);
194         } else {
195             throw new XmlPullParserException("Invalid styleValue attribute value.", parser, null);
196         }
197 
198         skip(parser);  // axis tag is empty, ignore any contents and consume end tag
199         return new Axis(tag, styleValue);
200     }
201 
readAlias(XmlPullParser parser)202     private static Alias readAlias(XmlPullParser parser)
203             throws XmlPullParserException, IOException {
204         Alias alias = new Alias();
205         alias.name = parser.getAttributeValue(null, "name");
206         alias.toName = parser.getAttributeValue(null, "to");
207         String weightStr = parser.getAttributeValue(null, "weight");
208         if (weightStr == null) {
209             alias.weight = 400;
210         } else {
211             alias.weight = Integer.parseInt(weightStr);
212         }
213         skip(parser);  // alias tag is empty, ignore any contents and consume end tag
214         return alias;
215     }
216 
skip(XmlPullParser parser)217     private static void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
218         int depth = 1;
219         while (depth > 0) {
220             switch (parser.next()) {
221             case XmlPullParser.START_TAG:
222                 depth++;
223                 break;
224             case XmlPullParser.END_TAG:
225                 depth--;
226                 break;
227             }
228         }
229     }
230 }
231