2 * Copyright (C) 2014 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package android.graphics;
19 import android.text.FontConfig;
20 import android.util.Xml;
22 import org.xmlpull.v1.XmlPullParser;
23 import org.xmlpull.v1.XmlPullParserException;
25 import android.annotation.Nullable;
26 import com.android.internal.annotations.VisibleForTesting;
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;
35 * Parser for font config files.
39 public class FontListParser {
41 /* Parse fallback list (no names) */
42 public static FontConfig parse(InputStream in) throws XmlPullParserException, IOException {
44 XmlPullParser parser = Xml.newPullParser();
45 parser.setInput(in, null);
47 return readFamilies(parser);
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.
56 public static ArrayList<FontConfig.Axis> parseFontVariationSettings(@Nullable String settings) {
57 ArrayList<FontConfig.Axis> axisList = new ArrayList<>();
58 if (settings == null) {
61 String[] settingList = settings.split(",");
63 for (String setting : settingList) {
65 while (pos < setting.length()) {
66 char c = setting.charAt(pos);
67 if (c == '\'' || c == '"') {
69 } else if (!isSpacer(c)) {
70 continue settingLoop; // Only spacers are allowed before tag appeared.
74 if (pos + 7 > setting.length()) {
75 continue; // 7 is the minimum length of tag-style value pair text.
77 if (setting.charAt(pos) != setting.charAt(pos + 5)) {
78 continue; // Tag should be wrapped with double or single quote.
80 String tagString = setting.substring(pos + 1, pos + 5);
81 if (!TAG_PATTERN.matcher(tagString).matches()) {
82 continue; // Skip incorrect format tag.
85 while (pos < setting.length()) {
86 if (!isSpacer(setting.charAt(pos++))) {
87 break; // Skip spacers between the tag and the styleValue.
90 // Skip invalid styleValue
92 String valueString = setting.substring(pos - 1);
93 if (!STYLE_VALUE_PATTERN.matcher(valueString).matches()) {
94 continue; // Skip incorrect format styleValue.
97 styleValue = Float.parseFloat(valueString);
98 } catch (NumberFormatException e) {
99 continue; // ignoreing invalid number format
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));
109 public static int makeTag(char c1, char c2, char c3, char c4) {
110 return (c1 << 24) | (c2 << 16) | (c3 << 8) | c4;
113 private static boolean isSpacer(char c) {
114 return c == ' ' || c == '\r' || c == '\t' || c == '\n';
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<>();
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));
134 return new FontConfig(families.toArray(new FontConfig.Family[families.size()]),
135 aliases.toArray(new FontConfig.Alias[aliases.size()]));
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));
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;
161 return new FontConfig.Family(name, fonts.toArray(new FontConfig.Font[fonts.size()]), lang,
165 /** Matches leading and trailing XML whitespace. */
166 private static final Pattern FILENAME_WHITESPACE_PATTERN =
167 Pattern.compile("^[ \\n\\r\\t]+|[ \\n\\r\\t]+$");
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());
182 if (parser.getEventType() != XmlPullParser.START_TAG) continue;
183 String tag = parser.getName();
184 if (tag.equals("axis")) {
185 axes.add(readAxis(parser));
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);
196 /** The 'tag' attribute value is read as four character values between U+0020 and U+007E
199 private static final Pattern TAG_PATTERN = Pattern.compile("[\\x20-\\x7E]{4}");
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].
204 private static final Pattern STYLE_VALUE_PATTERN =
205 Pattern.compile("-?(([0-9]+(\\.[0-9]+)?)|(\\.[0-9]+))");
207 private static FontConfig.Axis readAxis(XmlPullParser parser)
208 throws XmlPullParserException, IOException {
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));
214 throw new XmlPullParserException("Invalid tag attribute value.", parser, null);
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);
222 throw new XmlPullParserException("Invalid styleValue attribute value.", parser, null);
225 skip(parser); // axis tag is empty, ignore any contents and consume end tag
226 return new FontConfig.Axis(tag, styleValue);
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");
235 if (weightStr == null) {
238 weight = Integer.parseInt(weightStr);
240 skip(parser); // alias tag is empty, ignore any contents and consume end tag
241 return new FontConfig.Alias(name, toName, weight);
244 private static void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
247 switch (parser.next()) {
248 case XmlPullParser.START_TAG:
251 case XmlPullParser.END_TAG: