OSDN Git Service

Merge "Add getProgramList call to the broadcast radio API."
[android-x86/frameworks-base.git] / graphics / java / android / graphics / FontListParser.java
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.text.FontConfig;
20 import android.util.Xml;
21
22 import org.xmlpull.v1.XmlPullParser;
23 import org.xmlpull.v1.XmlPullParserException;
24
25 import android.annotation.Nullable;
26 import com.android.internal.annotations.VisibleForTesting;
27
28 import java.io.IOException;
29 import java.io.InputStream;
30 import java.util.ArrayList;
31 import java.util.List;
32 import java.util.regex.Pattern;
33
34 /**
35  * Parser for font config files.
36  *
37  * @hide
38  */
39 public class FontListParser {
40
41     /* Parse fallback list (no names) */
42     public static FontConfig parse(InputStream in) throws XmlPullParserException, IOException {
43         try {
44             XmlPullParser parser = Xml.newPullParser();
45             parser.setInput(in, null);
46             parser.nextTag();
47             return readFamilies(parser);
48         } finally {
49             in.close();
50         }
51     }
52
53     // Note that a well-formed variation contains a four-character tag and a float as styleValue,
54     // with spacers in between. The tag is enclosd either by double quotes or single quotes.
55     @VisibleForTesting
56     public static ArrayList<FontConfig.Axis> parseFontVariationSettings(@Nullable String settings) {
57         ArrayList<FontConfig.Axis> axisList = new ArrayList<>();
58         if (settings == null) {
59             return axisList;
60         }
61         String[] settingList = settings.split(",");
62         settingLoop:
63         for (String setting : settingList) {
64             int pos = 0;
65             while (pos < setting.length()) {
66                 char c = setting.charAt(pos);
67                 if (c == '\'' || c == '"') {
68                     break;
69                 } else if (!isSpacer(c)) {
70                     continue settingLoop;  // Only spacers are allowed before tag appeared.
71                 }
72                 pos++;
73             }
74             if (pos + 7 > setting.length()) {
75                 continue;  // 7 is the minimum length of tag-style value pair text.
76             }
77             if (setting.charAt(pos) != setting.charAt(pos + 5)) {
78                 continue;  // Tag should be wrapped with double or single quote.
79             }
80             String tagString = setting.substring(pos + 1, pos + 5);
81             if (!TAG_PATTERN.matcher(tagString).matches()) {
82                 continue;  // Skip incorrect format tag.
83             }
84             pos += 6;
85             while (pos < setting.length()) {
86                 if (!isSpacer(setting.charAt(pos++))) {
87                     break;  // Skip spacers between the tag and the styleValue.
88                 }
89             }
90             // Skip invalid styleValue
91             float styleValue;
92             String valueString = setting.substring(pos - 1);
93             if (!STYLE_VALUE_PATTERN.matcher(valueString).matches()) {
94                 continue;  // Skip incorrect format styleValue.
95             }
96             try {
97                 styleValue = Float.parseFloat(valueString);
98             } catch (NumberFormatException e) {
99                 continue;  // ignoreing invalid number format
100             }
101             int tag = makeTag(tagString.charAt(0), tagString.charAt(1), tagString.charAt(2),
102                     tagString.charAt(3));
103             axisList.add(new FontConfig.Axis(tag, styleValue));
104         }
105         return axisList;
106     }
107
108     @VisibleForTesting
109     public static int makeTag(char c1, char c2, char c3, char c4) {
110         return (c1 << 24) | (c2 << 16) | (c3 << 8) | c4;
111     }
112
113     private static boolean isSpacer(char c) {
114         return c == ' ' || c == '\r' || c == '\t' || c == '\n';
115     }
116
117     private static FontConfig readFamilies(XmlPullParser parser)
118             throws XmlPullParserException, IOException {
119         List<FontConfig.Family> families = new ArrayList<>();
120         List<FontConfig.Alias> aliases = new ArrayList<>();
121
122         parser.require(XmlPullParser.START_TAG, null, "familyset");
123         while (parser.next() != XmlPullParser.END_TAG) {
124             if (parser.getEventType() != XmlPullParser.START_TAG) continue;
125             String tag = parser.getName();
126             if (tag.equals("family")) {
127                 families.add(readFamily(parser));
128             } else if (tag.equals("alias")) {
129                 aliases.add(readAlias(parser));
130             } else {
131                 skip(parser);
132             }
133         }
134         return new FontConfig(families.toArray(new FontConfig.Family[families.size()]),
135                 aliases.toArray(new FontConfig.Alias[aliases.size()]));
136     }
137
138     private static FontConfig.Family readFamily(XmlPullParser parser)
139             throws XmlPullParserException, IOException {
140         String name = parser.getAttributeValue(null, "name");
141         String lang = parser.getAttributeValue(null, "lang");
142         String variant = parser.getAttributeValue(null, "variant");
143         List<FontConfig.Font> fonts = new ArrayList<FontConfig.Font>();
144         while (parser.next() != XmlPullParser.END_TAG) {
145             if (parser.getEventType() != XmlPullParser.START_TAG) continue;
146             String tag = parser.getName();
147             if (tag.equals("font")) {
148                 fonts.add(readFont(parser));
149             } else {
150                 skip(parser);
151             }
152         }
153         int intVariant = FontConfig.Family.VARIANT_DEFAULT;
154         if (variant != null) {
155             if (variant.equals("compact")) {
156                 intVariant = FontConfig.Family.VARIANT_COMPACT;
157             } else if (variant.equals("elegant")) {
158                 intVariant = FontConfig.Family.VARIANT_ELEGANT;
159             }
160         }
161         return new FontConfig.Family(name, fonts.toArray(new FontConfig.Font[fonts.size()]), lang,
162                 intVariant);
163     }
164
165     /** Matches leading and trailing XML whitespace. */
166     private static final Pattern FILENAME_WHITESPACE_PATTERN =
167             Pattern.compile("^[ \\n\\r\\t]+|[ \\n\\r\\t]+$");
168
169     private static FontConfig.Font readFont(XmlPullParser parser)
170             throws XmlPullParserException, IOException {
171         String indexStr = parser.getAttributeValue(null, "index");
172         int index = indexStr == null ? 0 : Integer.parseInt(indexStr);
173         List<FontConfig.Axis> axes = new ArrayList<FontConfig.Axis>();
174         String weightStr = parser.getAttributeValue(null, "weight");
175         int weight = weightStr == null ? 400 : Integer.parseInt(weightStr);
176         boolean isItalic = "italic".equals(parser.getAttributeValue(null, "style"));
177         StringBuilder filename = new StringBuilder();
178         while (parser.next() != XmlPullParser.END_TAG) {
179             if (parser.getEventType() == XmlPullParser.TEXT) {
180                 filename.append(parser.getText());
181             }
182             if (parser.getEventType() != XmlPullParser.START_TAG) continue;
183             String tag = parser.getName();
184             if (tag.equals("axis")) {
185                 axes.add(readAxis(parser));
186             } else {
187                 skip(parser);
188             }
189         }
190         String fullFilename = "/system/fonts/" +
191                 FILENAME_WHITESPACE_PATTERN.matcher(filename).replaceAll("");
192         return new FontConfig.Font(fullFilename, index,
193                 axes.toArray(new FontConfig.Axis[axes.size()]), weight, isItalic);
194     }
195
196     /** The 'tag' attribute value is read as four character values between U+0020 and U+007E
197      *  inclusive.
198      */
199     private static final Pattern TAG_PATTERN = Pattern.compile("[\\x20-\\x7E]{4}");
200
201     /** The 'styleValue' attribute has an optional leading '-', followed by '<digits>',
202      *  '<digits>.<digits>', or '.<digits>' where '<digits>' is one or more of [0-9].
203      */
204     private static final Pattern STYLE_VALUE_PATTERN =
205             Pattern.compile("-?(([0-9]+(\\.[0-9]+)?)|(\\.[0-9]+))");
206
207     private static FontConfig.Axis readAxis(XmlPullParser parser)
208             throws XmlPullParserException, IOException {
209         int tag = 0;
210         String tagStr = parser.getAttributeValue(null, "tag");
211         if (tagStr != null && TAG_PATTERN.matcher(tagStr).matches()) {
212             tag = makeTag(tagStr.charAt(0), tagStr.charAt(1), tagStr.charAt(2), tagStr.charAt(3));
213         } else {
214             throw new XmlPullParserException("Invalid tag attribute value.", parser, null);
215         }
216
217         float styleValue = 0;
218         String styleValueStr = parser.getAttributeValue(null, "stylevalue");
219         if (styleValueStr != null && STYLE_VALUE_PATTERN.matcher(styleValueStr).matches()) {
220             styleValue = Float.parseFloat(styleValueStr);
221         } else {
222             throw new XmlPullParserException("Invalid styleValue attribute value.", parser, null);
223         }
224
225         skip(parser);  // axis tag is empty, ignore any contents and consume end tag
226         return new FontConfig.Axis(tag, styleValue);
227     }
228
229     private static FontConfig.Alias readAlias(XmlPullParser parser)
230             throws XmlPullParserException, IOException {
231         String name = parser.getAttributeValue(null, "name");
232         String toName = parser.getAttributeValue(null, "to");
233         String weightStr = parser.getAttributeValue(null, "weight");
234         int weight;
235         if (weightStr == null) {
236             weight = 400;
237         } else {
238             weight = Integer.parseInt(weightStr);
239         }
240         skip(parser);  // alias tag is empty, ignore any contents and consume end tag
241         return new FontConfig.Alias(name, toName, weight);
242     }
243
244     private static void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
245         int depth = 1;
246         while (depth > 0) {
247             switch (parser.next()) {
248             case XmlPullParser.START_TAG:
249                 depth++;
250                 break;
251             case XmlPullParser.END_TAG:
252                 depth--;
253                 break;
254             }
255         }
256     }
257 }