blob: eb2ed07f9e918f620c2b4f2feff9887a151a70a8 [file] [log] [blame]
/*
* Copyright 2011 The Android Open Source Project
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "SkFontConfigParser_android.h"
#include "SkFontMgr_android.h"
#include "SkStream.h"
#include "SkTDArray.h"
#include "SkTSearch.h"
#include "SkTypeface.h"
#include <expat.h>
#include <dirent.h>
#include <stdio.h>
#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"
#define VENDOR_FONTS_FILE "/vendor/etc/fallback_fonts.xml"
#define LOCALE_FALLBACK_FONTS_SYSTEM_DIR "/system/etc"
#define LOCALE_FALLBACK_FONTS_VENDOR_DIR "/vendor/etc"
#define LOCALE_FALLBACK_FONTS_PREFIX "fallback_fonts-"
#define LOCALE_FALLBACK_FONTS_SUFFIX ".xml"
#ifndef SK_FONT_FILE_PREFIX
# define SK_FONT_FILE_PREFIX "/fonts/"
#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.
*/
/** 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
};
/**
* The FamilyData structure is passed around by the parser so that each handler
* can read these variables that are relevant to the current parsing.
*/
struct FamilyData {
FamilyData(XML_Parser parser, SkTDArray<FontFamily*>& families,
const SkString& basePath, bool isFallback)
: fParser(parser)
, fFamilies(families)
, fCurrentFamily(NULL)
, fCurrentFontInfo(NULL)
, fCurrentTag(kNo_CurrentTag)
, fVersion(0)
, fBasePath(basePath)
, fIsFallback(isFallback)
{ };
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
};
/** Parses a null terminated string into an integer type, checking for overflow.
* http://www.w3.org/TR/html-markup/datatypes.html#common.data.integer.non-negative-def
*
* If the string cannot be parsed into 'value', returns false and does not change 'value'.
*/
template <typename T> static bool parse_non_negative_integer(const char* s, T* value) {
SK_COMPILE_ASSERT(std::numeric_limits<T>::is_integer, T_must_be_integer);
const T nMax = std::numeric_limits<T>::max() / 10;
const T dMax = std::numeric_limits<T>::max() - (nMax * 10);
T n = 0;
for (; *s; ++s) {
// Check if digit
if (*s < '0' || '9' < *s) {
return false;
}
int d = *s - '0';
// Check for overflow
if (n > nMax || (n == nMax && d > dMax)) {
return false;
}
n = (n * 10) + d;
}
*value = n;
return true;
}
static bool memeq(const char* s1, const char* s2, size_t n1, size_t n2) {
return n1 == n2 && 0 == memcmp(s1, s2, n1);
}
#define MEMEQ(c, s, n) memeq(c, s, sizeof(c) - 1, n)
#define ATTS_NON_NULL(a, i) (a[i] != NULL && a[i+1] != NULL)
namespace lmpParser {
static void family_element_handler(FontFamily* family, const char** attributes) {
// A non-fallback <family> tag must have a canonical name attribute.
// A fallback <family> tag has no name, and may have lang and variant
// attributes.
family->fIsFallbackFont = true;
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)) {
// Value should be either elegant or compact.
if (MEMEQ("elegant", value, valueLen)) {
family->fVariant = kElegant_FontVariant;
} else if (MEMEQ("compact", value, valueLen)) {
family->fVariant = kCompact_FontVariant;
}
}
}
}
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);
}
static void font_element_handler(FamilyData* self, FontFileInfo* file, const char** attributes) {
// A <font> should have weight (integer) and style (normal, italic) attributes.
// NOTE: we ignore the style.
// The element should contain a filename.
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)) {
SkDebugf("---- Font weight %s (INVALID)", value);
}
}
}
XML_SetCharacterDataHandler(self->fParser, font_file_name_handler);
}
static FontFamily* find_family(FamilyData* self, const SkString& familyName) {
for (int i = 0; i < self->fFamilies.count(); i++) {
FontFamily* candidate = self->fFamilies[i];
for (int j = 0; j < candidate->fNames.count(); j++) {
if (candidate->fNames[j] == familyName) {
return candidate;
}
}
}
return NULL;
}
static void alias_element_handler(FamilyData* self, const char** attributes) {
// An <alias> must have name and to attributes.
// It may have weight (integer).
// If it *does not* have a weight, it is a variant name for a <family>.
// If it *does* have a weight, it names the <font>(s) of a specific weight
// from a <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)) {
SkDebugf("---- Font weight %s (INVALID)", value);
}
}
}
// Assumes that the named family is already declared
FontFamily* targetFamily = find_family(self, to);
if (!targetFamily) {
SkDebugf("---- Font alias target %s (NOT FOUND)", to.c_str());
return;
}
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]);
}
}
*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)) {
self->fCurrentFamily.reset(new FontFamily(self->fBasePath, self->fIsFallback));
family_element_handler(self->fCurrentFamily, attributes);
} else if (MEMEQ("font", tag, len)) {
FontFileInfo* file = &self->fCurrentFamily->fFonts.push_back();
self->fCurrentFontInfo = file;
font_element_handler(self, file, attributes);
} else if (MEMEQ("alias", tag, len)) {
alias_element_handler(self, attributes);
}
}
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);
}
}
} // 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);
}
break;
default:
// Noop - don't care about any text that's not in the Fonts or Names list
break;
}
}
}
/**
* 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) {
for (size_t i = 0; ATTS_NON_NULL(attributes, i); i += 2) {
const char* attributeName = attributes[i];
const char* attributeValue = attributes[i+1];
size_t nameLength = strlen(attributeName);
size_t valueLength = strlen(attributeValue);
if (MEMEQ("variant", attributeName, nameLength)) {
const FontVariant prevVariant = currentFamily.fVariant;
if (MEMEQ("elegant", attributeValue, valueLength)) {
currentFamily.fVariant = kElegant_FontVariant;
} else if (MEMEQ("compact", attributeValue, valueLength)) {
currentFamily.fVariant = kCompact_FontVariant;
}
if (currentFamily.fFonts.count() > 1 && currentFamily.fVariant != prevVariant) {
SkDebugf("Every font file within a family must have identical variants");
}
} else if (MEMEQ("lang", attributeName, nameLength)) {
SkLanguage prevLang = currentFamily.fLanguage;
currentFamily.fLanguage = SkLanguage(attributeValue, valueLength);
if (currentFamily.fFonts.count() > 1 && currentFamily.fLanguage != prevLang) {
SkDebugf("Every font file within a family must have identical languages");
}
} else if (MEMEQ("index", attributeName, nameLength)) {
if (!parse_non_negative_integer(attributeValue, &newFileInfo.fIndex)) {
SkDebugf("---- SystemFonts index=%s (INVALID)", attributeValue);
}
}
}
}
self->fCurrentFontInfo = &newFileInfo;
XML_SetCharacterDataHandler(self->fParser, text_handler);
}
/**
* 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) {
FamilyData* self = static_cast<FamilyData*>(data);
size_t len = strlen(tag);
if (MEMEQ("familyset", tag, len)) {
// The familyset tag has an optional "version" attribute with an integer value >= 0
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 tag has an optional "order" attribute with an integer value >= 0
// 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;
}
}
} 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);
}
}
/**
* 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);
}
}
} // namespace jbParser
static void XMLCALL xml_entity_decl_handler(void *data,
const XML_Char *entityName,
int is_parameter_entity,
const XML_Char *value,
int value_length,
const XML_Char *base,
const XML_Char *systemId,
const XML_Char *publicId,
const XML_Char *notationName)
{
FamilyData* self = static_cast<FamilyData*>(data);
SkDebugf("Entity declaration %s found, stopping processing.", entityName);
XML_StopParser(self->fParser, XML_FALSE);
}
static const XML_Memory_Handling_Suite sk_XML_alloc = {
sk_malloc_throw,
sk_realloc_throw,
sk_free
};
template<typename T> struct remove_ptr {typedef T type;};
template<typename T> struct remove_ptr<T*> {typedef T type;};
/**
* This function parses the given filename and stores the results in the given
* families array. Returns the version of the file, negative if the file does not exist.
*/
static int parse_config_file(const char* filename, SkTDArray<FontFamily*>& families,
const SkString& basePath, bool isFallback)
{
SkFILEStream file(filename);
// Some of the files we attempt to parse (in particular, /vendor/etc/fallback_fonts.xml)
// are optional - failure here is okay because one of these optional files may not exist.
if (!file.isValid()) {
SkDebugf("File %s could not be opened.\n", filename);
return -1;
}
SkAutoTCallVProc<remove_ptr<XML_Parser>::type, XML_ParserFree> parser(
XML_ParserCreate_MM(NULL, &sk_XML_alloc, NULL));
if (!parser) {
SkDebugf("Could not create XML parser.\n");
return -1;
}
FamilyData self(parser, families, basePath, isFallback);
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);
// 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.
// (Unless XML_CONTEXT_BYTES is undefined, but all users define it.)
// In debug, buffer a small odd number of bytes to detect slicing in XML_CharacterDataHandler.
static const int bufferSize = 512 SkDEBUGCODE( - 507);
bool done = false;
while (!done) {
void* buffer = XML_GetBuffer(parser, bufferSize);
if (!buffer) {
SkDebugf("Could not buffer enough to continue.\n");
return -1;
}
size_t len = file.read(buffer, bufferSize);
done = file.isAtEnd();
XML_Status status = XML_ParseBuffer(parser, len, done);
if (XML_STATUS_ERROR == status) {
XML_Error error = XML_GetErrorCode(parser);
int line = XML_GetCurrentLineNumber(parser);
int column = XML_GetCurrentColumnNumber(parser);
int index = XML_GetCurrentByteIndex(parser);
const XML_LChar* errorString = XML_ErrorString(error);
SkDebugf("Line: %d Column: %d (Offset: %d) Error %d: %s.\n",
line, column, index, error, errorString);
return -1;
}
}
return self.fVersion;
}
/** Returns the version of the system font file actually found, negative if none. */
static int append_system_font_families(SkTDArray<FontFamily*>& fontFamilies,
const SkString& basePath)
{
int initialCount = fontFamilies.count();
int version = parse_config_file(LMP_SYSTEM_FONTS_FILE, fontFamilies, basePath, false);
if (version < 0 || fontFamilies.count() == initialCount) {
version = parse_config_file(OLD_SYSTEM_FONTS_FILE, fontFamilies, basePath, false);
}
return version;
}
/**
* In some versions of Android prior to Android 4.2 (JellyBean MR1 at API
* Level 17) the fallback fonts for certain locales were encoded in their own
* XML files with a suffix that identified the locale. We search the provided
* directory for those files,add all of their entries to the fallback chain, and
* include the locale as part of each entry.
*/
static void append_fallback_font_families_for_locale(SkTDArray<FontFamily*>& fallbackFonts,
const char* dir,
const SkString& basePath)
{
#if defined(SK_BUILD_FOR_ANDROID_FRAMEWORK)
// The framework is beyond Android 4.2 and can therefore skip this function
return;
#endif
SkAutoTCallIProc<DIR, closedir> fontDirectory(opendir(dir));
if (NULL == fontDirectory) {
return;
}
for (struct dirent* dirEntry; (dirEntry = readdir(fontDirectory));) {
// The size of the prefix and suffix.
static const size_t fixedLen = sizeof(LOCALE_FALLBACK_FONTS_PREFIX) - 1
+ sizeof(LOCALE_FALLBACK_FONTS_SUFFIX) - 1;
// The size of the prefix, suffix, and a minimum valid language code
static const size_t minSize = fixedLen + 2;
SkString fileName(dirEntry->d_name);
if (fileName.size() < minSize ||
!fileName.startsWith(LOCALE_FALLBACK_FONTS_PREFIX) ||
!fileName.endsWith(LOCALE_FALLBACK_FONTS_SUFFIX))
{
continue;
}
SkString locale(fileName.c_str() + sizeof(LOCALE_FALLBACK_FONTS_PREFIX) - 1,
fileName.size() - fixedLen);
SkString absoluteFilename;
absoluteFilename.printf("%s/%s", dir, fileName.c_str());
SkTDArray<FontFamily*> langSpecificFonts;
parse_config_file(absoluteFilename.c_str(), langSpecificFonts, basePath, true);
for (int i = 0; i < langSpecificFonts.count(); ++i) {
FontFamily* family = langSpecificFonts[i];
family->fLanguage = SkLanguage(locale);
*fallbackFonts.append() = family;
}
}
}
static void append_system_fallback_font_families(SkTDArray<FontFamily*>& fallbackFonts,
const SkString& basePath)
{
parse_config_file(FALLBACK_FONTS_FILE, fallbackFonts, basePath, true);
append_fallback_font_families_for_locale(fallbackFonts,
LOCALE_FALLBACK_FONTS_SYSTEM_DIR,
basePath);
}
static void mixin_vendor_fallback_font_families(SkTDArray<FontFamily*>& fallbackFonts,
const SkString& basePath)
{
SkTDArray<FontFamily*> vendorFonts;
parse_config_file(VENDOR_FONTS_FILE, vendorFonts, basePath, true);
append_fallback_font_families_for_locale(vendorFonts,
LOCALE_FALLBACK_FONTS_VENDOR_DIR,
basePath);
// This loop inserts the vendor fallback fonts in the correct order in the
// overall fallbacks list.
int currentOrder = -1;
for (int i = 0; i < vendorFonts.count(); ++i) {
FontFamily* family = vendorFonts[i];
int order = family->fOrder;
if (order < 0) {
if (currentOrder < 0) {
// Default case - just add it to the end of the fallback list
*fallbackFonts.append() = family;
} else {
// no order specified on this font, but we're incrementing the order
// based on an earlier order insertion request
*fallbackFonts.insert(currentOrder++) = family;
}
} else {
// Add the font into the fallback list in the specified order. Set
// currentOrder for correct placement of other fonts in the vendor list.
*fallbackFonts.insert(order) = family;
currentOrder = order + 1;
}
}
}
void SkFontConfigParser::GetSystemFontFamilies(SkTDArray<FontFamily*>& fontFamilies) {
// Version 21 of the system font configuration does not need any fallback configuration files.
SkString basePath(getenv("ANDROID_ROOT"));
basePath.append(SK_FONT_FILE_PREFIX, sizeof(SK_FONT_FILE_PREFIX) - 1);
if (append_system_font_families(fontFamilies, basePath) >= 21) {
return;
}
// Append all the fallback fonts to system fonts
SkTDArray<FontFamily*> fallbackFonts;
append_system_fallback_font_families(fallbackFonts, basePath);
mixin_vendor_fallback_font_families(fallbackFonts, basePath);
fontFamilies.append(fallbackFonts.count(), fallbackFonts.begin());
}
void SkFontConfigParser::GetCustomFontFamilies(SkTDArray<FontFamily*>& fontFamilies,
const SkString& basePath,
const char* fontsXml,
const char* fallbackFontsXml,
const char* langFallbackFontsDir)
{
if (fontsXml) {
parse_config_file(fontsXml, fontFamilies, basePath, false);
}
if (fallbackFontsXml) {
parse_config_file(fallbackFontsXml, fontFamilies, basePath, true);
}
if (langFallbackFontsDir) {
append_fallback_font_families_for_locale(fontFamilies,
langFallbackFontsDir,
basePath);
}
}
SkLanguage SkLanguage::getParent() const {
SkASSERT(!fTag.isEmpty());
const char* tag = fTag.c_str();
// strip off the rightmost "-.*"
const char* parentTagEnd = strrchr(tag, '-');
if (parentTagEnd == NULL) {
return SkLanguage();
}
size_t parentTagLen = parentTagEnd - tag;
return SkLanguage(tag, parentTagLen);
}