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.compat.annotation.UnsupportedAppUsage;
20 import android.graphics.fonts.FontVariationAxis;
21 import android.text.FontConfig;
22 import android.util.Xml;
23 
24 import org.xmlpull.v1.XmlPullParser;
25 import org.xmlpull.v1.XmlPullParserException;
26 
27 import java.io.IOException;
28 import java.io.InputStream;
29 import java.util.ArrayList;
30 import java.util.List;
31 import java.util.regex.Pattern;
32 
33 /**
34  * Parser for font config files.
35  *
36  * @hide
37  */
38 public class FontListParser {
39 
40     /* Parse fallback list (no names) */
41     @UnsupportedAppUsage
parse(InputStream in)42     public static FontConfig parse(InputStream in) throws XmlPullParserException, IOException {
43         return parse(in, "/system/fonts");
44     }
45 
46     /**
47      * Parse the fonts.xml
48      */
parse(InputStream in, String fontDir)49     public static FontConfig parse(InputStream in, String fontDir)
50             throws XmlPullParserException, IOException {
51         try {
52             XmlPullParser parser = Xml.newPullParser();
53             parser.setInput(in, null);
54             parser.nextTag();
55             return readFamilies(parser, fontDir);
56         } finally {
57             in.close();
58         }
59     }
60 
readFamilies(XmlPullParser parser, String fontDir)61     private static FontConfig readFamilies(XmlPullParser parser, String fontDir)
62             throws XmlPullParserException, IOException {
63         List<FontConfig.Family> families = new ArrayList<>();
64         List<FontConfig.Alias> aliases = new ArrayList<>();
65 
66         parser.require(XmlPullParser.START_TAG, null, "familyset");
67         while (parser.next() != XmlPullParser.END_TAG) {
68             if (parser.getEventType() != XmlPullParser.START_TAG) continue;
69             String tag = parser.getName();
70             if (tag.equals("family")) {
71                 families.add(readFamily(parser, fontDir));
72             } else if (tag.equals("alias")) {
73                 aliases.add(readAlias(parser));
74             } else {
75                 skip(parser);
76             }
77         }
78         return new FontConfig(families.toArray(new FontConfig.Family[families.size()]),
79                 aliases.toArray(new FontConfig.Alias[aliases.size()]));
80     }
81 
82     /**
83      * Reads a family element
84      */
readFamily(XmlPullParser parser, String fontDir)85     public static FontConfig.Family readFamily(XmlPullParser parser, String fontDir)
86             throws XmlPullParserException, IOException {
87         final String name = parser.getAttributeValue(null, "name");
88         final String lang = parser.getAttributeValue("", "lang");
89         final String variant = parser.getAttributeValue(null, "variant");
90         final List<FontConfig.Font> fonts = new ArrayList<FontConfig.Font>();
91         while (parser.next() != XmlPullParser.END_TAG) {
92             if (parser.getEventType() != XmlPullParser.START_TAG) continue;
93             final String tag = parser.getName();
94             if (tag.equals("font")) {
95                 fonts.add(readFont(parser, fontDir));
96             } else {
97                 skip(parser);
98             }
99         }
100         int intVariant = FontConfig.Family.VARIANT_DEFAULT;
101         if (variant != null) {
102             if (variant.equals("compact")) {
103                 intVariant = FontConfig.Family.VARIANT_COMPACT;
104             } else if (variant.equals("elegant")) {
105                 intVariant = FontConfig.Family.VARIANT_ELEGANT;
106             }
107         }
108         return new FontConfig.Family(name, fonts.toArray(new FontConfig.Font[fonts.size()]), lang,
109                 intVariant);
110     }
111 
112     /** Matches leading and trailing XML whitespace. */
113     private static final Pattern FILENAME_WHITESPACE_PATTERN =
114             Pattern.compile("^[ \\n\\r\\t]+|[ \\n\\r\\t]+$");
115 
readFont(XmlPullParser parser, String fontDir)116     private static FontConfig.Font readFont(XmlPullParser parser, String fontDir)
117             throws XmlPullParserException, IOException {
118         String indexStr = parser.getAttributeValue(null, "index");
119         int index = indexStr == null ? 0 : Integer.parseInt(indexStr);
120         List<FontVariationAxis> axes = new ArrayList<FontVariationAxis>();
121         String weightStr = parser.getAttributeValue(null, "weight");
122         int weight = weightStr == null ? 400 : Integer.parseInt(weightStr);
123         boolean isItalic = "italic".equals(parser.getAttributeValue(null, "style"));
124         String fallbackFor = parser.getAttributeValue(null, "fallbackFor");
125         StringBuilder filename = new StringBuilder();
126         while (parser.next() != XmlPullParser.END_TAG) {
127             if (parser.getEventType() == XmlPullParser.TEXT) {
128                 filename.append(parser.getText());
129             }
130             if (parser.getEventType() != XmlPullParser.START_TAG) continue;
131             String tag = parser.getName();
132             if (tag.equals("axis")) {
133                 axes.add(readAxis(parser));
134             } else {
135                 skip(parser);
136             }
137         }
138         String sanitizedName = FILENAME_WHITESPACE_PATTERN.matcher(filename).replaceAll("");
139         return new FontConfig.Font(fontDir + sanitizedName, index, axes.toArray(
140                 new FontVariationAxis[axes.size()]), weight, isItalic, fallbackFor);
141     }
142 
readAxis(XmlPullParser parser)143     private static FontVariationAxis readAxis(XmlPullParser parser)
144             throws XmlPullParserException, IOException {
145         String tagStr = parser.getAttributeValue(null, "tag");
146         String styleValueStr = parser.getAttributeValue(null, "stylevalue");
147         skip(parser);  // axis tag is empty, ignore any contents and consume end tag
148         return new FontVariationAxis(tagStr, Float.parseFloat(styleValueStr));
149     }
150 
151     /**
152      * Reads alias elements
153      */
readAlias(XmlPullParser parser)154     public static FontConfig.Alias readAlias(XmlPullParser parser)
155             throws XmlPullParserException, IOException {
156         String name = parser.getAttributeValue(null, "name");
157         String toName = parser.getAttributeValue(null, "to");
158         String weightStr = parser.getAttributeValue(null, "weight");
159         int weight;
160         if (weightStr == null) {
161             weight = 400;
162         } else {
163             weight = Integer.parseInt(weightStr);
164         }
165         skip(parser);  // alias tag is empty, ignore any contents and consume end tag
166         return new FontConfig.Alias(name, toName, weight);
167     }
168 
169     /**
170      * Skip until next element
171      */
skip(XmlPullParser parser)172     public static void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
173         int depth = 1;
174         while (depth > 0) {
175             switch (parser.next()) {
176             case XmlPullParser.START_TAG:
177                 depth++;
178                 break;
179             case XmlPullParser.END_TAG:
180                 depth--;
181                 break;
182             }
183         }
184     }
185 }
186