Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / third_party / skia / src / ports / SkFontConfigParser_android.cpp
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 "SkTSearch.h"
11 #include "SkTypeface.h"
12
13 #include <expat.h>
14 #include <dirent.h>
15 #include <stdio.h>
16
17 #include <limits>
18
19
20
21 // From Android version LMP onwards, all font files collapse into
22 // /system/etc/fonts.xml. Instead of trying to detect which version
23 // we're on, try to open fonts.xml; if that fails, fall back to the
24 // older filename.
25 #define LMP_SYSTEM_FONTS_FILE "/system/etc/fonts.xml"
26 #define OLD_SYSTEM_FONTS_FILE "/system/etc/system_fonts.xml"
27 #define FALLBACK_FONTS_FILE "/system/etc/fallback_fonts.xml"
28 #define VENDOR_FONTS_FILE "/vendor/etc/fallback_fonts.xml"
29
30 #define LOCALE_FALLBACK_FONTS_SYSTEM_DIR "/system/etc"
31 #define LOCALE_FALLBACK_FONTS_VENDOR_DIR "/vendor/etc"
32 #define LOCALE_FALLBACK_FONTS_PREFIX "fallback_fonts-"
33 #define LOCALE_FALLBACK_FONTS_SUFFIX ".xml"
34
35 /**
36  * This file contains TWO parsers: one for JB and earlier (system_fonts.xml /
37  * fallback_fonts.xml), one for LMP and later (fonts.xml).
38  * We start with the JB parser, and if we detect a <familyset> tag with
39  * version 21 or higher we switch to the LMP parser.
40  */
41
42 // These defines are used to determine the kind of tag that we're currently
43 // populating with data. We only care about the sibling tags nameset and fileset
44 // for now.
45 #define NO_TAG 0
46 #define NAMESET_TAG 1
47 #define FILESET_TAG 2
48
49 /**
50  * The FamilyData structure is passed around by the parser so that each handler
51  * can read these variables that are relevant to the current parsing.
52  */
53 struct FamilyData {
54     FamilyData(XML_Parser* parserRef, SkTDArray<FontFamily*> &familiesRef) :
55         parser(parserRef),
56         families(familiesRef),
57         currentFamily(NULL),
58         currentFontInfo(NULL),
59         currentTag(NO_TAG) {};
60
61     XML_Parser* parser;                // The expat parser doing the work
62     SkTDArray<FontFamily*> &families;  // The array that each family is put into as it is parsed
63     FontFamily* currentFamily;         // The current family being created
64     FontFileInfo* currentFontInfo;     // The current fontInfo being created
65     int currentTag;                    // A flag to indicate whether we're in nameset/fileset tags
66 };
67
68 /** http://www.w3.org/TR/html-markup/datatypes.html#common.data.integer.non-negative-def */
69 template <typename T> static bool parseNonNegativeInteger(const char* s, T* value) {
70     SK_COMPILE_ASSERT(std::numeric_limits<T>::is_integer, T_must_be_integer);
71     const T nMax = std::numeric_limits<T>::max() / 10;
72     const T dMax = std::numeric_limits<T>::max() - (nMax * 10);
73     T n = 0;
74     for (; *s; ++s) {
75         // Check if digit
76         if (*s < '0' || '9' < *s) {
77             return false;
78         }
79         int d = *s - '0';
80         // Check for overflow
81         if (n > nMax || (n == nMax && d > dMax)) {
82             return false;
83         }
84         n = (n * 10) + d;
85     }
86     *value = n;
87     return true;
88 }
89
90 namespace lmpParser {
91
92 void familyElementHandler(FontFamily* family, const char** attributes) {
93     // A non-fallback <family> tag must have a canonical name attribute.
94     // A fallback <family> tag has no name, and may have lang and variant
95     // attributes.
96     family->fIsFallbackFont = true;
97     for (size_t i = 0; attributes[i] != NULL &&
98                        attributes[i+1] != NULL; i += 2) {
99         const char* name = attributes[i];
100         const char* value = attributes[i+1];
101         size_t nameLen = strlen(name);
102         size_t valueLen = strlen(value);
103         if (nameLen == 4 && !strncmp("name", name, nameLen)) {
104             SkAutoAsciiToLC tolc(value);
105             family->fNames.push_back().set(tolc.lc());
106             family->fIsFallbackFont = false;
107         } else if (nameLen == 4 && !strncmp("lang", name, nameLen)) {
108             family->fLanguage = SkLanguage (value);
109         } else if (nameLen == 7 && !strncmp("variant", name, nameLen)) {
110             // Value should be either elegant or compact.
111             if (valueLen == 7 && !strncmp("elegant", value, valueLen)) {
112                 family->fVariant = kElegant_FontVariant;
113             } else if (valueLen == 7 && !strncmp("compact", value, valueLen)) {
114                 family->fVariant = kCompact_FontVariant;
115             }
116         }
117     }
118 }
119
120 void fontFileNameHandler(void* data, const char* s, int len) {
121     FamilyData* familyData = (FamilyData*) data;
122     familyData->currentFontInfo->fFileName.set(s, len);
123 }
124
125 void fontElementHandler(XML_Parser* parser, FontFileInfo* file, const char** attributes) {
126     // A <font> should have weight (integer) and style (normal, italic) attributes.
127     // NOTE: we ignore the style.
128     // The element should contain a filename.
129     for (size_t i = 0; attributes[i] != NULL &&
130                        attributes[i+1] != NULL; i += 2) {
131         const char* name = attributes[i];
132         const char* value = attributes[i+1];
133         size_t nameLen = strlen(name);
134         if (nameLen == 6 && !strncmp("weight", name, nameLen)) {
135             if (!parseNonNegativeInteger(value, &file->fWeight)) {
136                 SkDebugf("---- Font weight %s (INVALID)", value);
137                 file->fWeight = 0;
138             }
139         }
140     }
141     XML_SetCharacterDataHandler(*parser, fontFileNameHandler);
142 }
143
144 FontFamily* findFamily(FamilyData* familyData, const char* familyName) {
145     size_t nameLen = strlen(familyName);
146     for (int i = 0; i < familyData->families.count(); i++) {
147         FontFamily* candidate = familyData->families[i];
148         for (int j = 0; j < candidate->fNames.count(); j++) {
149             if (!strncmp(candidate->fNames[j].c_str(), familyName, nameLen) &&
150                 nameLen == strlen(candidate->fNames[j].c_str())) {
151                 return candidate;
152             }
153         }
154     }
155
156     return NULL;
157 }
158
159 void aliasElementHandler(FamilyData* familyData, const char** attributes) {
160     // An <alias> must have name and to attributes.
161     //   It may have weight (integer).
162     // If it *does not* have a weight, it is a variant name for a <family>.
163     // If it *does* have a weight, it names the <font>(s) of a specific weight
164     //   from a <family>.
165
166     SkString aliasName;
167     SkString to;
168     int weight = 0;
169     for (size_t i = 0; attributes[i] != NULL &&
170                        attributes[i+1] != NULL; i += 2) {
171         const char* name = attributes[i];
172         const char* value = attributes[i+1];
173         size_t nameLen = strlen(name);
174         if (nameLen == 4 && !strncmp("name", name, nameLen)) {
175             SkAutoAsciiToLC tolc(value);
176             aliasName.set(tolc.lc());
177         } else if (nameLen == 2 && !strncmp("to", name, nameLen)) {
178             to.set(value);
179         } else if (nameLen == 6 && !strncmp("weight", name, nameLen)) {
180             parseNonNegativeInteger(value, &weight);
181         }
182     }
183
184     // Assumes that the named family is already declared
185     FontFamily* targetFamily = findFamily(familyData, to.c_str());
186     if (!targetFamily) {
187         SkDebugf("---- Font alias target %s (NOT FOUND)", to.c_str());
188         return;
189     }
190
191     if (weight) {
192         FontFamily* family = new FontFamily();
193         family->fNames.push_back().set(aliasName);
194
195         for (int i = 0; i < targetFamily->fFonts.count(); i++) {
196             if (targetFamily->fFonts[i].fWeight == weight) {
197                 family->fFonts.push_back(targetFamily->fFonts[i]);
198             }
199         }
200         *familyData->families.append() = family;
201     } else {
202         targetFamily->fNames.push_back().set(aliasName);
203     }
204 }
205
206 bool findWeight400(FontFamily* family) {
207     for (int i = 0; i < family->fFonts.count(); i++) {
208         if (family->fFonts[i].fWeight == 400) {
209             return true;
210         }
211     }
212     return false;
213 }
214
215 bool desiredWeight(int weight) {
216     return (weight == 400 || weight == 700);
217 }
218
219 int countDesiredWeight(FontFamily* family) {
220     int count = 0;
221     for (int i = 0; i < family->fFonts.count(); i++) {
222         if (desiredWeight(family->fFonts[i].fWeight)) {
223             count++;
224         }
225     }
226     return count;
227 }
228
229 // To meet Skia's expectations, any family that contains weight=400
230 // fonts should *only* contain {400,700}
231 void purgeUndesiredWeights(FontFamily* family) {
232     int count = countDesiredWeight(family);
233     for (int i = 1, j = 0; i < family->fFonts.count(); i++) {
234         if (desiredWeight(family->fFonts[j].fWeight)) {
235             j++;
236         }
237         if ((i != j) && desiredWeight(family->fFonts[i].fWeight)) {
238             family->fFonts[j] = family->fFonts[i];
239         }
240     }
241     family->fFonts.resize_back(count);
242 }
243
244 void familysetElementEndHandler(FamilyData* familyData) {
245     for (int i = 0; i < familyData->families.count(); i++) {
246         if (findWeight400(familyData->families[i])) {
247             purgeUndesiredWeights(familyData->families[i]);
248         }
249     }
250 }
251
252 void startElementHandler(void* data, const char* tag,
253                          const char** attributes) {
254     FamilyData* familyData = (FamilyData*) data;
255     size_t len = strlen(tag);
256     if (len == 6 && !strncmp(tag, "family", len)) {
257         familyData->currentFamily = new FontFamily();
258         familyElementHandler(familyData->currentFamily, attributes);
259     } else if (len == 4 && !strncmp(tag, "font", len)) {
260         FontFileInfo* file = &familyData->currentFamily->fFonts.push_back();
261         familyData->currentFontInfo = file;
262         fontElementHandler(familyData->parser, file, attributes);
263     } else if (len == 5 && !strncmp(tag, "alias", len)) {
264         aliasElementHandler(familyData, attributes);
265     }
266 }
267
268 void endElementHandler(void* data, const char* tag) {
269     FamilyData* familyData = (FamilyData*) data;
270     size_t len = strlen(tag);
271     if (len == 9 && strncmp(tag, "familyset", len) == 0) {
272         familysetElementEndHandler(familyData);
273     } else if (len == 6 && strncmp(tag, "family", len) == 0) {
274         *familyData->families.append() = familyData->currentFamily;
275         familyData->currentFamily = NULL;
276     } else if (len == 4 && !strncmp(tag, "font", len)) {
277         XML_SetCharacterDataHandler(*familyData->parser, NULL);
278     }
279 }
280
281 } // lmpParser
282
283 namespace jbParser {
284
285 /**
286  * Handler for arbitrary text. This is used to parse the text inside each name
287  * or file tag. The resulting strings are put into the fNames or FontFileInfo arrays.
288  */
289 static void textHandler(void* data, const char* s, int len) {
290     FamilyData* familyData = (FamilyData*) data;
291     // Make sure we're in the right state to store this name information
292     if (familyData->currentFamily &&
293             (familyData->currentTag == NAMESET_TAG || familyData->currentTag == FILESET_TAG)) {
294         switch (familyData->currentTag) {
295         case NAMESET_TAG: {
296             SkAutoAsciiToLC tolc(s, len);
297             familyData->currentFamily->fNames.push_back().set(tolc.lc(), len);
298             break;
299         }
300         case FILESET_TAG:
301             if (familyData->currentFontInfo) {
302                 familyData->currentFontInfo->fFileName.set(s, len);
303             }
304             break;
305         default:
306             // Noop - don't care about any text that's not in the Fonts or Names list
307             break;
308         }
309     }
310 }
311
312 /**
313  * Handler for font files. This processes the attributes for language and
314  * variants then lets textHandler handle the actual file name
315  */
316 static void fontFileElementHandler(FamilyData* familyData, const char** attributes) {
317     FontFileInfo& newFileInfo = familyData->currentFamily->fFonts.push_back();
318     if (attributes) {
319         size_t currentAttributeIndex = 0;
320         while (attributes[currentAttributeIndex] &&
321                attributes[currentAttributeIndex + 1]) {
322             const char* attributeName = attributes[currentAttributeIndex];
323             const char* attributeValue = attributes[currentAttributeIndex+1];
324             size_t nameLength = strlen(attributeName);
325             size_t valueLength = strlen(attributeValue);
326             if (nameLength == 7 && strncmp(attributeName, "variant", nameLength) == 0) {
327                 const FontVariant prevVariant = familyData->currentFamily->fVariant;
328                 if (valueLength == 7 && strncmp(attributeValue, "elegant", valueLength) == 0) {
329                     familyData->currentFamily->fVariant = kElegant_FontVariant;
330                 } else if (valueLength == 7 &&
331                            strncmp(attributeValue, "compact", valueLength) == 0) {
332                     familyData->currentFamily->fVariant = kCompact_FontVariant;
333                 }
334                 if (familyData->currentFamily->fFonts.count() > 1 &&
335                         familyData->currentFamily->fVariant != prevVariant) {
336                     SkDebugf("Every font file within a family must have identical variants");
337                     sk_throw();
338                 }
339
340             } else if (nameLength == 4 && strncmp(attributeName, "lang", nameLength) == 0) {
341                 SkLanguage prevLang = familyData->currentFamily->fLanguage;
342                 familyData->currentFamily->fLanguage = SkLanguage(attributeValue);
343                 if (familyData->currentFamily->fFonts.count() > 1 &&
344                         familyData->currentFamily->fLanguage != prevLang) {
345                     SkDebugf("Every font file within a family must have identical languages");
346                     sk_throw();
347                 }
348             } else if (nameLength == 5 && strncmp(attributeName, "index", nameLength) == 0) {
349                 int value;
350                 if (parseNonNegativeInteger(attributeValue, &value)) {
351                     newFileInfo.fIndex = value;
352                 } else {
353                     SkDebugf("---- SystemFonts index=%s (INVALID)", attributeValue);
354                 }
355             }
356             //each element is a pair of attributeName/attributeValue string pairs
357             currentAttributeIndex += 2;
358         }
359     }
360     familyData->currentFontInfo = &newFileInfo;
361     XML_SetCharacterDataHandler(*familyData->parser, textHandler);
362 }
363
364 /**
365  * Handler for the start of a tag. The only tags we expect are familyset, family,
366  * nameset, fileset, name, and file.
367  */
368 static void startElementHandler(void* data, const char* tag, const char** atts) {
369     FamilyData* familyData = (FamilyData*) data;
370     size_t len = strlen(tag);
371     if (len == 9 && strncmp(tag, "familyset", len) == 0) {
372         // The familyset tag has an optional "version" attribute with an integer value >= 0
373         for (size_t i = 0; atts[i] != NULL &&
374                            atts[i+1] != NULL; i += 2) {
375             size_t nameLen = strlen(atts[i]);
376             if (nameLen == 7 && strncmp(atts[i], "version", nameLen)) continue;
377             const char* valueString = atts[i+1];
378             int version;
379             if (parseNonNegativeInteger(valueString, &version) && (version >= 21)) {
380                 XML_SetElementHandler(*familyData->parser,
381                                       lmpParser::startElementHandler,
382                                       lmpParser::endElementHandler);
383             }
384         }
385     } else if (len == 6 && strncmp(tag, "family", len) == 0) {
386         familyData->currentFamily = new FontFamily();
387         // The Family tag has an optional "order" attribute with an integer value >= 0
388         // If this attribute does not exist, the default value is -1
389         for (size_t i = 0; atts[i] != NULL &&
390                            atts[i+1] != NULL; i += 2) {
391             const char* valueString = atts[i+1];
392             int value;
393             if (parseNonNegativeInteger(valueString, &value)) {
394                 familyData->currentFamily->fOrder = value;
395             }
396         }
397     } else if (len == 7 && strncmp(tag, "nameset", len) == 0) {
398         familyData->currentTag = NAMESET_TAG;
399     } else if (len == 7 && strncmp(tag, "fileset", len) == 0) {
400         familyData->currentTag = FILESET_TAG;
401     } else if (len == 4 && strncmp(tag, "name", len) == 0 && familyData->currentTag == NAMESET_TAG) {
402         // If it's a Name, parse the text inside
403         XML_SetCharacterDataHandler(*familyData->parser, textHandler);
404     } else if (len == 4 && strncmp(tag, "file", len) == 0 && familyData->currentTag == FILESET_TAG) {
405         // If it's a file, parse the attributes, then parse the text inside
406         fontFileElementHandler(familyData, atts);
407     }
408 }
409
410 /**
411  * Handler for the end of tags. We only care about family, nameset, fileset,
412  * name, and file.
413  */
414 static void endElementHandler(void* data, const char* tag) {
415     FamilyData* familyData = (FamilyData*) data;
416     size_t len = strlen(tag);
417     if (len == 6 && strncmp(tag, "family", len)== 0) {
418         // Done parsing a Family - store the created currentFamily in the families array
419         *familyData->families.append() = familyData->currentFamily;
420         familyData->currentFamily = NULL;
421     } else if (len == 7 && strncmp(tag, "nameset", len) == 0) {
422         familyData->currentTag = NO_TAG;
423     } else if (len == 7 && strncmp(tag, "fileset", len) == 0) {
424         familyData->currentTag = NO_TAG;
425     } else if ((len == 4 &&
426                 strncmp(tag, "name", len) == 0 &&
427                 familyData->currentTag == NAMESET_TAG) ||
428                (len == 4 &&
429                 strncmp(tag, "file", len) == 0 &&
430                 familyData->currentTag == FILESET_TAG)) {
431         // Disable the arbitrary text handler installed to load Name data
432         XML_SetCharacterDataHandler(*familyData->parser, NULL);
433     }
434 }
435
436 } // namespace jbParser
437
438 /**
439  * This function parses the given filename and stores the results in the given
440  * families array.
441  */
442 static void parseConfigFile(const char* filename, SkTDArray<FontFamily*> &families) {
443
444     FILE* file = fopen(filename, "r");
445
446     // Some of the files we attempt to parse (in particular, /vendor/etc/fallback_fonts.xml)
447     // are optional - failure here is okay because one of these optional files may not exist.
448     if (NULL == file) {
449         return;
450     }
451
452     XML_Parser parser = XML_ParserCreate(NULL);
453     FamilyData* familyData = new FamilyData(&parser, families);
454     XML_SetUserData(parser, familyData);
455     // Start parsing oldschool; switch these in flight if we detect a newer version of the file.
456     XML_SetElementHandler(parser, jbParser::startElementHandler, jbParser::endElementHandler);
457
458     char buffer[512];
459     bool done = false;
460     while (!done) {
461         fgets(buffer, sizeof(buffer), file);
462         size_t len = strlen(buffer);
463         if (feof(file) != 0) {
464             done = true;
465         }
466         XML_Parse(parser, buffer, len, done);
467     }
468     XML_ParserFree(parser);
469     fclose(file);
470 }
471
472 static void getSystemFontFamilies(SkTDArray<FontFamily*> &fontFamilies) {
473     int initialCount = fontFamilies.count();
474     parseConfigFile(LMP_SYSTEM_FONTS_FILE, fontFamilies);
475
476     if (initialCount == fontFamilies.count()) {
477         parseConfigFile(OLD_SYSTEM_FONTS_FILE, fontFamilies);
478     }
479 }
480
481 /**
482  * In some versions of Android prior to Android 4.2 (JellyBean MR1 at API
483  * Level 17) the fallback fonts for certain locales were encoded in their own
484  * XML files with a suffix that identified the locale.  We search the provided
485  * directory for those files,add all of their entries to the fallback chain, and
486  * include the locale as part of each entry.
487  */
488 static void getFallbackFontFamiliesForLocale(SkTDArray<FontFamily*> &fallbackFonts, const char* dir) {
489 #if defined(SK_BUILD_FOR_ANDROID_FRAMEWORK)
490     // The framework is beyond Android 4.2 and can therefore skip this function
491     return;
492 #endif
493
494     DIR* fontDirectory = opendir(dir);
495     if (fontDirectory != NULL){
496         struct dirent* dirEntry = readdir(fontDirectory);
497         while (dirEntry) {
498
499             // The size of both the prefix, suffix, and a minimum valid language code
500             static const size_t minSize = strlen(LOCALE_FALLBACK_FONTS_PREFIX) +
501                                           strlen(LOCALE_FALLBACK_FONTS_SUFFIX) + 2;
502
503             SkString fileName(dirEntry->d_name);
504             if (fileName.size() >= minSize &&
505                     fileName.startsWith(LOCALE_FALLBACK_FONTS_PREFIX) &&
506                     fileName.endsWith(LOCALE_FALLBACK_FONTS_SUFFIX)) {
507
508                 static const size_t fixedLen = strlen(LOCALE_FALLBACK_FONTS_PREFIX) -
509                                                strlen(LOCALE_FALLBACK_FONTS_SUFFIX);
510
511                 SkString locale(fileName.c_str() - strlen(LOCALE_FALLBACK_FONTS_PREFIX),
512                                 fileName.size() - fixedLen);
513
514                 SkString absoluteFilename;
515                 absoluteFilename.printf("%s/%s", dir, fileName.c_str());
516
517                 SkTDArray<FontFamily*> langSpecificFonts;
518                 parseConfigFile(absoluteFilename.c_str(), langSpecificFonts);
519
520                 for (int i = 0; i < langSpecificFonts.count(); ++i) {
521                     FontFamily* family = langSpecificFonts[i];
522                     family->fLanguage = SkLanguage(locale);
523                     *fallbackFonts.append() = family;
524                 }
525             }
526
527             // proceed to the next entry in the directory
528             dirEntry = readdir(fontDirectory);
529         }
530         // cleanup the directory reference
531         closedir(fontDirectory);
532     }
533 }
534
535 static void getFallbackFontFamilies(SkTDArray<FontFamily*> &fallbackFonts) {
536     SkTDArray<FontFamily*> vendorFonts;
537     parseConfigFile(FALLBACK_FONTS_FILE, fallbackFonts);
538     parseConfigFile(VENDOR_FONTS_FILE, vendorFonts);
539
540     getFallbackFontFamiliesForLocale(fallbackFonts, LOCALE_FALLBACK_FONTS_SYSTEM_DIR);
541     getFallbackFontFamiliesForLocale(vendorFonts, LOCALE_FALLBACK_FONTS_VENDOR_DIR);
542
543     // This loop inserts the vendor fallback fonts in the correct order in the
544     // overall fallbacks list.
545     int currentOrder = -1;
546     for (int i = 0; i < vendorFonts.count(); ++i) {
547         FontFamily* family = vendorFonts[i];
548         int order = family->fOrder;
549         if (order < 0) {
550             if (currentOrder < 0) {
551                 // Default case - just add it to the end of the fallback list
552                 *fallbackFonts.append() = family;
553             } else {
554                 // no order specified on this font, but we're incrementing the order
555                 // based on an earlier order insertion request
556                 *fallbackFonts.insert(currentOrder++) = family;
557             }
558         } else {
559             // Add the font into the fallback list in the specified order. Set
560             // currentOrder for correct placement of other fonts in the vendor list.
561             *fallbackFonts.insert(order) = family;
562             currentOrder = order + 1;
563         }
564     }
565 }
566
567 /**
568  * Loads data on font families from various expected configuration files. The
569  * resulting data is returned in the given fontFamilies array.
570  */
571 void SkFontConfigParser::GetFontFamilies(SkTDArray<FontFamily*> &fontFamilies) {
572
573     getSystemFontFamilies(fontFamilies);
574
575     // Append all the fallback fonts to system fonts
576     SkTDArray<FontFamily*> fallbackFonts;
577     getFallbackFontFamilies(fallbackFonts);
578     for (int i = 0; i < fallbackFonts.count(); ++i) {
579         fallbackFonts[i]->fIsFallbackFont = true;
580         *fontFamilies.append() = fallbackFonts[i];
581     }
582 }
583
584 void SkFontConfigParser::GetTestFontFamilies(SkTDArray<FontFamily*> &fontFamilies,
585                                              const char* testMainConfigFile,
586                                              const char* testFallbackConfigFile) {
587     parseConfigFile(testMainConfigFile, fontFamilies);
588
589     SkTDArray<FontFamily*> fallbackFonts;
590     if (NULL != testFallbackConfigFile) {
591         parseConfigFile(testFallbackConfigFile, fallbackFonts);
592     }
593
594     // Append all fallback fonts to system fonts
595     for (int i = 0; i < fallbackFonts.count(); ++i) {
596         fallbackFonts[i]->fIsFallbackFont = true;
597         *fontFamilies.append() = fallbackFonts[i];
598     }
599 }
600
601 SkLanguage SkLanguage::getParent() const {
602     SkASSERT(!fTag.isEmpty());
603     const char* tag = fTag.c_str();
604
605     // strip off the rightmost "-.*"
606     const char* parentTagEnd = strrchr(tag, '-');
607     if (parentTagEnd == NULL) {
608         return SkLanguage();
609     }
610     size_t parentTagLen = parentTagEnd - tag;
611     return SkLanguage(tag, parentTagLen);
612 }