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