blob: 206c6e3bf383fa945d073888dfdd2c489a1469d2 [file] [log] [blame]
// Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontConfigParser_cobalt.h"
#include <libxml/parser.h>
#include <limits>
#include <stack>
#include <string>
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/string_util.h"
#include "base/stringprintf.h"
#include "SkData.h"
#include "SkOSFile.h"
#include "SkStream.h"
#include "SkTSearch.h"
namespace {
const char* kConfigFile = "fonts.xml";
/////////////////////////////////////////////////////////////////////////////
// Helpers
/////////////////////////////////////////////////////////////////////////////
std::string StringPrintVAndTrim(const char* message, va_list arguments) {
const std::string formatted_message = base::StringPrintV(message, arguments);
std::string trimmed_message;
TrimWhitespace(formatted_message, TRIM_ALL, &trimmed_message);
return trimmed_message;
}
// https://www.w3.org/TR/html-markup/datatypes.html#common.data.integer.non-negative-def
template <typename T>
bool ParseNonNegativeInteger(const char* s, T* value) {
SK_COMPILE_ASSERT(std::numeric_limits<T>::is_integer, T_must_be_integer);
const T n_max = std::numeric_limits<T>::max() / 10;
const T d_max = std::numeric_limits<T>::max() - (n_max * 10);
T n = 0;
for (; *s; ++s) {
// Check if digit
if (!IsAsciiDigit(*s)) {
return false;
}
int d = *s - '0';
// Check for overflow
if (n > n_max || (n == n_max && d > d_max)) {
LOG(ERROR) << "---- ParseNonNegativeInteger error: overflow";
return false;
}
n = (n * 10) + d;
}
*value = n;
return true;
}
template <typename T>
bool ParseInteger(const char* s, T* value) {
SK_COMPILE_ASSERT(std::numeric_limits<T>::is_signed, T_must_be_signed);
T multiplier = 1;
if (*s && *s == '-') {
multiplier = -1;
++s;
}
if (!ParseNonNegativeInteger(s, value)) {
return false;
}
*value *= multiplier;
return true;
}
template <typename T>
bool ParseFontWeight(const char* s, T* value) {
T n = 0;
if (!ParseNonNegativeInteger(s, &n)) {
return false;
}
// Verify that the weight is a multiple of 100 between 100 and 900.
// https://www.w3.org/TR/css-fonts-3/#font-weight-prop
if (n < 100 || n > 900 || n % 100 != 0) {
return false;
}
*value = n;
return true;
}
// The page range list is a comma-delimited list of character page ranges. Each
// page range can either consist of either a single integer value, or a pair of
// values separated by a hyphen (representing the min and max value of the
// range). All page values must fall between 0 and kMaxPageValue. Additionally,
// the page ranges must be provided in ascending order. Any failure to meet
// these expectations will result in a parsing error.
bool ParsePageRangeList(const char* s, font_character_map::PageRanges* ranges) {
const int16 n_max = font_character_map::kMaxPageValue / 10;
const int16 d_max = font_character_map::kMaxPageValue - (n_max * 10);
int16 last_max = -1;
while (*s) {
font_character_map::PageRange range_value;
// Skip whitespace
while (IsAsciiWhitespace(*s)) {
++s;
if (!*s) {
return true;
}
}
for (int i = 0; i <= 1; ++i) {
if (!IsAsciiDigit(*s)) {
LOG(ERROR)
<< "---- ParsePageRangeList error: non-ascii digit page range";
return false;
}
int16 n = 0;
for (; *s; ++s) {
if (!IsAsciiDigit(*s)) {
break;
}
int d = *s - '0';
// Check for overflow
if (n > n_max || (n == n_max && d > d_max)) {
LOG(ERROR) << "---- ParsePageRangeList error: page range overflow";
return false;
}
n = (n * 10) + d;
}
if (i == 0) {
// Verify that this new range is larger than the previously encountered
// max. Ranges must appear in order. If it isn't, then the parsing has
// failed.
if (last_max >= n) {
LOG(ERROR) << "---- ParsePageRangeList error: pages unordered";
return false;
}
range_value.first = n;
if (*s && *s == '-') {
++s;
continue;
} else {
last_max = n;
range_value.second = n;
ranges->push_back(range_value);
break;
}
} else {
if (range_value.first <= n) {
last_max = n;
range_value.second = n;
ranges->push_back(range_value);
} else {
LOG(ERROR) << "---- ParsePageRangeList error: page range flipped";
return false;
}
}
}
if (*s) {
// Skip whitespace
while (IsAsciiWhitespace(*s)) {
++s;
if (!*s) {
return true;
}
}
if (*s == ',') {
++s;
} else {
LOG(ERROR) << "---- ParsePageRangeList error: invalid character";
return false;
}
}
}
return true;
}
/////////////////////////////////////////////////////////////////////////////
// Libxml SAX Handlers
/////////////////////////////////////////////////////////////////////////////
typedef unsigned char xmlChar;
void StartElement(void* context, const xmlChar* name,
const xmlChar** attribute_pairs);
void EndElement(void* context, const xmlChar* name);
void Characters(void* context, const xmlChar* ch, int len);
void ParserWarning(void* context, const char* message, ...);
void ParserError(void* context, const char* message, ...);
void ParserFatal(void* context, const char* message, ...);
xmlSAXHandler xml_sax_handler = {
NULL, /* internalSubset */
NULL, /* isStandalone */
NULL, /* hasInternalSubset */
NULL, /* hasExternalSubset */
NULL, /* resolveEntity */
NULL, /* getEntity */
NULL, /* entityDecl */
NULL, /* notationDecl */
NULL, /* attributeDecl */
NULL, /* elementDecl */
NULL, /* unparsedEntityDecl */
NULL, /* setDocumentLocator */
NULL, /* startDocument */
NULL, /* endDocument */
&StartElement, /* startElement */
&EndElement, /* endElement */
NULL, /* reference */
&Characters, /* characters */
NULL, /* ignorableWhitespace */
NULL, /* processingInstruction */
NULL, /* comment */
&ParserWarning, /* xmlParserWarning */
&ParserError, /* xmlParserError */
&ParserFatal, /* xmlParserFatalError */
NULL, /* getParameterEntity */
NULL, /* cdataBlock */
NULL, /* externalSubset */
1, /* initialized */
NULL, /* private */
NULL, /* startElementNsSAX2Func */
NULL, /* endElementNsSAX2Func */
NULL /* xmlStructuredErrorFunc */
};
enum ElementType {
kFamilyElementType,
kFontElementType,
kAliasElementType,
kOtherElementType,
};
// 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 {
explicit FamilyData(SkTDArray<FontFamily*>* families_array)
: families(families_array), current_font_info(NULL) {}
// The array that each family is put into as it is parsed
SkTDArray<FontFamily*>* families;
// The current family being created. FamilyData owns this object while it is
// not NULL.
scoped_ptr<FontFamily> current_family;
// The current fontInfo being created. It is owned by FontFamily.
FontFileInfo* current_font_info;
std::stack<ElementType> element_stack;
};
void FamilyElementHandler(FontFamily* family, const char** attributes) {
if (attributes == NULL) {
return;
}
// A <family> may have the following attributes:
// name (string), fallback_priority (int), lang (string), pages (comma
// delimited int ranges)
// A <family> tag must have a canonical name attribute or be a fallback
// family in order to be usable, unless it is the default family.
// A family is a fallback family if one of two conditions are met:
// 1. It does not have a "name" attribute.
// 2. It has a "fallback_priority" attribute.
// If the fallback_priority attribute exists, then the family is given the
// fallback priority specified by the value. If it does not exist, then the
// fallback priority defaults to 0.
// The lang and pages attributes are only used by fallback families.
bool encountered_fallback_attribute = false;
family->is_fallback_family = true;
for (size_t i = 0; attributes[i] != NULL && attributes[i + 1] != NULL;
i += 2) {
const char* name = attributes[i];
const char* value = attributes[i + 1];
size_t name_len = strlen(name);
if (name_len == 4 && strncmp(name, "name", name_len) == 0) {
SkAutoAsciiToLC to_lowercase(value);
family->names.push_back().set(to_lowercase.lc());
// As long as no fallback attribute is encountered, then the existence of
// a name attribute removes this family from fallback.
if (!encountered_fallback_attribute) {
family->is_fallback_family = false;
}
} else if (name_len == 4 && strncmp("lang", name, name_len) == 0) {
family->language = SkLanguage(value);
} else if (name_len == 5 && strncmp("pages", name, name_len) == 0) {
if (!ParsePageRangeList(value, &family->page_ranges)) {
LOG(ERROR) << "---- Invalid page ranges [" << value << "]";
NOTREACHED();
family->page_ranges.reset();
}
} else if (name_len == 17 &&
strncmp("fallback_priority", name, name_len) == 0) {
encountered_fallback_attribute = true;
if (!ParseInteger(value, &family->fallback_priority)) {
LOG(ERROR) << "---- Invalid fallback priority [" << value << "]";
NOTREACHED();
}
} else {
LOG(ERROR) << "---- Unsupported family attribute [" << name << "]";
NOTREACHED();
}
}
}
void FontElementHandler(FontFileInfo* file, const char** attributes) {
DCHECK(file != NULL);
// A <font> may have following attributes:
// weight (non-negative integer), style (normal, italic), font_name (string),
// postscript_name (string), and index (non-negative integer)
// The element should contain a filename.
for (size_t i = 0; attributes[i] != NULL && attributes[i + 1] != NULL;
i += 2) {
const char* name = attributes[i];
const char* value = attributes[i + 1];
switch (strlen(name)) {
case 5:
if (strncmp("index", name, 5) == 0) {
if (!ParseNonNegativeInteger(value, &file->index)) {
LOG(ERROR) << "---- Invalid font index [" << value << "]";
NOTREACHED();
}
continue;
} else if (strncmp("style", name, 5) == 0) {
if (strncmp("italic", value, 6) == 0) {
file->style = FontFileInfo::kItalic_FontStyle;
continue;
} else if (strncmp("normal", value, 6) == 0) {
file->style = FontFileInfo::kNormal_FontStyle;
continue;
} else {
LOG(ERROR) << "---- Unsupported style [" << value << "]";
NOTREACHED();
}
}
break;
case 6:
if (strncmp("weight", name, 6) == 0) {
if (!ParseFontWeight(value, &file->weight)) {
LOG(ERROR) << "---- Invalid font weight [" << value << "]";
NOTREACHED();
}
continue;
}
break;
case 9:
if (strncmp("font_name", name, 9) == 0) {
SkAutoAsciiToLC to_lowercase(value);
file->full_font_name = to_lowercase.lc();
continue;
}
break;
case 15:
if (strncmp("postscript_name", name, 15) == 0) {
SkAutoAsciiToLC to_lowercase(value);
file->postscript_name = to_lowercase.lc();
continue;
}
break;
case 25:
if (strncmp("disable_synthetic_bolding", name, 25) == 0) {
file->disable_synthetic_bolding =
strcmp("true", value) == 0 || strcmp("1", value) == 0;
continue;
}
break;
default:
break;
}
LOG(ERROR) << "---- Unsupported font attribute [" << name << "]";
NOTREACHED();
}
}
FontFamily* FindFamily(FamilyData* family_data, const char* family_name) {
size_t name_len = strlen(family_name);
for (int i = 0; i < family_data->families->count(); i++) {
FontFamily* candidate = (*family_data->families)[i];
for (int j = 0; j < candidate->names.count(); j++) {
if (!strncmp(candidate->names[j].c_str(), family_name, name_len) &&
name_len == strlen(candidate->names[j].c_str())) {
return candidate;
}
}
}
return NULL;
}
void AliasElementHandler(FamilyData* family_data, const char** attributes) {
// An <alias> must have name and to attributes.
// It is a variant name for a <family>.
SkString alias_name;
SkString to;
for (size_t i = 0; attributes[i] != NULL && attributes[i + 1] != NULL;
i += 2) {
const char* name = attributes[i];
const char* value = attributes[i + 1];
size_t name_len = strlen(name);
if (name_len == 4 && strncmp("name", name, name_len) == 0) {
SkAutoAsciiToLC to_lowercase(value);
alias_name.set(to_lowercase.lc());
} else if (name_len == 2 && strncmp("to", name, name_len) == 0) {
to.set(value);
}
}
// Assumes that the named family is already declared
FontFamily* target_family = FindFamily(family_data, to.c_str());
if (!target_family) {
LOG(ERROR) << "---- Invalid alias target [name: " << alias_name.c_str()
<< ", to: " << to.c_str() << "]";
NOTREACHED();
return;
} else if (alias_name.size() == 0) {
LOG(ERROR) << "---- Invalid alias name [to: " << to.c_str() << "]";
NOTREACHED();
return;
}
target_family->names.push_back().set(alias_name);
}
void StartElement(void* data, const xmlChar* xml_tag,
const xmlChar** xml_attribute_pairs) {
FamilyData* family_data = reinterpret_cast<FamilyData*>(data);
const char* tag = reinterpret_cast<const char*>(xml_tag);
const char** attribute_pairs =
reinterpret_cast<const char**>(xml_attribute_pairs);
size_t tag_len = strlen(tag);
if (tag_len == 6 && strncmp("family", tag, tag_len) == 0) {
family_data->element_stack.push(kFamilyElementType);
family_data->current_family = make_scoped_ptr(new FontFamily());
FamilyElementHandler(family_data->current_family.get(), attribute_pairs);
} else if (tag_len == 4 && strncmp("font", tag, tag_len) == 0) {
family_data->element_stack.push(kFontElementType);
FontFileInfo* file = &family_data->current_family->fonts.push_back();
family_data->current_font_info = file;
FontElementHandler(file, attribute_pairs);
} else if (tag_len == 5 && strncmp("alias", tag, tag_len) == 0) {
family_data->element_stack.push(kAliasElementType);
AliasElementHandler(family_data, attribute_pairs);
} else {
family_data->element_stack.push(kOtherElementType);
}
}
void EndElement(void* data, const xmlChar* xml_tag) {
FamilyData* family_data = reinterpret_cast<FamilyData*>(data);
const char* tag = reinterpret_cast<const char*>(xml_tag);
size_t tag_len = strlen(tag);
if (tag_len == 6 && strncmp("family", tag, tag_len) == 0) {
if (family_data->current_family != NULL) {
*family_data->families->append() = family_data->current_family.release();
} else {
LOG(ERROR) << "---- Encountered end family tag with no current family";
NOTREACHED();
}
}
family_data->element_stack.pop();
}
void Characters(void* data, const xmlChar* xml_characters, int len) {
FamilyData* family_data = reinterpret_cast<FamilyData*>(data);
const char* characters = reinterpret_cast<const char*>(xml_characters);
if (family_data->element_stack.size() > 0 &&
family_data->element_stack.top() == kFontElementType) {
family_data->current_font_info->file_name.set(characters, len);
}
}
void ParserWarning(void* context, const char* message, ...) {
va_list arguments;
va_start(arguments, message);
DLOG(WARNING) << "---- Parsing warning: "
<< StringPrintVAndTrim(message, arguments).c_str();
}
void ParserError(void* context, const char* message, ...) {
va_list arguments;
va_start(arguments, message);
LOG(ERROR) << "---- Parsing error: "
<< StringPrintVAndTrim(message, arguments).c_str();
NOTREACHED();
}
void ParserFatal(void* context, const char* message, ...) {
va_list arguments;
va_start(arguments, message);
LOG(ERROR) << "---- Parsing fatal error: "
<< StringPrintVAndTrim(message, arguments).c_str();
NOTREACHED();
}
// This function parses the given filename and stores the results in the given
// families array.
void ParseConfigFile(const char* directory, SkTDArray<FontFamily*>* families) {
SkString file_path = SkOSPath::Join(directory, kConfigFile);
SkAutoTUnref<SkStream> file_stream(SkStream::NewFromFile(file_path.c_str()));
if (file_stream == NULL) {
LOG(ERROR) << "---- Failed to open %s", file_path.c_str();
return;
}
SkAutoDataUnref file_data(
SkData::NewFromStream(file_stream, file_stream->getLength()));
if (file_data == NULL) {
LOG(ERROR) << "---- Failed to read %s", file_path.c_str();
return;
}
FamilyData family_data(families);
xmlSAXUserParseMemory(&xml_sax_handler, &family_data,
static_cast<const char*>(file_data->data()),
static_cast<int>(file_data->size()));
}
} // namespace
namespace SkFontConfigParser {
// Loads data on font families from the configuration file. The resulting data
// is returned in the given fontFamilies array.
void GetFontFamilies(const char* directory,
SkTDArray<FontFamily*>* font_families) {
ParseConfigFile(directory, font_families);
}
} // namespace SkFontConfigParser