1/*
2 * Copyright 2011 The Android Open Source Project
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include "SkFontConfigParser_android.h"
9#include "SkTDArray.h"
10#include "SkTypeface.h"
11
12#include <expat.h>
13#include <stdio.h>
14#include <sys/system_properties.h>
15
16#define SYSTEM_FONTS_FILE "/system/etc/system_fonts.xml"
17#define FALLBACK_FONTS_FILE "/system/etc/fallback_fonts.xml"
18#define VENDOR_FONTS_FILE "/vendor/etc/fallback_fonts.xml"
19
20// These defines are used to determine the kind of tag that we're currently
21// populating with data. We only care about the sibling tags nameset and fileset
22// for now.
23#define NO_TAG 0
24#define NAMESET_TAG 1
25#define FILESET_TAG 2
26
27/**
28 * The FamilyData structure is passed around by the parser so that each handler
29 * can read these variables that are relevant to the current parsing.
30 */
31struct FamilyData {
32    FamilyData(XML_Parser *parserRef, SkTDArray<FontFamily*> &familiesRef) :
33        parser(parserRef),
34        families(familiesRef),
35        currentFamily(NULL),
36        currentFontInfo(NULL),
37        currentTag(NO_TAG) {};
38
39    XML_Parser *parser;                // The expat parser doing the work
40    SkTDArray<FontFamily*> &families;  // The array that each family is put into as it is parsed
41    FontFamily *currentFamily;         // The current family being created
42    FontFileInfo *currentFontInfo;     // The current fontInfo being created
43    int currentTag;                    // A flag to indicate whether we're in nameset/fileset tags
44};
45
46/**
47 * Handler for arbitrary text. This is used to parse the text inside each name
48 * or file tag. The resulting strings are put into the fNames or FontFileInfo arrays.
49 */
50static void textHandler(void *data, const char *s, int len) {
51    FamilyData *familyData = (FamilyData*) data;
52    // Make sure we're in the right state to store this name information
53    if (familyData->currentFamily &&
54            (familyData->currentTag == NAMESET_TAG || familyData->currentTag == FILESET_TAG)) {
55        switch (familyData->currentTag) {
56        case NAMESET_TAG:
57            familyData->currentFamily->fNames.push_back().set(s, len);
58            break;
59        case FILESET_TAG:
60            if (familyData->currentFontInfo) {
61                familyData->currentFontInfo->fFileName.set(s, len);
62            }
63            break;
64        default:
65            // Noop - don't care about any text that's not in the Fonts or Names list
66            break;
67        }
68    }
69}
70
71/**
72 * Handler for font files. This processes the attributes for language and
73 * variants then lets textHandler handle the actual file name
74 */
75static void fontFileElementHandler(FamilyData *familyData, const char **attributes) {
76
77    FontFileInfo& newFileInfo = familyData->currentFamily->fFontFiles.push_back();
78    if (attributes) {
79        int currentAttributeIndex = 0;
80        while (attributes[currentAttributeIndex]) {
81            const char* attributeName = attributes[currentAttributeIndex];
82            const char* attributeValue = attributes[currentAttributeIndex+1];
83            int nameLength = strlen(attributeName);
84            int valueLength = strlen(attributeValue);
85            if (strncmp(attributeName, "variant", nameLength) == 0) {
86                if (strncmp(attributeValue, "elegant", valueLength) == 0) {
87                    newFileInfo.fPaintOptions.setFontVariant(SkPaintOptionsAndroid::kElegant_Variant);
88                } else if (strncmp(attributeValue, "compact", valueLength) == 0) {
89                    newFileInfo.fPaintOptions.setFontVariant(SkPaintOptionsAndroid::kCompact_Variant);
90                }
91            } else if (strncmp(attributeName, "lang", nameLength) == 0) {
92                newFileInfo.fPaintOptions.setLanguage(attributeValue);
93            }
94            //each element is a pair of attributeName/attributeValue string pairs
95            currentAttributeIndex += 2;
96        }
97    }
98    familyData->currentFontInfo = &newFileInfo;
99    XML_SetCharacterDataHandler(*familyData->parser, textHandler);
100}
101
102/**
103 * Handler for the start of a tag. The only tags we expect are family, nameset,
104 * fileset, name, and file.
105 */
106static void startElementHandler(void *data, const char *tag, const char **atts) {
107    FamilyData *familyData = (FamilyData*) data;
108    int len = strlen(tag);
109    if (strncmp(tag, "family", len)== 0) {
110        familyData->currentFamily = new FontFamily();
111        familyData->currentFamily->order = -1;
112        // The Family tag has an optional "order" attribute with an integer value >= 0
113        // If this attribute does not exist, the default value is -1
114        for (int i = 0; atts[i] != NULL; i += 2) {
115            const char* valueString = atts[i+1];
116            int value;
117            int len = sscanf(valueString, "%d", &value);
118            if (len > 0) {
119                familyData->currentFamily->order = value;
120            }
121        }
122    } else if (len == 7 && strncmp(tag, "nameset", len) == 0) {
123        familyData->currentTag = NAMESET_TAG;
124    } else if (len == 7 && strncmp(tag, "fileset", len) == 0) {
125        familyData->currentTag = FILESET_TAG;
126    } else if (strncmp(tag, "name", len) == 0 && familyData->currentTag == NAMESET_TAG) {
127        // If it's a Name, parse the text inside
128        XML_SetCharacterDataHandler(*familyData->parser, textHandler);
129    } else if (strncmp(tag, "file", len) == 0 && familyData->currentTag == FILESET_TAG) {
130        // If it's a file, parse the attributes, then parse the text inside
131        fontFileElementHandler(familyData, atts);
132    }
133}
134
135/**
136 * Handler for the end of tags. We only care about family, nameset, fileset,
137 * name, and file.
138 */
139static void endElementHandler(void *data, const char *tag) {
140    FamilyData *familyData = (FamilyData*) data;
141    int len = strlen(tag);
142    if (strncmp(tag, "family", len)== 0) {
143        // Done parsing a Family - store the created currentFamily in the families array
144        *familyData->families.append() = familyData->currentFamily;
145        familyData->currentFamily = NULL;
146    } else if (len == 7 && strncmp(tag, "nameset", len) == 0) {
147        familyData->currentTag = NO_TAG;
148    } else if (len == 7 && strncmp(tag, "fileset", len) == 0) {
149        familyData->currentTag = NO_TAG;
150    } else if ((strncmp(tag, "name", len) == 0 && familyData->currentTag == NAMESET_TAG) ||
151            (strncmp(tag, "file", len) == 0 && familyData->currentTag == FILESET_TAG)) {
152        // Disable the arbitrary text handler installed to load Name data
153        XML_SetCharacterDataHandler(*familyData->parser, NULL);
154    }
155}
156
157/**
158 * This function parses the given filename and stores the results in the given
159 * families array.
160 */
161static void parseConfigFile(const char *filename, SkTDArray<FontFamily*> &families) {
162
163    FILE* file = NULL;
164
165#if !defined(SK_BUILD_FOR_ANDROID_FRAMEWORK)
166    // if we are using a version of Android prior to Android 4.2 (JellyBean MR1
167    // at API Level 17) then we need to look for files with a different suffix.
168    char sdkVersion[PROP_VALUE_MAX];
169    __system_property_get("ro.build.version.sdk", sdkVersion);
170    const int sdkVersionInt = atoi(sdkVersion);
171
172    if (0 != *sdkVersion && sdkVersionInt < 17) {
173        SkString basename;
174        SkString updatedFilename;
175        SkString locale = SkFontConfigParser::GetLocale();
176
177        basename.set(filename);
178        // Remove the .xml suffix. We'll add it back in a moment.
179        if (basename.endsWith(".xml")) {
180            basename.resize(basename.size()-4);
181        }
182        // Try first with language and region
183        updatedFilename.printf("%s-%s.xml", basename.c_str(), locale.c_str());
184        file = fopen(updatedFilename.c_str(), "r");
185        if (!file) {
186            // If not found, try next with just language
187            updatedFilename.printf("%s-%.2s.xml", basename.c_str(), locale.c_str());
188            file = fopen(updatedFilename.c_str(), "r");
189        }
190    }
191#endif
192
193    if (NULL == file) {
194        file = fopen(filename, "r");
195    }
196
197    // Some of the files we attempt to parse (in particular, /vendor/etc/fallback_fonts.xml)
198    // are optional - failure here is okay because one of these optional files may not exist.
199    if (NULL == file) {
200        return;
201    }
202
203    XML_Parser parser = XML_ParserCreate(NULL);
204    FamilyData *familyData = new FamilyData(&parser, families);
205    XML_SetUserData(parser, familyData);
206    XML_SetElementHandler(parser, startElementHandler, endElementHandler);
207
208    char buffer[512];
209    bool done = false;
210    while (!done) {
211        fgets(buffer, sizeof(buffer), file);
212        int len = strlen(buffer);
213        if (feof(file) != 0) {
214            done = true;
215        }
216        XML_Parse(parser, buffer, len, done);
217    }
218    XML_ParserFree(parser);
219    fclose(file);
220}
221
222static void getSystemFontFamilies(SkTDArray<FontFamily*> &fontFamilies) {
223    parseConfigFile(SYSTEM_FONTS_FILE, fontFamilies);
224}
225
226static void getFallbackFontFamilies(SkTDArray<FontFamily*> &fallbackFonts) {
227    SkTDArray<FontFamily*> vendorFonts;
228    parseConfigFile(FALLBACK_FONTS_FILE, fallbackFonts);
229    parseConfigFile(VENDOR_FONTS_FILE, vendorFonts);
230
231    // This loop inserts the vendor fallback fonts in the correct order in the
232    // overall fallbacks list.
233    int currentOrder = -1;
234    for (int i = 0; i < vendorFonts.count(); ++i) {
235        FontFamily* family = vendorFonts[i];
236        int order = family->order;
237        if (order < 0) {
238            if (currentOrder < 0) {
239                // Default case - just add it to the end of the fallback list
240                *fallbackFonts.append() = family;
241            } else {
242                // no order specified on this font, but we're incrementing the order
243                // based on an earlier order insertion request
244                *fallbackFonts.insert(currentOrder++) = family;
245            }
246        } else {
247            // Add the font into the fallback list in the specified order. Set
248            // currentOrder for correct placement of other fonts in the vendor list.
249            *fallbackFonts.insert(order) = family;
250            currentOrder = order + 1;
251        }
252    }
253}
254
255/**
256 * Loads data on font families from various expected configuration files. The
257 * resulting data is returned in the given fontFamilies array.
258 */
259void SkFontConfigParser::GetFontFamilies(SkTDArray<FontFamily*> &fontFamilies) {
260
261    getSystemFontFamilies(fontFamilies);
262
263    // Append all the fallback fonts to system fonts
264    SkTDArray<FontFamily*> fallbackFonts;
265    getFallbackFontFamilies(fallbackFonts);
266    for (int i = 0; i < fallbackFonts.count(); ++i) {
267        fallbackFonts[i]->fIsFallbackFont = true;
268        *fontFamilies.append() = fallbackFonts[i];
269    }
270}
271
272void SkFontConfigParser::GetTestFontFamilies(SkTDArray<FontFamily*> &fontFamilies,
273                                             const char* testMainConfigFile,
274                                             const char* testFallbackConfigFile) {
275    parseConfigFile(testMainConfigFile, fontFamilies);
276
277    SkTDArray<FontFamily*> fallbackFonts;
278    parseConfigFile(testFallbackConfigFile, fallbackFonts);
279
280    // Append all fallback fonts to system fonts
281    for (int i = 0; i < fallbackFonts.count(); ++i) {
282        fallbackFonts[i]->fIsFallbackFont = true;
283        *fontFamilies.append() = fallbackFonts[i];
284    }
285}
286
287/**
288 * Read the persistent locale.
289 */
290SkString SkFontConfigParser::GetLocale()
291{
292    char propLang[PROP_VALUE_MAX], propRegn[PROP_VALUE_MAX];
293    __system_property_get("persist.sys.language", propLang);
294    __system_property_get("persist.sys.country", propRegn);
295
296    if (*propLang == 0 && *propRegn == 0) {
297        /* Set to ro properties, default is en_US */
298        __system_property_get("ro.product.locale.language", propLang);
299        __system_property_get("ro.product.locale.region", propRegn);
300        if (*propLang == 0 && *propRegn == 0) {
301            strcpy(propLang, "en");
302            strcpy(propRegn, "US");
303        }
304    }
305
306    SkString locale(6);
307    char* localeCStr = locale.writable_str();
308
309    strncpy(localeCStr, propLang, 2);
310    localeCStr[2] = '-';
311    strncpy(&localeCStr[3], propRegn, 2);
312    localeCStr[5] = '\0';
313
314    return locale;
315}
316