FontListParser.java revision 3002ab6729237dd608a8640794c52960b3ec54d0
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 0 and 255 inclusive. */ 232 private static final Pattern TAG_PATTERN = Pattern.compile("[\\x00-\\xFF]{4}"); 233 234 /** The 'styleValue' attribute has an optional leading '-', followed by '<digits>', 235 * '<digits>.<digits>', or '.<digits>' where '<digits>' is one or more of [0-9]. 236 */ 237 private static final Pattern STYLE_VALUE_PATTERN = 238 Pattern.compile("-?(([0-9]+(\\.[0-9]+)?)|(\\.[0-9]+))"); 239 240 private static Axis readAxis(XmlPullParser parser) 241 throws XmlPullParserException, IOException { 242 int tag = 0; 243 String tagStr = parser.getAttributeValue(null, "tag"); 244 if (tagStr != null && TAG_PATTERN.matcher(tagStr).matches()) { 245 tag = makeTag(tagStr.charAt(0), tagStr.charAt(1), tagStr.charAt(2), tagStr.charAt(3)); 246 } else { 247 throw new XmlPullParserException("Invalid tag attribute value.", parser, null); 248 } 249 250 float styleValue = 0; 251 String styleValueStr = parser.getAttributeValue(null, "stylevalue"); 252 if (styleValueStr != null && STYLE_VALUE_PATTERN.matcher(styleValueStr).matches()) { 253 styleValue = Float.parseFloat(styleValueStr); 254 } else { 255 throw new XmlPullParserException("Invalid styleValue attribute value.", parser, null); 256 } 257 258 skip(parser); // axis tag is empty, ignore any contents and consume end tag 259 return new Axis(tag, styleValue); 260 } 261 262 private static Alias readAlias(XmlPullParser parser) 263 throws XmlPullParserException, IOException { 264 Alias alias = new Alias(); 265 alias.name = parser.getAttributeValue(null, "name"); 266 alias.toName = parser.getAttributeValue(null, "to"); 267 String weightStr = parser.getAttributeValue(null, "weight"); 268 if (weightStr == null) { 269 alias.weight = 400; 270 } else { 271 alias.weight = Integer.parseInt(weightStr); 272 } 273 skip(parser); // alias tag is empty, ignore any contents and consume end tag 274 return alias; 275 } 276 277 private static void skip(XmlPullParser parser) throws XmlPullParserException, IOException { 278 int depth = 1; 279 while (depth > 0) { 280 switch (parser.next()) { 281 case XmlPullParser.START_TAG: 282 depth++; 283 break; 284 case XmlPullParser.END_TAG: 285 depth--; 286 break; 287 } 288 } 289 } 290} 291