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