blob: 3c7aeedb1c2863a2870c510e3b96ce39c1bd30c1 [file] [log] [blame]
// Copyright 2016 The Cobalt Authors. 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 <memory>
#include <stack>
#include <string>
#include "SkData.h"
#include "SkOSFile.h"
#include "SkOSPath.h"
#include "SkStream.h"
#include "SkTSearch.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.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;
base::TrimWhitespaceASCII(formatted_message, base::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) {
static_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 (!base::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) {
static_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 (base::IsAsciiWhitespace(*s)) {
++s;
if (!*s) {
return true;
}
}
for (int i = 0; i <= 1; ++i) {
if (!base::IsAsciiDigit(*s)) {
LOG(ERROR)
<< "---- ParsePageRangeList error: non-ascii digit page range";
return false;
}
int16 n = 0;
for (; *s; ++s) {
if (!base::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 (base::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 ParserContext structure is passed around by the parser so that each
// handler can read these variables that are relevant to the current parsing.
struct ParserContext {
explicit ParserContext(SkTDArray<FontFamilyInfo*>* families_array)
: families(families_array), current_font_info(NULL) {}
// The array that each family is put into as it is parsed
SkTDArray<FontFamilyInfo*>* families;
// The current family being created. FamilyData owns this object while it is
// not NULL.
std::unique_ptr<FontFamilyInfo> current_family;
// The current FontFileInfo being created. It is owned by FontFamilyInfo.
FontFileInfo* current_font_info;
// Contains all of the elements that are actively being parsed.
std::stack<ElementType> element_stack;
};
void FamilyElementHandler(FontFamilyInfo* 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();
}
}
FontFamilyInfo* FindFamily(ParserContext* context, const char* family_name) {
size_t name_len = strlen(family_name);
for (int i = 0; i < context->families->count(); i++) {
FontFamilyInfo* candidate = (*context->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(ParserContext* context, 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
FontFamilyInfo* target_family = FindFamily(context, 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* context, const xmlChar* xml_tag,
const xmlChar** xml_attribute_pairs) {
ParserContext* parser_context = reinterpret_cast<ParserContext*>(context);
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) {
parser_context->element_stack.push(kFamilyElementType);
parser_context->current_family = base::WrapUnique(new FontFamilyInfo());
FamilyElementHandler(parser_context->current_family.get(), attribute_pairs);
} else if (tag_len == 4 && strncmp("font", tag, tag_len) == 0) {
parser_context->element_stack.push(kFontElementType);
FontFileInfo* file = &parser_context->current_family->fonts.push_back();
parser_context->current_font_info = file;
FontElementHandler(file, attribute_pairs);
} else if (tag_len == 5 && strncmp("alias", tag, tag_len) == 0) {
parser_context->element_stack.push(kAliasElementType);
AliasElementHandler(parser_context, attribute_pairs);
} else {
parser_context->element_stack.push(kOtherElementType);
}
}
void EndElement(void* context, const xmlChar* xml_tag) {
ParserContext* parser_context = reinterpret_cast<ParserContext*>(context);
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 (parser_context->current_family != NULL) {
*parser_context->families->append() =
parser_context->current_family.release();
} else {
LOG(ERROR) << "---- Encountered end family tag with no current family";
NOTREACHED();
}
}
parser_context->element_stack.pop();
}
void Characters(void* context, const xmlChar* xml_characters, int len) {
ParserContext* parser_context = reinterpret_cast<ParserContext*>(context);
const char* characters = reinterpret_cast<const char*>(xml_characters);
if (parser_context->element_stack.size() > 0 &&
parser_context->element_stack.top() == kFontElementType) {
parser_context->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<FontFamilyInfo*>* families) {
SkString file_path = SkOSPath::Join(directory, kConfigFile);
std::unique_ptr<SkStream> file_stream(
SkStream::MakeFromFile(file_path.c_str()));
if (file_stream == NULL) {
LOG(ERROR) << "---- Failed to open " << file_path.c_str();
return;
}
sk_sp<SkData> file_data(
SkData::MakeFromStream(file_stream.get(), file_stream->getLength()));
if (file_data == NULL) {
LOG(ERROR) << "---- Failed to read " << file_path.c_str();
return;
}
ParserContext parser_context(families);
int return_value =
xmlSAXUserParseMemory(&xml_sax_handler, &parser_context,
static_cast<const char*>(file_data->data()),
static_cast<int>(file_data->size()));
DCHECK_EQ(return_value, 0);
}
} // 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<FontFamilyInfo*>* font_families) {
ParseConfigFile(directory, font_families);
}
} // namespace SkFontConfigParser