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 */
53struct 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 */
69template <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
90namespace lmpParser {
91
92void 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
120void fontFileNameHandler(void* data, const char* s, int len) {
121    FamilyData* familyData = (FamilyData*) data;
122    familyData->currentFontInfo->fFileName.set(s, len);
123}
124
125void 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
144FontFamily* 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
159void 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
206bool 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
215bool desiredWeight(int weight) {
216    return (weight == 400 || weight == 700);
217}
218
219int 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}
231void 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
244void 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
252void 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
268void 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
283namespace 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 */
289static 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 */
316static 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 */
368static 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 */
414static 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 */
442static 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
472static 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 */
488static 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
535static 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 */
571void 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
584void SkFontConfigParser::GetTestFontFamilies(SkTDArray<FontFamily*> &fontFamilies,
585                                             const char* testMainConfigFile,
586                                             const char* testFallbackConfigFile) {
587    parseConfigFile(testMainConfigFile, fontFamilies);
588
589    SkTDArray<FontFamily*> fallbackFonts;
590    if (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
601SkLanguage 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}
613