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