FontListParser.java revision 9ff994d98846d24bc488939af6e7dc440149a4bc
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 17package android.graphics; 18 19import android.text.FontConfig; 20import android.util.Xml; 21 22import org.xmlpull.v1.XmlPullParser; 23import org.xmlpull.v1.XmlPullParserException; 24 25import android.annotation.Nullable; 26import com.android.internal.annotations.VisibleForTesting; 27 28import java.io.IOException; 29import java.io.InputStream; 30import java.util.ArrayList; 31import java.util.List; 32import java.util.regex.Pattern; 33 34/** 35 * Parser for font config files. 36 * 37 * @hide 38 */ 39public 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 FontConfig config = new FontConfig(); 120 parser.require(XmlPullParser.START_TAG, null, "familyset"); 121 while (parser.next() != XmlPullParser.END_TAG) { 122 if (parser.getEventType() != XmlPullParser.START_TAG) continue; 123 String tag = parser.getName(); 124 if (tag.equals("family")) { 125 config.getFamilies().add(readFamily(parser)); 126 } else if (tag.equals("alias")) { 127 config.getAliases().add(readAlias(parser)); 128 } else { 129 skip(parser); 130 } 131 } 132 return config; 133 } 134 135 private static FontConfig.Family readFamily(XmlPullParser parser) 136 throws XmlPullParserException, IOException { 137 String name = parser.getAttributeValue(null, "name"); 138 String lang = parser.getAttributeValue(null, "lang"); 139 String variant = parser.getAttributeValue(null, "variant"); 140 List<FontConfig.Font> fonts = new ArrayList<FontConfig.Font>(); 141 while (parser.next() != XmlPullParser.END_TAG) { 142 if (parser.getEventType() != XmlPullParser.START_TAG) continue; 143 String tag = parser.getName(); 144 if (tag.equals("font")) { 145 fonts.add(readFont(parser)); 146 } else { 147 skip(parser); 148 } 149 } 150 return new FontConfig.Family(name, fonts, lang, variant); 151 } 152 153 /** Matches leading and trailing XML whitespace. */ 154 private static final Pattern FILENAME_WHITESPACE_PATTERN = 155 Pattern.compile("^[ \\n\\r\\t]+|[ \\n\\r\\t]+$"); 156 157 private static FontConfig.Font readFont(XmlPullParser parser) 158 throws XmlPullParserException, IOException { 159 String indexStr = parser.getAttributeValue(null, "index"); 160 int index = indexStr == null ? 0 : Integer.parseInt(indexStr); 161 List<FontConfig.Axis> axes = new ArrayList<FontConfig.Axis>(); 162 String weightStr = parser.getAttributeValue(null, "weight"); 163 int weight = weightStr == null ? 400 : Integer.parseInt(weightStr); 164 boolean isItalic = "italic".equals(parser.getAttributeValue(null, "style")); 165 StringBuilder filename = new StringBuilder(); 166 while (parser.next() != XmlPullParser.END_TAG) { 167 if (parser.getEventType() == XmlPullParser.TEXT) { 168 filename.append(parser.getText()); 169 } 170 if (parser.getEventType() != XmlPullParser.START_TAG) continue; 171 String tag = parser.getName(); 172 if (tag.equals("axis")) { 173 axes.add(readAxis(parser)); 174 } else { 175 skip(parser); 176 } 177 } 178 String fullFilename = "/system/fonts/" + 179 FILENAME_WHITESPACE_PATTERN.matcher(filename).replaceAll(""); 180 return new FontConfig.Font(fullFilename, index, axes, weight, isItalic); 181 } 182 183 /** The 'tag' attribute value is read as four character values between U+0020 and U+007E 184 * inclusive. 185 */ 186 private static final Pattern TAG_PATTERN = Pattern.compile("[\\x20-\\x7E]{4}"); 187 188 /** The 'styleValue' attribute has an optional leading '-', followed by '<digits>', 189 * '<digits>.<digits>', or '.<digits>' where '<digits>' is one or more of [0-9]. 190 */ 191 private static final Pattern STYLE_VALUE_PATTERN = 192 Pattern.compile("-?(([0-9]+(\\.[0-9]+)?)|(\\.[0-9]+))"); 193 194 private static FontConfig.Axis readAxis(XmlPullParser parser) 195 throws XmlPullParserException, IOException { 196 int tag = 0; 197 String tagStr = parser.getAttributeValue(null, "tag"); 198 if (tagStr != null && TAG_PATTERN.matcher(tagStr).matches()) { 199 tag = makeTag(tagStr.charAt(0), tagStr.charAt(1), tagStr.charAt(2), tagStr.charAt(3)); 200 } else { 201 throw new XmlPullParserException("Invalid tag attribute value.", parser, null); 202 } 203 204 float styleValue = 0; 205 String styleValueStr = parser.getAttributeValue(null, "stylevalue"); 206 if (styleValueStr != null && STYLE_VALUE_PATTERN.matcher(styleValueStr).matches()) { 207 styleValue = Float.parseFloat(styleValueStr); 208 } else { 209 throw new XmlPullParserException("Invalid styleValue attribute value.", parser, null); 210 } 211 212 skip(parser); // axis tag is empty, ignore any contents and consume end tag 213 return new FontConfig.Axis(tag, styleValue); 214 } 215 216 private static FontConfig.Alias readAlias(XmlPullParser parser) 217 throws XmlPullParserException, IOException { 218 String name = parser.getAttributeValue(null, "name"); 219 String toName = parser.getAttributeValue(null, "to"); 220 String weightStr = parser.getAttributeValue(null, "weight"); 221 int weight; 222 if (weightStr == null) { 223 weight = 400; 224 } else { 225 weight = Integer.parseInt(weightStr); 226 } 227 skip(parser); // alias tag is empty, ignore any contents and consume end tag 228 return new FontConfig.Alias(name, toName, weight); 229 } 230 231 private static void skip(XmlPullParser parser) throws XmlPullParserException, IOException { 232 int depth = 1; 233 while (depth > 0) { 234 switch (parser.next()) { 235 case XmlPullParser.START_TAG: 236 depth++; 237 break; 238 case XmlPullParser.END_TAG: 239 depth--; 240 break; 241 } 242 } 243 } 244} 245