| Index: src/ports/SkFontConfigParser_android.cpp | 
| diff --git a/src/ports/SkFontConfigParser_android.cpp b/src/ports/SkFontConfigParser_android.cpp | 
| index 3f0ebbfc6b8f3f2e1547db559d26b778d15d4524..73e4c4773896afa4aaf2f7d59975da5e696557d5 100644 | 
| --- a/src/ports/SkFontConfigParser_android.cpp | 
| +++ b/src/ports/SkFontConfigParser_android.cpp | 
| @@ -19,10 +19,6 @@ | 
| #include <limits> | 
| #include <stdlib.h> | 
|  | 
| -// From Android version LMP onwards, all font files collapse into | 
| -// /system/etc/fonts.xml. Instead of trying to detect which version | 
| -// we're on, try to open fonts.xml; if that fails, fall back to the | 
| -// older filename. | 
| #define LMP_SYSTEM_FONTS_FILE "/system/etc/fonts.xml" | 
| #define OLD_SYSTEM_FONTS_FILE "/system/etc/system_fonts.xml" | 
| #define FALLBACK_FONTS_FILE "/system/etc/fallback_fonts.xml" | 
| @@ -38,48 +34,81 @@ | 
| #endif | 
|  | 
| /** | 
| - * This file contains TWO parsers: one for JB and earlier (system_fonts.xml / | 
| - * fallback_fonts.xml), one for LMP and later (fonts.xml). | 
| - * We start with the JB parser, and if we detect a <familyset> tag with | 
| - * version 21 or higher we switch to the LMP parser. | 
| + * This file contains TWO 'familyset' handlers: | 
| + * One for JB and earlier which works with | 
| + *   /system/etc/system_fonts.xml | 
| + *   /system/etc/fallback_fonts.xml | 
| + *   /vendor/etc/fallback_fonts.xml | 
| + *   /system/etc/fallback_fonts-XX.xml | 
| + *   /vendor/etc/fallback_fonts-XX.xml | 
| + * and the other for LMP and later which works with | 
| + *   /system/etc/fonts.xml | 
| + * | 
| + * If the 'familyset' 'version' attribute is 21 or higher the LMP parser is used, otherwise the JB. | 
| */ | 
|  | 
| -/** Used to track which tag is currently populating with data. | 
| - *  Only nameset and fileset are of interest for now. | 
| - */ | 
| -enum CurrentTag { | 
| -    kNo_CurrentTag, | 
| -    kNameSet_CurrentTag, | 
| -    kFileSet_CurrentTag | 
| +struct FamilyData; | 
| + | 
| +struct TagHandler { | 
| +    /** Called at the start tag. | 
| +     *  Called immediately after the parent tag retuns this handler from a call to 'tag'. | 
| +     *  Allows setting up for handling the tag content and processing attributes. | 
| +     *  If NULL, will not be called. | 
| +     */ | 
| +    void (*start)(FamilyData* data, const char* tag, const char** attributes); | 
| + | 
| +    /** Called at the end tag. | 
| +     *  Allows post-processing of any accumulated information. | 
| +     *  This will be the last call made in relation to the current tag. | 
| +     *  If NULL, will not be called. | 
| +     */ | 
| +    void (*end)(FamilyData* data, const char* tag); | 
| + | 
| +    /** Called when a nested tag is encountered. | 
| +     *  This is responsible for determining how to handle the tag. | 
| +     *  If the tag is not recognized, return NULL to skip the tag. | 
| +     *  If NULL, all nested tags will be skipped. | 
| +     */ | 
| +    const TagHandler* (*tag)(FamilyData* data, const char* tag, const char** attributes); | 
| + | 
| +    /** The character handler for this tag. | 
| +     *  This is only active for character data contained directly in this tag (not sub-tags). | 
| +     *  The first parameter will be castable to a FamilyData*. | 
| +     *  If NULL, any character data in this tag will be ignored. | 
| +     */ | 
| +    XML_CharacterDataHandler chars; | 
| }; | 
|  | 
| -/** | 
| - * The FamilyData structure is passed around by the parser so that each handler | 
| - * can read these variables that are relevant to the current parsing. | 
| - */ | 
| +/** Represents the current parsing state. */ | 
| struct FamilyData { | 
| FamilyData(XML_Parser parser, SkTDArray<FontFamily*>& families, | 
| -               const SkString& basePath, bool isFallback, const char* filename) | 
| +               const SkString& basePath, bool isFallback, const char* filename, | 
| +               const TagHandler* topLevelHandler) | 
| : fParser(parser) | 
| , fFamilies(families) | 
| , fCurrentFamily(NULL) | 
| , fCurrentFontInfo(NULL) | 
| -        , fCurrentTag(kNo_CurrentTag) | 
| , fVersion(0) | 
| , fBasePath(basePath) | 
| , fIsFallback(isFallback) | 
| , fFilename(filename) | 
| +        , fDepth(1) | 
| +        , fSkip(0) | 
| +        , fHandler(&topLevelHandler, 1) | 
| { }; | 
|  | 
| XML_Parser fParser;                       // The expat parser doing the work, owned by caller | 
| SkTDArray<FontFamily*>& fFamilies;        // The array to append families, owned by caller | 
| SkAutoTDelete<FontFamily> fCurrentFamily; // The family being created, owned by this | 
| FontFileInfo* fCurrentFontInfo;           // The fontInfo being created, owned by fCurrentFamily | 
| -    CurrentTag fCurrentTag;                   // The kind of tag currently being parsed. | 
| int fVersion;                             // The version of the file parsed. | 
| const SkString& fBasePath;                // The current base path. | 
| const bool fIsFallback;                   // Indicates the file being parsed is a fallback file | 
| const char* fFilename;                    // The name of the file currently being parsed. | 
| + | 
| +    int fDepth;                               // The current element depth of the parse. | 
| +    int fSkip;                                // The depth to stop skipping, 0 if not skipping. | 
| +    SkTDArray<const TagHandler*> fHandler;    // The stack of current tag handlers. | 
| }; | 
|  | 
| /** Parses a null terminated string into an integer type, checking for overflow. | 
| @@ -124,70 +153,107 @@ static bool memeq(const char* s1, const char* s2, size_t n1, size_t n2) { | 
| XML_GetCurrentColumnNumber(self->fParser), \ | 
| ##__VA_ARGS__); | 
|  | 
| -namespace lmpParser { | 
| +static bool is_whitespace(char c) { | 
| +    return c == ' ' || c == '\n'|| c == '\r' || c == '\t'; | 
| +} | 
|  | 
| -static void family_element_handler(FamilyData* self, const char** attributes) { | 
| -    // A <family> element without a 'name' (string) attribute is a fallback font. | 
| -    // The element may have 'lang' (string) and 'variant' ("elegant", "compact") attributes. | 
| -    // The element should contain <font> elements. | 
| -    FontFamily* family = new FontFamily(self->fBasePath, true); | 
| -    self->fCurrentFamily.reset(family); | 
| -    for (size_t i = 0; ATTS_NON_NULL(attributes, i); i += 2) { | 
| -        const char* name = attributes[i]; | 
| -        const char* value = attributes[i+1]; | 
| -        size_t nameLen = strlen(name); | 
| -        size_t valueLen = strlen(value); | 
| -        if (MEMEQ("name", name, nameLen)) { | 
| -            SkAutoAsciiToLC tolc(value); | 
| -            family->fNames.push_back().set(tolc.lc()); | 
| -            family->fIsFallbackFont = false; | 
| -        } else if (MEMEQ("lang", name, nameLen)) { | 
| -            family->fLanguage = SkLanguage(value, valueLen); | 
| -        } else if (MEMEQ("variant", name, nameLen)) { | 
| -            if (MEMEQ("elegant", value, valueLen)) { | 
| -                family->fVariant = kElegant_FontVariant; | 
| -            } else if (MEMEQ("compact", value, valueLen)) { | 
| -                family->fVariant = kCompact_FontVariant; | 
| -            } | 
| -        } | 
| +static void trim_string(SkString* s) { | 
| +    char* str = s->writable_str(); | 
| +    const char* start = str;  // start is inclusive | 
| +    const char* end = start + s->size();  // end is exclusive | 
| +    while (is_whitespace(*start)) { ++start; } | 
| +    if (start != end) { | 
| +        --end;  // make end inclusive | 
| +        while (is_whitespace(*end)) { --end; } | 
| +        ++end;  // make end exclusive | 
| } | 
| +    size_t len = end - start; | 
| +    memmove(str, start, len); | 
| +    s->resize(len); | 
| } | 
|  | 
| -static void XMLCALL font_file_name_handler(void* data, const char* s, int len) { | 
| -    FamilyData* self = static_cast<FamilyData*>(data); | 
| -    self->fCurrentFontInfo->fFileName.append(s, len); | 
| -} | 
| +namespace lmpParser { | 
|  | 
| -static void font_element_handler(FamilyData* self, const char** attributes) { | 
| -    // A <font> element should be contained in a <family> element. | 
| -    // The element may have 'weight' (non-negative integer), 'style' ("normal", "italic"), | 
| -    // and 'index' (non-negative integer) attributes. | 
| -    // The element should contain a filename. | 
| -    FontFileInfo& file = self->fCurrentFamily->fFonts.push_back(); | 
| -    self->fCurrentFontInfo = &file; | 
| -    for (size_t i = 0; ATTS_NON_NULL(attributes, i); i += 2) { | 
| -        const char* name = attributes[i]; | 
| -        const char* value = attributes[i+1]; | 
| -        size_t nameLen = strlen(name); | 
| -        if (MEMEQ("weight", name, nameLen)) { | 
| -            if (!parse_non_negative_integer(value, &file.fWeight)) { | 
| -                SK_FONTCONFIGPARSER_WARNING("'%s' is an invalid weight", value); | 
| +static const TagHandler fontHandler = { | 
| +    /*start*/[](FamilyData* self, const char* tag, const char** attributes) { | 
| +        // 'weight' (non-negative integer) [default 0] | 
| +        // 'style' ("normal", "italic") [default "auto"] | 
| +        // 'index' (non-negative integer) [default 0] | 
| +        // The character data should be a filename. | 
| +        FontFileInfo& file = self->fCurrentFamily->fFonts.push_back(); | 
| +        self->fCurrentFontInfo = &file; | 
| +        for (size_t i = 0; ATTS_NON_NULL(attributes, i); i += 2) { | 
| +            const char* name = attributes[i]; | 
| +            const char* value = attributes[i+1]; | 
| +            size_t nameLen = strlen(name); | 
| +            if (MEMEQ("weight", name, nameLen)) { | 
| +                if (!parse_non_negative_integer(value, &file.fWeight)) { | 
| +                    SK_FONTCONFIGPARSER_WARNING("'%s' is an invalid weight", value); | 
| +                } | 
| +            } else if (MEMEQ("style", name, nameLen)) { | 
| +                size_t valueLen = strlen(value); | 
| +                if (MEMEQ("normal", value, valueLen)) { | 
| +                    file.fStyle = FontFileInfo::Style::kNormal; | 
| +                } else if (MEMEQ("italic", value, valueLen)) { | 
| +                    file.fStyle = FontFileInfo::Style::kItalic; | 
| +                } | 
| +            } else if (MEMEQ("index", name, nameLen)) { | 
| +                if (!parse_non_negative_integer(value, &file.fIndex)) { | 
| +                    SK_FONTCONFIGPARSER_WARNING("'%s' is an invalid index", value); | 
| +                } | 
| } | 
| -        } else if (MEMEQ("style", name, nameLen)) { | 
| +        } | 
| +    }, | 
| +    /*end*/[](FamilyData* self, const char* tag) { | 
| +        trim_string(&self->fCurrentFontInfo->fFileName); | 
| +    }, | 
| +    /*tag*/NULL, | 
| +    /*chars*/[](void* data, const char* s, int len) { | 
| +        FamilyData* self = static_cast<FamilyData*>(data); | 
| +        self->fCurrentFontInfo->fFileName.append(s, len); | 
| +    } | 
| +}; | 
| + | 
| +static const TagHandler familyHandler = { | 
| +    /*start*/[](FamilyData* self, const char* tag, const char** attributes) { | 
| +        // 'name' (string) [optional] | 
| +        // 'lang' (string) [default ""] | 
| +        // 'variant' ("elegant", "compact") [default "default"] | 
| +        // If there is no name, this is a fallback only font. | 
| +        FontFamily* family = new FontFamily(self->fBasePath, true); | 
| +        self->fCurrentFamily.reset(family); | 
| +        for (size_t i = 0; ATTS_NON_NULL(attributes, i); i += 2) { | 
| +            const char* name = attributes[i]; | 
| +            const char* value = attributes[i+1]; | 
| +            size_t nameLen = strlen(name); | 
| size_t valueLen = strlen(value); | 
| -            if (MEMEQ("normal", value, valueLen)) { | 
| -                file.fStyle = FontFileInfo::Style::kNormal; | 
| -            } else if (MEMEQ("italic", value, valueLen)) { | 
| -                file.fStyle = FontFileInfo::Style::kItalic; | 
| -            } | 
| -        } else if (MEMEQ("index", name, nameLen)) { | 
| -            if (!parse_non_negative_integer(value, &file.fIndex)) { | 
| -                SK_FONTCONFIGPARSER_WARNING("'%s' is an invalid index", value); | 
| +            if (MEMEQ("name", name, nameLen)) { | 
| +                SkAutoAsciiToLC tolc(value); | 
| +                family->fNames.push_back().set(tolc.lc()); | 
| +                family->fIsFallbackFont = false; | 
| +            } else if (MEMEQ("lang", name, nameLen)) { | 
| +                family->fLanguage = SkLanguage(value, valueLen); | 
| +            } else if (MEMEQ("variant", name, nameLen)) { | 
| +                if (MEMEQ("elegant", value, valueLen)) { | 
| +                    family->fVariant = kElegant_FontVariant; | 
| +                } else if (MEMEQ("compact", value, valueLen)) { | 
| +                    family->fVariant = kCompact_FontVariant; | 
| +                } | 
| } | 
| } | 
| -    } | 
| -    XML_SetCharacterDataHandler(self->fParser, font_file_name_handler); | 
| -} | 
| +    }, | 
| +    /*end*/[](FamilyData* self, const char* tag) { | 
| +        *self->fFamilies.append() = self->fCurrentFamily.detach(); | 
| +    }, | 
| +    /*tag*/[](FamilyData* self, const char* tag, const char** attributes) -> const TagHandler* { | 
| +        size_t len = strlen(tag); | 
| +        if (MEMEQ("font", tag, len)) { | 
| +            return &fontHandler; | 
| +        } | 
| +        return NULL; | 
| +    }, | 
| +    /*chars*/NULL, | 
| +}; | 
|  | 
| static FontFamily* find_family(FamilyData* self, const SkString& familyName) { | 
| for (int i = 0; i < self->fFamilies.count(); i++) { | 
| @@ -201,242 +267,279 @@ static FontFamily* find_family(FamilyData* self, const SkString& familyName) { | 
| return NULL; | 
| } | 
|  | 
| -static void alias_element_handler(FamilyData* self, const char** attributes) { | 
| -    // A <alias> element must have 'name' (string) and 'to' (string) attributes. | 
| -    // The element may also have a 'weight' (non-negative integer) attribute. | 
| -    // The 'name' introduces a new family name. | 
| -    // The 'to' specifies which (previous) <family> to alias. | 
| -    // If it *does not* have a weight, 'name' is an alias for the entire 'to' <family>. | 
| -    // If it *does* have a weight, 'name' is a new family consisting of the <font>(s) with 'weight' | 
| -    // from the 'to' <family>. | 
| - | 
| -    SkString aliasName; | 
| -    SkString to; | 
| -    int weight = 0; | 
| -    for (size_t i = 0; ATTS_NON_NULL(attributes, i); i += 2) { | 
| -        const char* name = attributes[i]; | 
| -        const char* value = attributes[i+1]; | 
| -        size_t nameLen = strlen(name); | 
| -        if (MEMEQ("name", name, nameLen)) { | 
| -            SkAutoAsciiToLC tolc(value); | 
| -            aliasName.set(tolc.lc()); | 
| -        } else if (MEMEQ("to", name, nameLen)) { | 
| -            to.set(value); | 
| -        } else if (MEMEQ("weight", name, nameLen)) { | 
| -            if (!parse_non_negative_integer(value, &weight)) { | 
| -                SK_FONTCONFIGPARSER_WARNING("'%s' is an invalid weight", value); | 
| +static const TagHandler aliasHandler = { | 
| +    /*start*/[](FamilyData* self, const char* tag, const char** attributes) { | 
| +        // 'name' (string) introduces a new family name. | 
| +        // 'to' (string) specifies which (previous) family to alias | 
| +        // 'weight' (non-negative integer) [optional] | 
| +        // If it *does not* have a weight, 'name' is an alias for the entire 'to' family. | 
| +        // If it *does* have a weight, 'name' is a new family consisting of | 
| +        // the font(s) with 'weight' from the 'to' family. | 
| + | 
| +        SkString aliasName; | 
| +        SkString to; | 
| +        int weight = 0; | 
| +        for (size_t i = 0; ATTS_NON_NULL(attributes, i); i += 2) { | 
| +            const char* name = attributes[i]; | 
| +            const char* value = attributes[i+1]; | 
| +            size_t nameLen = strlen(name); | 
| +            if (MEMEQ("name", name, nameLen)) { | 
| +                SkAutoAsciiToLC tolc(value); | 
| +                aliasName.set(tolc.lc()); | 
| +            } else if (MEMEQ("to", name, nameLen)) { | 
| +                to.set(value); | 
| +            } else if (MEMEQ("weight", name, nameLen)) { | 
| +                if (!parse_non_negative_integer(value, &weight)) { | 
| +                    SK_FONTCONFIGPARSER_WARNING("'%s' is an invalid weight", value); | 
| +                } | 
| } | 
| } | 
| -    } | 
|  | 
| -    // Assumes that the named family is already declared | 
| -    FontFamily* targetFamily = find_family(self, to); | 
| -    if (!targetFamily) { | 
| -        SK_FONTCONFIGPARSER_WARNING("'%s' alias target not found", to.c_str()); | 
| -        return; | 
| -    } | 
| +        // Assumes that the named family is already declared | 
| +        FontFamily* targetFamily = find_family(self, to); | 
| +        if (!targetFamily) { | 
| +            SK_FONTCONFIGPARSER_WARNING("'%s' alias target not found", to.c_str()); | 
| +            return; | 
| +        } | 
|  | 
| -    if (weight) { | 
| -        FontFamily* family = new FontFamily(targetFamily->fBasePath, self->fIsFallback); | 
| -        family->fNames.push_back().set(aliasName); | 
| +        if (weight) { | 
| +            FontFamily* family = new FontFamily(targetFamily->fBasePath, self->fIsFallback); | 
| +            family->fNames.push_back().set(aliasName); | 
|  | 
| -        for (int i = 0; i < targetFamily->fFonts.count(); i++) { | 
| -            if (targetFamily->fFonts[i].fWeight == weight) { | 
| -                family->fFonts.push_back(targetFamily->fFonts[i]); | 
| +            for (int i = 0; i < targetFamily->fFonts.count(); i++) { | 
| +                if (targetFamily->fFonts[i].fWeight == weight) { | 
| +                    family->fFonts.push_back(targetFamily->fFonts[i]); | 
| +                } | 
| } | 
| +            *self->fFamilies.append() = family; | 
| +        } else { | 
| +            targetFamily->fNames.push_back().set(aliasName); | 
| } | 
| -        *self->fFamilies.append() = family; | 
| -    } else { | 
| -        targetFamily->fNames.push_back().set(aliasName); | 
| -    } | 
| -} | 
| - | 
| -static void XMLCALL start_element_handler(void* data, const char* tag, const char** attributes) { | 
| -    FamilyData* self = static_cast<FamilyData*>(data); | 
| -    size_t len = strlen(tag); | 
| -    if (MEMEQ("family", tag, len)) { | 
| -        family_element_handler(self, attributes); | 
| -    } else if (MEMEQ("font", tag, len)) { | 
| -        font_element_handler(self, attributes); | 
| -    } else if (MEMEQ("alias", tag, len)) { | 
| -        alias_element_handler(self, attributes); | 
| -    } | 
| -} | 
| - | 
| -static bool is_whitespace(char c) { | 
| -    return c == ' ' || c == '\n'|| c == '\r' || c == '\t'; | 
| -} | 
| - | 
| -static void trim_string(SkString* s) { | 
| -    char* str = s->writable_str(); | 
| -    const char* start = str;  // start is inclusive | 
| -    const char* end = start + s->size();  // end is exclusive | 
| -    while (is_whitespace(*start)) { ++start; } | 
| -    if (start != end) { | 
| -        --end;  // make end inclusive | 
| -        while (is_whitespace(*end)) { --end; } | 
| -        ++end;  // make end exclusive | 
| -    } | 
| -    size_t len = end - start; | 
| -    memmove(str, start, len); | 
| -    s->resize(len); | 
| -} | 
| +    }, | 
| +    /*end*/NULL, | 
| +    /*tag*/NULL, | 
| +    /*chars*/NULL, | 
| +}; | 
|  | 
| -static void XMLCALL end_element_handler(void* data, const char* tag) { | 
| -    FamilyData* self = static_cast<FamilyData*>(data); | 
| -    size_t len = strlen(tag); | 
| -    if (MEMEQ("family", tag, len)) { | 
| -        *self->fFamilies.append() = self->fCurrentFamily.detach(); | 
| -    } else if (MEMEQ("font", tag, len)) { | 
| -        XML_SetCharacterDataHandler(self->fParser, NULL); | 
| -        trim_string(&self->fCurrentFontInfo->fFileName); | 
| -    } | 
| -} | 
| +static const TagHandler familySetHandler = { | 
| +    /*start*/[](FamilyData* self, const char* tag, const char** attributes) { }, | 
| +    /*end*/NULL, | 
| +    /*tag*/[](FamilyData* self, const char* tag, const char** attributes) -> const TagHandler* { | 
| +        size_t len = strlen(tag); | 
| +        if (MEMEQ("family", tag, len)) { | 
| +            return &familyHandler; | 
| +        } else if (MEMEQ("alias", tag, len)) { | 
| +            return &aliasHandler; | 
| +        } | 
| +        return NULL; | 
| +    }, | 
| +    /*chars*/NULL, | 
| +}; | 
|  | 
| } // lmpParser | 
|  | 
| namespace jbParser { | 
|  | 
| -/** | 
| - * Handler for arbitrary text. This is used to parse the text inside each name | 
| - * or file tag. The resulting strings are put into the fNames or FontFileInfo arrays. | 
| - */ | 
| -static void XMLCALL text_handler(void* data, const char* s, int len) { | 
| -    FamilyData* self = static_cast<FamilyData*>(data); | 
| -    // Make sure we're in the right state to store this name information | 
| -    if (self->fCurrentFamily.get()) { | 
| -        switch (self->fCurrentTag) { | 
| -        case kNameSet_CurrentTag: { | 
| -            SkAutoAsciiToLC tolc(s, len); | 
| -            self->fCurrentFamily->fNames.back().append(tolc.lc(), len); | 
| -            break; | 
| -        } | 
| -        case kFileSet_CurrentTag: | 
| -            if (self->fCurrentFontInfo) { | 
| -                self->fCurrentFontInfo->fFileName.append(s, len); | 
| +static const TagHandler fileHandler = { | 
| +    /*start*/[](FamilyData* self, const char* tag, const char** attributes) { | 
| +        // 'variant' ("elegant", "compact") [default "default"] | 
| +        // 'lang' (string) [default ""] | 
| +        // 'index' (non-negative integer) [default 0] | 
| +        // The character data should be a filename. | 
| +        FontFamily& currentFamily = *self->fCurrentFamily.get(); | 
| +        FontFileInfo& newFileInfo = currentFamily.fFonts.push_back(); | 
| +        if (attributes) { | 
| +            for (size_t i = 0; ATTS_NON_NULL(attributes, i); i += 2) { | 
| +                const char* name = attributes[i]; | 
| +                const char* value = attributes[i+1]; | 
| +                size_t nameLen = strlen(name); | 
| +                size_t valueLen = strlen(value); | 
| +                if (MEMEQ("variant", name, nameLen)) { | 
| +                    const FontVariant prevVariant = currentFamily.fVariant; | 
| +                    if (MEMEQ("elegant", value, valueLen)) { | 
| +                        currentFamily.fVariant = kElegant_FontVariant; | 
| +                    } else if (MEMEQ("compact", value, valueLen)) { | 
| +                        currentFamily.fVariant = kCompact_FontVariant; | 
| +                    } | 
| +                    if (currentFamily.fFonts.count() > 1 && currentFamily.fVariant != prevVariant) { | 
| +                        SK_FONTCONFIGPARSER_WARNING("'%s' unexpected variant found\n" | 
| +                            "Note: Every font file within a family must have identical variants.", | 
| +                            value); | 
| +                    } | 
| + | 
| +                } else if (MEMEQ("lang", name, nameLen)) { | 
| +                    SkLanguage prevLang = currentFamily.fLanguage; | 
| +                    currentFamily.fLanguage = SkLanguage(value, valueLen); | 
| +                    if (currentFamily.fFonts.count() > 1 && currentFamily.fLanguage != prevLang) { | 
| +                        SK_FONTCONFIGPARSER_WARNING("'%s' unexpected language found\n" | 
| +                            "Note: Every font file within a family must have identical languages.", | 
| +                            value); | 
| +                    } | 
| + | 
| +                } else if (MEMEQ("index", name, nameLen)) { | 
| +                    if (!parse_non_negative_integer(value, &newFileInfo.fIndex)) { | 
| +                        SK_FONTCONFIGPARSER_WARNING("'%s' is an invalid index", value); | 
| +                    } | 
| +                } | 
| } | 
| -            break; | 
| -        default: | 
| -            // Noop - don't care about any text that's not in the Fonts or Names list | 
| -            break; | 
| } | 
| +        self->fCurrentFontInfo = &newFileInfo; | 
| +    }, | 
| +    /*end*/NULL, | 
| +    /*tag*/NULL, | 
| +    /*chars*/[](void* data, const char* s, int len) { | 
| +        FamilyData* self = static_cast<FamilyData*>(data); | 
| +        self->fCurrentFontInfo->fFileName.append(s, len); | 
| } | 
| -} | 
| +}; | 
|  | 
| -/** | 
| - * Handler for font files. This processes the attributes for language and | 
| - * variants then lets textHandler handle the actual file name | 
| - */ | 
| -static void font_file_element_handler(FamilyData* self, const char** attributes) { | 
| -    FontFamily& currentFamily = *self->fCurrentFamily.get(); | 
| -    FontFileInfo& newFileInfo = currentFamily.fFonts.push_back(); | 
| -    if (attributes) { | 
| +static const TagHandler fileSetHandler = { | 
| +    /*start*/NULL, | 
| +    /*end*/NULL, | 
| +    /*tag*/[](FamilyData* self, const char* tag, const char** attributes) -> const TagHandler* { | 
| +        size_t len = strlen(tag); | 
| +        if (MEMEQ("file", tag, len)) { | 
| +            return &fileHandler; | 
| +        } | 
| +        return NULL; | 
| +    }, | 
| +    /*chars*/NULL, | 
| +}; | 
| + | 
| +static const TagHandler nameHandler = { | 
| +    /*start*/[](FamilyData* self, const char* tag, const char** attributes) { | 
| +        // The character data should be a name for the font. | 
| +        self->fCurrentFamily->fNames.push_back(); | 
| +    }, | 
| +    /*end*/NULL, | 
| +    /*tag*/NULL, | 
| +    /*chars*/[](void* data, const char* s, int len) { | 
| +        FamilyData* self = static_cast<FamilyData*>(data); | 
| +        SkAutoAsciiToLC tolc(s, len); | 
| +        self->fCurrentFamily->fNames.back().append(tolc.lc(), len); | 
| +    } | 
| +}; | 
| + | 
| +static const TagHandler nameSetHandler = { | 
| +    /*start*/NULL, | 
| +    /*end*/NULL, | 
| +    /*tag*/[](FamilyData* self, const char* tag, const char** attributes) -> const TagHandler* { | 
| +        size_t len = strlen(tag); | 
| +        if (MEMEQ("name", tag, len)) { | 
| +            return &nameHandler; | 
| +        } | 
| +        return NULL; | 
| +    }, | 
| +    /*chars*/NULL, | 
| +}; | 
| + | 
| +static const TagHandler familyHandler = { | 
| +    /*start*/[](FamilyData* self, const char* tag, const char** attributes) { | 
| +        self->fCurrentFamily.reset(new FontFamily(self->fBasePath, self->fIsFallback)); | 
| +        // 'order' (non-negative integer) [default -1] | 
| for (size_t i = 0; ATTS_NON_NULL(attributes, i); i += 2) { | 
| -            const char* name = attributes[i]; | 
| const char* value = attributes[i+1]; | 
| -            size_t nameLen = strlen(name); | 
| -            size_t valueLen = strlen(value); | 
| -            if (MEMEQ("variant", name, nameLen)) { | 
| -                const FontVariant prevVariant = currentFamily.fVariant; | 
| -                if (MEMEQ("elegant", value, valueLen)) { | 
| -                    currentFamily.fVariant = kElegant_FontVariant; | 
| -                } else if (MEMEQ("compact", value, valueLen)) { | 
| -                    currentFamily.fVariant = kCompact_FontVariant; | 
| -                } | 
| -                if (currentFamily.fFonts.count() > 1 && currentFamily.fVariant != prevVariant) { | 
| -                    SK_FONTCONFIGPARSER_WARNING("'%s' unexpected variant found\n" | 
| -                        "Note: Every font file within a family must have identical variants.", | 
| -                        value); | 
| -                } | 
| +            parse_non_negative_integer(value, &self->fCurrentFamily->fOrder); | 
| +        } | 
| +    }, | 
| +    /*end*/[](FamilyData* self, const char* tag) { | 
| +        *self->fFamilies.append() = self->fCurrentFamily.detach(); | 
| +    }, | 
| +    /*tag*/[](FamilyData* self, const char* tag, const char** attributes) -> const TagHandler* { | 
| +        size_t len = strlen(tag); | 
| +        if (MEMEQ("nameset", tag, len)) { | 
| +            return &nameSetHandler; | 
| +        } else if (MEMEQ("fileset", tag, len)) { | 
| +            return &fileSetHandler; | 
| +        } | 
| +        return NULL; | 
| +    }, | 
| +    /*chars*/NULL, | 
| +}; | 
|  | 
| -            } else if (MEMEQ("lang", name, nameLen)) { | 
| -                SkLanguage prevLang = currentFamily.fLanguage; | 
| -                currentFamily.fLanguage = SkLanguage(value, valueLen); | 
| -                if (currentFamily.fFonts.count() > 1 && currentFamily.fLanguage != prevLang) { | 
| -                    SK_FONTCONFIGPARSER_WARNING("'%s' unexpected language found\n" | 
| -                        "Note: Every font file within a family must have identical languages.", | 
| -                        value); | 
| -                } | 
| +static const TagHandler familySetHandler = { | 
| +    /*start*/NULL, | 
| +    /*end*/NULL, | 
| +    /*tag*/[](FamilyData* self, const char* tag, const char** attributes) -> const TagHandler* { | 
| +        size_t len = strlen(tag); | 
| +        if (MEMEQ("family", tag, len)) { | 
| +            return &familyHandler; | 
| +        } | 
| +        return NULL; | 
| +    }, | 
| +    /*chars*/NULL, | 
| +}; | 
|  | 
| -            } else if (MEMEQ("index", name, nameLen)) { | 
| -                if (!parse_non_negative_integer(value, &newFileInfo.fIndex)) { | 
| -                    SK_FONTCONFIGPARSER_WARNING("'%s' is an invalid index", value); | 
| +} // namespace jbParser | 
| + | 
| +static const TagHandler topLevelHandler = { | 
| +    /*start*/NULL, | 
| +    /*end*/NULL, | 
| +    /*tag*/[](FamilyData* self, const char* tag, const char** attributes) -> const TagHandler* { | 
| +        size_t len = strlen(tag); | 
| +        if (MEMEQ("familyset", tag, len)) { | 
| +            // 'version' (non-negative integer) [default 0] | 
| +            for (size_t i = 0; ATTS_NON_NULL(attributes, i); i += 2) { | 
| +                const char* name = attributes[i]; | 
| +                size_t nameLen = strlen(name); | 
| +                if (MEMEQ("version", name, nameLen)) { | 
| +                    const char* value = attributes[i+1]; | 
| +                    if (parse_non_negative_integer(value, &self->fVersion)) { | 
| +                        if (self->fVersion >= 21) { | 
| +                            return &lmpParser::familySetHandler; | 
| +                        } | 
| +                    } | 
| } | 
| } | 
| +            return &jbParser::familySetHandler; | 
| } | 
| -    } | 
| -    self->fCurrentFontInfo = &newFileInfo; | 
| -    XML_SetCharacterDataHandler(self->fParser, text_handler); | 
| -} | 
| +        return NULL; | 
| +    }, | 
| +    /*chars*/NULL, | 
| +}; | 
|  | 
| -/** | 
| - * Handler for the start of a tag. The only tags we expect are familyset, family, | 
| - * nameset, fileset, name, and file. | 
| - */ | 
| -static void XMLCALL start_element_handler(void* data, const char* tag, const char** attributes) { | 
| +static void XMLCALL start_element_handler(void *data, const char *tag, const char **attributes) { | 
| FamilyData* self = static_cast<FamilyData*>(data); | 
| -    size_t len = strlen(tag); | 
| -    if (MEMEQ("familyset", tag, len)) { | 
| -        // The <familyset> element has an optional 'version' (non-negative integer) attribute. | 
| -        for (size_t i = 0; ATTS_NON_NULL(attributes, i); i += 2) { | 
| -            size_t nameLen = strlen(attributes[i]); | 
| -            if (!MEMEQ("version", attributes[i], nameLen)) continue; | 
| -            const char* valueString = attributes[i+1]; | 
| -            int version; | 
| -            if (parse_non_negative_integer(valueString, &version) && (version >= 21)) { | 
| -                XML_SetElementHandler(self->fParser, | 
| -                                      lmpParser::start_element_handler, | 
| -                                      lmpParser::end_element_handler); | 
| -                self->fVersion = version; | 
| -            } | 
| -        } | 
| -    } else if (MEMEQ("family", tag, len)) { | 
| -        self->fCurrentFamily.reset(new FontFamily(self->fBasePath, self->fIsFallback)); | 
| -        // The <family> element has an optional 'order' (non-negative integer) attribute. | 
| -        // If this attribute does not exist, the default value is -1. | 
| -        for (size_t i = 0; ATTS_NON_NULL(attributes, i); i += 2) { | 
| -            const char* valueString = attributes[i+1]; | 
| -            int value; | 
| -            if (parse_non_negative_integer(valueString, &value)) { | 
| -                self->fCurrentFamily->fOrder = value; | 
| + | 
| +    if (!self->fSkip) { | 
| +        const TagHandler* parent = self->fHandler.top(); | 
| +        const TagHandler* child = parent->tag ? parent->tag(self, tag, attributes) : NULL; | 
| +        if (child) { | 
| +            if (child->start) { | 
| +                child->start(self, tag, attributes); | 
| } | 
| +            self->fHandler.push(child); | 
| +            XML_SetCharacterDataHandler(self->fParser, child->chars); | 
| +        } else { | 
| +            SK_FONTCONFIGPARSER_WARNING("'%s' tag not recognized, skipping", tag); | 
| +            XML_SetCharacterDataHandler(self->fParser, NULL); | 
| +            self->fSkip = self->fDepth; | 
| } | 
| -    } else if (MEMEQ("nameset", tag, len)) { | 
| -        self->fCurrentTag = kNameSet_CurrentTag; | 
| -    } else if (MEMEQ("fileset", tag, len)) { | 
| -        self->fCurrentTag = kFileSet_CurrentTag; | 
| -    } else if (MEMEQ("name", tag, len) && self->fCurrentTag == kNameSet_CurrentTag) { | 
| -        // If it's a Name, parse the text inside | 
| -        self->fCurrentFamily->fNames.push_back(); | 
| -        XML_SetCharacterDataHandler(self->fParser, text_handler); | 
| -    } else if (MEMEQ("file", tag, len) && self->fCurrentTag == kFileSet_CurrentTag) { | 
| -        // If it's a file, parse the attributes, then parse the text inside | 
| -        font_file_element_handler(self, attributes); | 
| } | 
| + | 
| +    ++self->fDepth; | 
| } | 
|  | 
| -/** | 
| - * Handler for the end of tags. We only care about family, nameset, fileset, | 
| - * name, and file. | 
| - */ | 
| static void XMLCALL end_element_handler(void* data, const char* tag) { | 
| FamilyData* self = static_cast<FamilyData*>(data); | 
| -    size_t len = strlen(tag); | 
| -    if (MEMEQ("family", tag, len)) { | 
| -        // Done parsing a Family - store the created currentFamily in the families array | 
| -        *self->fFamilies.append() = self->fCurrentFamily.detach(); | 
| -    } else if (MEMEQ("nameset", tag, len)) { | 
| -        self->fCurrentTag = kNo_CurrentTag; | 
| -    } else if (MEMEQ("fileset", tag, len)) { | 
| -        self->fCurrentTag = kNo_CurrentTag; | 
| -    } else if ((MEMEQ("name", tag, len) && self->fCurrentTag == kNameSet_CurrentTag) || | 
| -               (MEMEQ("file", tag, len) && self->fCurrentTag == kFileSet_CurrentTag)) { | 
| -        // Disable the arbitrary text handler installed to load Name data | 
| -        XML_SetCharacterDataHandler(self->fParser, NULL); | 
| +    --self->fDepth; | 
| + | 
| +    if (!self->fSkip) { | 
| +        const TagHandler* child = self->fHandler.top(); | 
| +        if (child->end) { | 
| +            child->end(self, tag); | 
| +        } | 
| +        self->fHandler.pop(); | 
| +        const TagHandler* parent = self->fHandler.top(); | 
| +        XML_SetCharacterDataHandler(self->fParser, parent->chars); | 
| } | 
| -} | 
|  | 
| -} // namespace jbParser | 
| +    if (self->fSkip == self->fDepth) { | 
| +        self->fSkip = 0; | 
| +        const TagHandler* parent = self->fHandler.top(); | 
| +        XML_SetCharacterDataHandler(self->fParser, parent->chars); | 
| +    } | 
| +} | 
|  | 
| static void XMLCALL xml_entity_decl_handler(void *data, | 
| const XML_Char *entityName, | 
| @@ -485,14 +588,14 @@ static int parse_config_file(const char* filename, SkTDArray<FontFamily*>& famil | 
| return -1; | 
| } | 
|  | 
| -    FamilyData self(parser, families, basePath, isFallback, filename); | 
| +    FamilyData self(parser, families, basePath, isFallback, filename, &topLevelHandler); | 
| XML_SetUserData(parser, &self); | 
|  | 
| // Disable entity processing, to inhibit internal entity expansion. See expat CVE-2013-0340 | 
| XML_SetEntityDeclHandler(parser, xml_entity_decl_handler); | 
|  | 
| // Start parsing oldschool; switch these in flight if we detect a newer version of the file. | 
| -    XML_SetElementHandler(parser, jbParser::start_element_handler, jbParser::end_element_handler); | 
| +    XML_SetElementHandler(parser, start_element_handler, end_element_handler); | 
|  | 
| // One would assume it would be faster to have a buffer on the stack and call XML_Parse. | 
| // But XML_Parse will call XML_GetBuffer anyway and memmove the passed buffer into it. | 
|  |