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.util.Xml; 20 21 import org.xmlpull.v1.XmlPullParser; 22 import org.xmlpull.v1.XmlPullParserException; 23 24 import java.io.IOException; 25 import java.io.InputStream; 26 import java.util.ArrayList; 27 import java.util.List; 28 import java.util.regex.Pattern; 29 30 /** 31 * Parser for font config files. 32 * 33 * @hide 34 */ 35 public class FontListParser { 36 37 public static class Config { Config()38 Config() { 39 families = new ArrayList<Family>(); 40 aliases = new ArrayList<Alias>(); 41 } 42 public List<Family> families; 43 public List<Alias> aliases; 44 } 45 46 public static class Axis { Axis(int tag, float styleValue)47 Axis(int tag, float styleValue) { 48 this.tag = tag; 49 this.styleValue = styleValue; 50 } 51 public final int tag; 52 public final float styleValue; 53 } 54 55 public static class Font { Font(String fontName, int ttcIndex, List<Axis> axes, int weight, boolean isItalic)56 Font(String fontName, int ttcIndex, List<Axis> axes, int weight, boolean isItalic) { 57 this.fontName = fontName; 58 this.ttcIndex = ttcIndex; 59 this.axes = axes; 60 this.weight = weight; 61 this.isItalic = isItalic; 62 } 63 public String fontName; 64 public int ttcIndex; 65 public final List<Axis> axes; 66 public int weight; 67 public boolean isItalic; 68 } 69 70 public static class Alias { 71 public String name; 72 public String toName; 73 public int weight; 74 } 75 76 public static class Family { Family(String name, List<Font> fonts, String lang, String variant)77 public Family(String name, List<Font> fonts, String lang, String variant) { 78 this.name = name; 79 this.fonts = fonts; 80 this.lang = lang; 81 this.variant = variant; 82 } 83 84 public String name; 85 public List<Font> fonts; 86 public String lang; 87 public String variant; 88 } 89 90 /* Parse fallback list (no names) */ parse(InputStream in)91 public static Config parse(InputStream in) throws XmlPullParserException, IOException { 92 try { 93 XmlPullParser parser = Xml.newPullParser(); 94 parser.setInput(in, null); 95 parser.nextTag(); 96 return readFamilies(parser); 97 } finally { 98 in.close(); 99 } 100 } 101 readFamilies(XmlPullParser parser)102 private static Config readFamilies(XmlPullParser parser) 103 throws XmlPullParserException, IOException { 104 Config config = new Config(); 105 parser.require(XmlPullParser.START_TAG, null, "familyset"); 106 while (parser.next() != XmlPullParser.END_TAG) { 107 if (parser.getEventType() != XmlPullParser.START_TAG) continue; 108 String tag = parser.getName(); 109 if (tag.equals("family")) { 110 config.families.add(readFamily(parser)); 111 } else if (tag.equals("alias")) { 112 config.aliases.add(readAlias(parser)); 113 } else { 114 skip(parser); 115 } 116 } 117 return config; 118 } 119 readFamily(XmlPullParser parser)120 private static Family readFamily(XmlPullParser parser) 121 throws XmlPullParserException, IOException { 122 String name = parser.getAttributeValue(null, "name"); 123 String lang = parser.getAttributeValue(null, "lang"); 124 String variant = parser.getAttributeValue(null, "variant"); 125 List<Font> fonts = new ArrayList<Font>(); 126 while (parser.next() != XmlPullParser.END_TAG) { 127 if (parser.getEventType() != XmlPullParser.START_TAG) continue; 128 String tag = parser.getName(); 129 if (tag.equals("font")) { 130 fonts.add(readFont(parser)); 131 } else { 132 skip(parser); 133 } 134 } 135 return new Family(name, fonts, lang, variant); 136 } 137 138 /** Matches leading and trailing XML whitespace. */ 139 private static final Pattern FILENAME_WHITESPACE_PATTERN = 140 Pattern.compile("^[ \\n\\r\\t]+|[ \\n\\r\\t]+$"); 141 readFont(XmlPullParser parser)142 private static Font readFont(XmlPullParser parser) 143 throws XmlPullParserException, IOException { 144 String indexStr = parser.getAttributeValue(null, "index"); 145 int index = indexStr == null ? 0 : Integer.parseInt(indexStr); 146 List<Axis> axes = new ArrayList<Axis>(); 147 String weightStr = parser.getAttributeValue(null, "weight"); 148 int weight = weightStr == null ? 400 : Integer.parseInt(weightStr); 149 boolean isItalic = "italic".equals(parser.getAttributeValue(null, "style")); 150 StringBuilder filename = new StringBuilder(); 151 while (parser.next() != XmlPullParser.END_TAG) { 152 if (parser.getEventType() == XmlPullParser.TEXT) { 153 filename.append(parser.getText()); 154 } 155 if (parser.getEventType() != XmlPullParser.START_TAG) continue; 156 String tag = parser.getName(); 157 if (tag.equals("axis")) { 158 axes.add(readAxis(parser)); 159 } else { 160 skip(parser); 161 } 162 } 163 String fullFilename = "/system/fonts/" + 164 FILENAME_WHITESPACE_PATTERN.matcher(filename).replaceAll(""); 165 return new Font(fullFilename, index, axes, weight, isItalic); 166 } 167 168 /** The 'tag' attribute value is read as four character values between 0 and 255 inclusive. */ 169 private static final Pattern TAG_PATTERN = Pattern.compile("[\\x00-\\xFF]{4}"); 170 171 /** The 'styleValue' attribute has an optional leading '-', followed by '<digits>', 172 * '<digits>.<digits>', or '.<digits>' where '<digits>' is one or more of [0-9]. 173 */ 174 private static final Pattern STYLE_VALUE_PATTERN = 175 Pattern.compile("-?(([0-9]+(\\.[0-9]+)?)|(\\.[0-9]+))"); 176 readAxis(XmlPullParser parser)177 private static Axis readAxis(XmlPullParser parser) 178 throws XmlPullParserException, IOException { 179 int tag = 0; 180 String tagStr = parser.getAttributeValue(null, "tag"); 181 if (tagStr != null && TAG_PATTERN.matcher(tagStr).matches()) { 182 tag = (tagStr.charAt(0) << 24) + 183 (tagStr.charAt(1) << 16) + 184 (tagStr.charAt(2) << 8) + 185 (tagStr.charAt(3) ); 186 } else { 187 throw new XmlPullParserException("Invalid tag attribute value.", parser, null); 188 } 189 190 float styleValue = 0; 191 String styleValueStr = parser.getAttributeValue(null, "stylevalue"); 192 if (styleValueStr != null && STYLE_VALUE_PATTERN.matcher(styleValueStr).matches()) { 193 styleValue = Float.parseFloat(styleValueStr); 194 } else { 195 throw new XmlPullParserException("Invalid styleValue attribute value.", parser, null); 196 } 197 198 skip(parser); // axis tag is empty, ignore any contents and consume end tag 199 return new Axis(tag, styleValue); 200 } 201 readAlias(XmlPullParser parser)202 private static Alias readAlias(XmlPullParser parser) 203 throws XmlPullParserException, IOException { 204 Alias alias = new Alias(); 205 alias.name = parser.getAttributeValue(null, "name"); 206 alias.toName = parser.getAttributeValue(null, "to"); 207 String weightStr = parser.getAttributeValue(null, "weight"); 208 if (weightStr == null) { 209 alias.weight = 400; 210 } else { 211 alias.weight = Integer.parseInt(weightStr); 212 } 213 skip(parser); // alias tag is empty, ignore any contents and consume end tag 214 return alias; 215 } 216 skip(XmlPullParser parser)217 private static void skip(XmlPullParser parser) throws XmlPullParserException, IOException { 218 int depth = 1; 219 while (depth > 0) { 220 switch (parser.next()) { 221 case XmlPullParser.START_TAG: 222 depth++; 223 break; 224 case XmlPullParser.END_TAG: 225 depth--; 226 break; 227 } 228 } 229 } 230 } 231