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