From d5a183e14d42560632a6aa270aede226215bb3d3 Mon Sep 17 00:00:00 2001 From: John Mark Bell Date: Sun, 4 Dec 2011 21:06:24 +0000 Subject: @font-face support. Credit: James Montgomerie Things missing: parser tests; the following descriptors: font-feature-settings, font-stretch, font-variant, unicode-range. svn path=/trunk/libcss/; revision=13244 --- src/parse/Makefile | 2 +- src/parse/font_face.c | 413 +++++++++++++++++++++++++++++++++++++++++++ src/parse/font_face.h | 22 +++ src/parse/language.c | 42 ++++- src/parse/properties/utils.c | 45 ++++- src/parse/properties/utils.h | 14 +- src/parse/propstrings.c | 10 ++ src/parse/propstrings.h | 5 +- src/select/Makefile | 2 +- src/select/font_face.c | 251 ++++++++++++++++++++++++++ src/select/font_face.h | 53 ++++++ src/select/select.c | 329 ++++++++++++++++++++++++++++++++-- src/stylesheet.c | 26 +-- src/stylesheet.h | 2 +- 14 files changed, 1169 insertions(+), 47 deletions(-) create mode 100644 src/parse/font_face.c create mode 100644 src/parse/font_face.h create mode 100644 src/select/font_face.c create mode 100644 src/select/font_face.h (limited to 'src') diff --git a/src/parse/Makefile b/src/parse/Makefile index a7548a8..3d1df42 100644 --- a/src/parse/Makefile +++ b/src/parse/Makefile @@ -1,4 +1,4 @@ # Sources -DIR_SOURCES := parse.c language.c important.c propstrings.c +DIR_SOURCES := parse.c language.c important.c propstrings.c font_face.c include build/makefiles/Makefile.subdir diff --git a/src/parse/font_face.c b/src/parse/font_face.c new file mode 100644 index 0000000..14a715d --- /dev/null +++ b/src/parse/font_face.c @@ -0,0 +1,413 @@ +/* + * This file is part of LibCSS. + * Licensed under the MIT License, + * http://www.opensource.org/licenses/mit-license.php + * Copyright 2011 Things Made Out Of Other Things Ltd. + * Written by James Montgomerie + */ + +#include "parse/font_face.h" + +#include +#include + +#include "parse/propstrings.h" +#include "parse/properties/utils.h" +#include "select/font_face.h" + +static bool font_rule_font_family_reserved(css_language *c, + const css_token *ident) +{ + bool match; + + return (lwc_string_caseless_isequal(ident->idata, c->strings[SERIF], + &match) == lwc_error_ok && match) || + (lwc_string_caseless_isequal(ident->idata, + c->strings[SANS_SERIF], &match) == lwc_error_ok && + match) || + (lwc_string_caseless_isequal(ident->idata, c->strings[CURSIVE], + &match) == lwc_error_ok && match) || + (lwc_string_caseless_isequal(ident->idata, c->strings[FANTASY], + &match) == lwc_error_ok && match) || + (lwc_string_caseless_isequal(ident->idata, + c->strings[MONOSPACE], &match) == lwc_error_ok && + match) || + (lwc_string_caseless_isequal(ident->idata, c->strings[INHERIT], + &match) == lwc_error_ok && match) || + (lwc_string_caseless_isequal(ident->idata, c->strings[INITIAL], + &match) == lwc_error_ok && match) || + (lwc_string_caseless_isequal(ident->idata, c->strings[DEFAULT], + &match) == lwc_error_ok && match); +} + +static css_error font_face_parse_font_family(css_language *c, + const parserutils_vector *vector, int *ctx, + css_font_face *font_face) +{ + css_error error; + lwc_string *string; + + error = css__ident_list_or_string_to_string(c, vector, ctx, + font_rule_font_family_reserved, &string); + if (error != CSS_OK) + return error; + + css__font_face_set_font_family(font_face, string); + + lwc_string_unref(string); + + return CSS_OK; +} + +static css_error font_face_src_parse_format(css_language *c, + const parserutils_vector *vector, int *ctx, + css_font_face_format *format) +{ + bool match; + const css_token *token; + + *format = CSS_FONT_FACE_FORMAT_UNSPECIFIED; + + /* 'format(' STRING [ ',' STRING ]* ')' + * + * 'format(' already consumed + */ + + do { + consumeWhitespace(vector, ctx); + + token = parserutils_vector_iterate(vector, ctx); + if (token == NULL || token->type != CSS_TOKEN_STRING) + return CSS_INVALID; + + if (lwc_string_isequal(token->idata, + c->strings[WOFF], &match) == lwc_error_ok && + match) { + *format |= CSS_FONT_FACE_FORMAT_WOFF; + } else if ((lwc_string_isequal(token->idata, + c->strings[TRUETYPE], &match) == lwc_error_ok && + match) || + (lwc_string_isequal(token->idata, + c->strings[OPENTYPE], &match) == lwc_error_ok && + match)) { + *format |= CSS_FONT_FACE_FORMAT_OPENTYPE; + } else if (lwc_string_isequal(token->idata, + c->strings[EMBEDDED_OPENTYPE], + &match) == lwc_error_ok && match) { + *format |= CSS_FONT_FACE_FORMAT_EMBEDDED_OPENTYPE; + } else if (lwc_string_isequal(token->idata, + c->strings[SVG], &match) == lwc_error_ok && + match) { + *format |= CSS_FONT_FACE_FORMAT_SVG; + } else { + /* The spec gives a list of possible strings, which + * hints that unknown strings should be parse errors, + * but it also talks about "unknown font formats", + * so we treat any string we don't know not as a parse + * error, but as indicating an "unknown font format". + */ + *format |= CSS_FONT_FACE_FORMAT_UNKNOWN; + } + + consumeWhitespace(vector, ctx); + token = parserutils_vector_iterate(vector, ctx); + } while (token != NULL && tokenIsChar(token, ',')); + + if (token == NULL || tokenIsChar(token, ')') == false) + return CSS_INVALID; + + return CSS_OK; +} + +static css_error font_face_src_parse_spec_or_name(css_language *c, + const parserutils_vector *vector, int *ctx, + lwc_string **location, + css_font_face_location_type *location_type, + css_font_face_format *format) +{ + const css_token *token; + css_error error; + bool match; + + /* spec-or-name ::= font-face-spec | font-face-name + * font-face-spec ::= URI [ 'format(' STRING [ ',' STRING ]* ')' ]? + * font-face-name ::= 'local(' ident-list-or-string ')' + * ident-list-or-string ::= IDENT IDENT* | STRING + */ + + consumeWhitespace(vector, ctx); + + token = parserutils_vector_iterate(vector, ctx); + if (token == NULL) + return CSS_INVALID; + + if (token->type == CSS_TOKEN_URI) { + error = c->sheet->resolve(c->sheet->resolve_pw, + c->sheet->url, token->idata, + location); + if (error != CSS_OK) + return error; + + *location_type = CSS_FONT_FACE_LOCATION_TYPE_URI; + + consumeWhitespace(vector, ctx); + + token = parserutils_vector_peek(vector, *ctx); + if (token != NULL && token->type == CSS_TOKEN_FUNCTION && + lwc_string_caseless_isequal(token->idata, + c->strings[FORMAT], &match) == lwc_error_ok && + match) { + parserutils_vector_iterate(vector, ctx); + + error = font_face_src_parse_format(c, vector, ctx, + format); + if (error != CSS_OK) { + lwc_string_unref(*location); + return error; + } + } + } else if (token->type == CSS_TOKEN_FUNCTION && + lwc_string_caseless_isequal(token->idata, + c->strings[LOCAL], + &match) == lwc_error_ok && match) { + consumeWhitespace(vector, ctx); + + error = css__ident_list_or_string_to_string( + c, vector, ctx, NULL, location); + if (error != CSS_OK) + return error; + + consumeWhitespace(vector, ctx); + + token = parserutils_vector_iterate(vector, ctx); + if (token == NULL || tokenIsChar(token, ')') == false) { + lwc_string_unref(*location); + return CSS_INVALID; + } + + *location_type = CSS_FONT_FACE_LOCATION_TYPE_LOCAL; + } else { + return CSS_INVALID; + } + + return CSS_OK; +} + +static css_error font_face_parse_src(css_language *c, + const parserutils_vector *vector, int *ctx, + css_font_face *font_face) +{ + int orig_ctx = *ctx; + css_error error = CSS_OK; + const css_token *token; + css_font_face_src *srcs = NULL, *new_srcs = NULL; + uint32_t n_srcs = 0; + + /* src ::= spec-or-name [ ',' spec-or-name ]* + * spec-or-name ::= font-face-spec | font-face-name + * font-face-spec ::= URI [ 'format(' STRING [ ',' STRING ]* ')' ]? + * font-face-name ::= 'local(' ident-list-or-string ')' + * ident-list-or-string ::= IDENT IDENT* | STRING + */ + + /* Create one css_font_face_src for each consecutive location and + * [potentially] type pair in the comma-separated list + */ + do { + lwc_string *location; + css_font_face_location_type location_type = + CSS_FONT_FACE_LOCATION_TYPE_UNSPECIFIED; + css_font_face_format format = + CSS_FONT_FACE_FORMAT_UNSPECIFIED; + + error = font_face_src_parse_spec_or_name(c, vector, ctx, + &location, &location_type, &format); + if (error != CSS_OK) + goto cleanup; + + /* This will be inefficient if there are a lot of locations - + * probably not a problem in practice. + */ + new_srcs = c->alloc(srcs, + (n_srcs + 1) * sizeof(css_font_face_src), + c->pw); + if (new_srcs == NULL) { + error = CSS_NOMEM; + goto cleanup; + } + srcs = new_srcs; + + srcs[n_srcs].location = location; + srcs[n_srcs].bits[0] = format << 2 | location_type; + + ++n_srcs; + + consumeWhitespace(vector, ctx); + token = parserutils_vector_iterate(vector, ctx); + } while (token != NULL && tokenIsChar(token, ',')); + + error = css__font_face_set_srcs(font_face, srcs, n_srcs); + +cleanup: + if (error != CSS_OK) { + *ctx = orig_ctx; + if (srcs != NULL) + c->alloc(srcs, 0, c->pw); + } + + return error; +} + +static css_error font_face_parse_font_style(css_language *c, + const parserutils_vector *vector, int *ctx, + css_font_face *font_face) +{ + int orig_ctx = *ctx; + css_error error = CSS_OK; + const css_token *token; + enum css_font_style_e style = 0; + bool match; + + /* IDENT(normal, italic, oblique) */ + + token = parserutils_vector_iterate(vector, ctx); + if ((token == NULL) || ((token->type != CSS_TOKEN_IDENT))) { + *ctx = orig_ctx; + return CSS_INVALID; + } + + if ((lwc_string_caseless_isequal(token->idata, + c->strings[NORMAL], &match) == lwc_error_ok && match)) { + style = CSS_FONT_STYLE_NORMAL; + } else if ((lwc_string_caseless_isequal(token->idata, + c->strings[ITALIC], &match) == lwc_error_ok && match)) { + style = CSS_FONT_STYLE_ITALIC; + } else if ((lwc_string_caseless_isequal(token->idata, + c->strings[OBLIQUE], &match) == lwc_error_ok && + match)) { + style = CSS_FONT_STYLE_OBLIQUE; + } else { + error = CSS_INVALID; + } + + if (error == CSS_OK) { + font_face->bits[0] = (font_face->bits[0] & 0xfc) | style; + } else { + *ctx = orig_ctx; + } + + return error; +} + +static css_error font_face_parse_font_weight(css_language *c, + const parserutils_vector *vector, int *ctx, + css_font_face *font_face) +{ + int orig_ctx = *ctx; + css_error error = CSS_OK; + const css_token *token; + enum css_font_weight_e weight = 0; + bool match; + + /* NUMBER (100, 200, 300, 400, 500, 600, 700, 800, 900) | + * IDENT (normal, bold) */ + token = parserutils_vector_iterate(vector, ctx); + if (token == NULL || (token->type != CSS_TOKEN_IDENT && + token->type != CSS_TOKEN_NUMBER)) { + *ctx = orig_ctx; + return CSS_INVALID; + } + + if (token->type == CSS_TOKEN_NUMBER) { + size_t consumed = 0; + css_fixed num = css__number_from_lwc_string(token->idata, + true, &consumed); + /* Invalid if there are trailing characters */ + if (consumed != lwc_string_length(token->idata)) { + *ctx = orig_ctx; + return CSS_INVALID; + } + + switch (FIXTOINT(num)) { + case 100: weight = CSS_FONT_WEIGHT_100; break; + case 200: weight = CSS_FONT_WEIGHT_200; break; + case 300: weight = CSS_FONT_WEIGHT_300; break; + case 400: weight = CSS_FONT_WEIGHT_400; break; + case 500: weight = CSS_FONT_WEIGHT_500; break; + case 600: weight = CSS_FONT_WEIGHT_600; break; + case 700: weight = CSS_FONT_WEIGHT_700; break; + case 800: weight = CSS_FONT_WEIGHT_800; break; + case 900: weight = CSS_FONT_WEIGHT_900; break; + default: error = CSS_INVALID; + } + } else if ((lwc_string_caseless_isequal(token->idata, + c->strings[NORMAL], &match) == lwc_error_ok && match)) { + weight = CSS_FONT_WEIGHT_NORMAL; + } else if ((lwc_string_caseless_isequal(token->idata, + c->strings[BOLD], &match) == lwc_error_ok && match)) { + weight = CSS_FONT_WEIGHT_BOLD; + } else { + error = CSS_INVALID; + } + + if (error == CSS_OK) { + font_face->bits[0] = (font_face->bits[0] & 0xc3) | + (weight << 2); + } else { + *ctx = orig_ctx; + } + + return error; +} + +/** + * Parse a descriptor in an @font-face rule + * + * \param c Parsing context + * \param descriptor Token for this descriptor + * \param vector Vector of tokens to process + * \param ctx Pointer to vector iteration context + * \param rule Rule to process descriptor into + * \return CSS_OK on success, + * CSS_BADPARM on bad parameters, + * CSS_INVALID on invalid syntax, + * CSS_NOMEM on memory exhaustion + */ +css_error css__parse_font_descriptor(css_language *c, + const css_token *descriptor, const parserutils_vector *vector, + int *ctx, css_rule_font_face *rule) +{ + css_font_face *font_face = rule->font_face; + css_error error; + bool match; + + if (font_face == NULL) { + error = css__font_face_create(c->sheet->alloc, + c->sheet->pw, &font_face); + if (error != CSS_OK) { + return error; + } + + rule->font_face = font_face; + } + + if (lwc_string_caseless_isequal(descriptor->idata, + c->strings[FONT_FAMILY], &match) == lwc_error_ok && + match) { + return font_face_parse_font_family(c, vector, ctx, font_face); + } else if (lwc_string_caseless_isequal(descriptor->idata, + c->strings[SRC], &match) == lwc_error_ok && match) { + return font_face_parse_src(c, vector, ctx, font_face); + } else if (lwc_string_caseless_isequal(descriptor->idata, + c->strings[FONT_STYLE], &match) == lwc_error_ok && + match) { + return font_face_parse_font_style(c, vector, ctx, font_face); + } else if (lwc_string_caseless_isequal(descriptor->idata, + c->strings[FONT_WEIGHT], &match) == lwc_error_ok && + match) { + return font_face_parse_font_weight(c, vector, ctx, font_face); + } + + return CSS_INVALID; +} + diff --git a/src/parse/font_face.h b/src/parse/font_face.h new file mode 100644 index 0000000..435380e --- /dev/null +++ b/src/parse/font_face.h @@ -0,0 +1,22 @@ +/* + * This file is part of LibCSS. + * Licensed under the MIT License, + * http://www.opensource.org/licenses/mit-license.php + * Copyright 2011 Things Made Out Of Other Things Ltd. + * Written by James Montgomerie + */ + +#ifndef css_parse_font_face_h_ +#define css_parse_font_face_h_ + +#include + +#include "stylesheet.h" +#include "lex/lex.h" +#include "parse/language.h" + +css_error css__parse_font_descriptor(css_language *c, + const css_token *descriptor, const parserutils_vector *vector, + int *ctx, struct css_rule_font_face *rule); + +#endif diff --git a/src/parse/language.c b/src/parse/language.c index 0436c22..a5fce0e 100644 --- a/src/parse/language.c +++ b/src/parse/language.c @@ -12,6 +12,7 @@ #include "stylesheet.h" #include "lex/lex.h" +#include "parse/font_face.h" #include "parse/important.h" #include "parse/language.h" #include "parse/parse.h" @@ -554,6 +555,26 @@ css_error handleStartAtRule(css_language *c, const parserutils_vector *vector) return error; } + /* Rule is now owned by the sheet, + * so no need to destroy it */ + + c->state = HAD_RULE; + } else if (lwc_string_caseless_isequal(atkeyword->idata, + c->strings[FONT_FACE], &match) == lwc_error_ok && + match) { + error = css__stylesheet_rule_create(c->sheet, + CSS_RULE_FONT_FACE, &rule); + if (error != CSS_OK) + return error; + + consumeWhitespace(vector, &ctx); + + error = css__stylesheet_add_rule(c->sheet, rule, NULL); + if (error != CSS_OK) { + css__stylesheet_rule_destroy(c->sheet, rule); + return error; + } + /* Rule is now owned by the sheet, * so no need to destroy it */ @@ -693,9 +714,9 @@ css_error handleBlockContent(css_language *c, const parserutils_vector *vector) context_entry *entry; css_rule *rule; - /* In CSS 2.1, block content comprises either declarations (if the - * current block is associated with @page or a selector), or rulesets - * (if the current block is associated with @media). */ + /* Block content comprises either declarations (if the current block is + * associated with @page, @font-face or a selector), or rulesets (if the + * current block is associated with @media). */ entry = parserutils_stack_get_current(c->context); if (entry == NULL || entry->data == NULL) @@ -704,7 +725,8 @@ css_error handleBlockContent(css_language *c, const parserutils_vector *vector) rule = entry->data; if (rule == NULL || (rule->type != CSS_RULE_SELECTOR && rule->type != CSS_RULE_PAGE && - rule->type != CSS_RULE_MEDIA)) + rule->type != CSS_RULE_MEDIA && + rule->type != CSS_RULE_FONT_FACE)) return CSS_INVALID; if (rule->type == CSS_RULE_MEDIA) { @@ -729,6 +751,7 @@ css_error handleDeclaration(css_language *c, const parserutils_vector *vector) /* Locations where declarations are permitted: * * + In @page + * + In @font-face * + In ruleset */ entry = parserutils_stack_get_current(c->context); @@ -737,7 +760,8 @@ css_error handleDeclaration(css_language *c, const parserutils_vector *vector) rule = entry->data; if (rule == NULL || (rule->type != CSS_RULE_SELECTOR && - rule->type != CSS_RULE_PAGE)) + rule->type != CSS_RULE_PAGE && + rule->type != CSS_RULE_FONT_FACE)) return CSS_INVALID; /* Strip any leading whitespace (can happen if in nested block) */ @@ -759,7 +783,13 @@ css_error handleDeclaration(css_language *c, const parserutils_vector *vector) consumeWhitespace(vector, &ctx); - error = parseProperty(c, ident, vector, &ctx, rule); + if (rule->type == CSS_RULE_FONT_FACE) { + css_rule_font_face * ff_rule = (css_rule_font_face *) rule; + error = css__parse_font_descriptor( + c, ident, vector, &ctx, ff_rule); + } else { + error = parseProperty(c, ident, vector, &ctx, rule); + } if (error != CSS_OK) return error; diff --git a/src/parse/properties/utils.c b/src/parse/properties/utils.c index bf7e212..4eca6fc 100644 --- a/src/parse/properties/utils.c +++ b/src/parse/properties/utils.c @@ -1043,6 +1043,45 @@ css_error css__parse_unit_keyword(const char *ptr, size_t len, css_unit *unit) return CSS_OK; } +/** + * Create a string from a list of IDENT/S tokens if the next token is IDENT + * or references the next token's string if it is a STRING + * + * \param c Parsing context + * \param vector Vector containing tokens + * \param ctx Vector iteration context + * \param reserved Callback to determine if an identifier is reserved + * \param result Pointer to location to receive resulting string + * \return CSS_OK on success, appropriate error otherwise. + * + * Post condition: \a *ctx is updated with the next token to process + * If the input is invalid, then \a *ctx remains unchanged. + * + * The resulting string's reference is passed to the caller + */ +css_error css__ident_list_or_string_to_string(css_language *c, + const parserutils_vector *vector, int *ctx, + bool (*reserved)(css_language *c, const css_token *ident), + lwc_string **result) +{ + const css_token *token; + + token = parserutils_vector_peek(vector, *ctx); + if (token == NULL) + return CSS_INVALID; + + if (token->type == CSS_TOKEN_STRING) { + token = parserutils_vector_iterate(vector, ctx); + *result = lwc_string_ref(token->idata); + return CSS_OK; + } else if(token->type == CSS_TOKEN_IDENT) { + return css__ident_list_to_string(c, vector, ctx, reserved, + result); + } + + return CSS_INVALID; +} + /** * Create a string from a list of IDENT/S tokens * @@ -1058,7 +1097,7 @@ css_error css__parse_unit_keyword(const char *ptr, size_t len, css_unit *unit) * * The resulting string's reference is passed to the caller */ -static css_error ident_list_to_string(css_language *c, +css_error css__ident_list_to_string(css_language *c, const parserutils_vector *vector, int *ctx, bool (*reserved)(css_language *c, const css_token *ident), lwc_string **result) @@ -1084,7 +1123,7 @@ static css_error ident_list_to_string(css_language *c, token->type == CSS_TOKEN_S)) { if (token->type == CSS_TOKEN_IDENT) { /* IDENT -- if reserved, reject style */ - if (reserved(c, token)) { + if (reserved != NULL && reserved(c, token)) { error = CSS_INVALID; goto cleanup; } @@ -1175,7 +1214,7 @@ css_error css__comma_list_to_style(css_language *c, *ctx = prev_ctx; - error = ident_list_to_string(c, vector, ctx, + error = css__ident_list_to_string(c, vector, ctx, reserved, &str); if (error != CSS_OK) goto cleanup; diff --git a/src/parse/properties/utils.h b/src/parse/properties/utils.h index ab045bd..be903ee 100644 --- a/src/parse/properties/utils.h +++ b/src/parse/properties/utils.h @@ -181,10 +181,22 @@ css_error css__parse_unit_specifier(css_language *c, css_error css__parse_unit_keyword(const char *ptr, size_t len, css_unit *unit); +css_error css__ident_list_or_string_to_string(css_language *c, + const parserutils_vector *vector, int *ctx, + bool (*reserved)(css_language *c, const css_token *ident), + lwc_string **result); + +css_error css__ident_list_to_string(css_language *c, + const parserutils_vector *vector, int *ctx, + bool (*reserved)(css_language *c, const css_token *ident), + lwc_string **result); + css_error css__comma_list_to_style(css_language *c, const parserutils_vector *vector, int *ctx, bool (*reserved)(css_language *c, const css_token *ident), - css_code_t (*get_value)(css_language *c, const css_token *token, bool first), + css_code_t (*get_value)(css_language *c, + const css_token *token, + bool first), css_style *result); #endif diff --git a/src/parse/propstrings.c b/src/parse/propstrings.c index bf1fcd0..dea8816 100644 --- a/src/parse/propstrings.c +++ b/src/parse/propstrings.c @@ -30,6 +30,7 @@ const stringmap_entry stringmap[LAST_KNOWN] = { { "import", SLEN("import") }, { "media", SLEN("media") }, { "namespace", SLEN("namespace") }, + { "font-face", SLEN("font-face") }, { "page", SLEN("page") }, { "aural", SLEN("aural") }, @@ -372,6 +373,15 @@ const stringmap_entry stringmap[LAST_KNOWN] = { { "currentColor", SLEN("currentColor") }, { "odd", SLEN("odd") }, { "even", SLEN("even") }, + { "src", SLEN("src") }, + { "local", SLEN("local") }, + { "initial", SLEN("initial") }, + { "format", SLEN("format") }, + { "woff", SLEN("woff") }, + { "truetype", SLEN("truetype") }, + { "opentype", SLEN("opentype") }, + { "embedded-opentype", SLEN("embedded-opentype") }, + { "svg", SLEN("svg") }, { "aliceblue", SLEN("aliceblue") }, { "antiquewhite", SLEN("antiquewhite") }, diff --git a/src/parse/propstrings.h b/src/parse/propstrings.h index 7c7b693..16affcd 100644 --- a/src/parse/propstrings.h +++ b/src/parse/propstrings.h @@ -15,7 +15,7 @@ enum { UNIVERSAL, /* At-rules */ - CHARSET, LIBCSS_IMPORT, MEDIA, NAMESPACE, PAGE, + CHARSET, LIBCSS_IMPORT, MEDIA, NAMESPACE, FONT_FACE, PAGE, /* Media types */ AURAL, BRAILLE, EMBOSSED, HANDHELD, PRINT, PROJECTION, @@ -88,7 +88,8 @@ enum { W_RESIZE, LIBCSS_TEXT, WAIT, HELP, PROGRESS, SERIF, SANS_SERIF, CURSIVE, FANTASY, MONOSPACE, MALE, FEMALE, CHILD, MIX, UNDERLINE, OVERLINE, LINE_THROUGH, BLINK, RGB, RGBA, HSL, HSLA, LIBCSS_LEFT, LIBCSS_CENTER, - LIBCSS_RIGHT, CURRENTCOLOR, ODD, EVEN, + LIBCSS_RIGHT, CURRENTCOLOR, ODD, EVEN, SRC, LOCAL, INITIAL, + FORMAT, WOFF, TRUETYPE, OPENTYPE, EMBEDDED_OPENTYPE, SVG, /* Named colours */ FIRST_COLOUR, diff --git a/src/select/Makefile b/src/select/Makefile index db1c4e6..b99eab3 100644 --- a/src/select/Makefile +++ b/src/select/Makefile @@ -1,4 +1,4 @@ # Sources -DIR_SOURCES := computed.c dispatch.c hash.c select.c +DIR_SOURCES := computed.c dispatch.c hash.c select.c font_face.c include build/makefiles/Makefile.subdir diff --git a/src/select/font_face.c b/src/select/font_face.c new file mode 100644 index 0000000..ae24827 --- /dev/null +++ b/src/select/font_face.c @@ -0,0 +1,251 @@ +/* + * This file is part of LibCSS. + * Licensed under the MIT License, + * http://www.opensource.org/licenses/mit-license.php + * Copyright 2011 Things Made Out Of Other Things Ltd. + * Written by James Montgomerie + */ + +#include + +#include "select/font_face.h" + +static void font_faces_srcs_destroy(css_font_face *font_face) +{ + uint32_t i; + css_font_face_src *srcs = font_face->srcs; + + for (i = 0; i < font_face->n_srcs; ++i) { + if (srcs[i].location != NULL) { + lwc_string_unref(srcs[i].location); + } + } + + font_face->alloc(srcs, 0, font_face->pw); + font_face->srcs = NULL; +} + +/** + * Create a font-face + * + * \param alloc Memory (de)allocation function + * \param pw Pointer to client-specific data + * \param result Pointer to location to receive result + * \return CSS_OK on success, + * CSS_NOMEM on memory exhaustion, + * CSS_BADPARM on bad parameters. + */ +css_error css__font_face_create(css_allocator_fn alloc, void *pw, + css_font_face **result) +{ + css_font_face *f; + + if (alloc == NULL || result == NULL) + return CSS_BADPARM; + + f = alloc(NULL, sizeof(css_font_face), pw); + if (f == NULL) + return CSS_NOMEM; + + memset(f, 0, sizeof(css_font_face)); + + f->alloc = alloc; + f->pw = pw; + + *result = f; + + return CSS_OK; +} + +/** + * Destroy a font-face + * + * \param font_face Font-face to destroy + * \return CSS_OK on success, appropriate error otherwise + */ +css_error css__font_face_destroy(css_font_face *font_face) +{ + if (font_face == NULL) + return CSS_BADPARM; + + if (font_face->font_family != NULL) + lwc_string_unref(font_face->font_family); + + if (font_face->srcs != NULL) + font_faces_srcs_destroy(font_face); + + font_face->alloc(font_face, 0, font_face->pw); + + return CSS_OK; +} + + +/** + * Set a font-face's font-family name + * + * \param font_face The font-face + * \param font_family Font-family name + * \param result Pointer to location to receive result + * \return CSS_OK on success, + * CSS_BADPARM on bad parameters. + */ +css_error css__font_face_set_font_family(css_font_face *font_face, + lwc_string *font_family) +{ + if (font_face == NULL || font_family == NULL) + return CSS_BADPARM; + + if (font_face->font_family != NULL) + lwc_string_unref(font_face->font_family); + + font_face->font_family = lwc_string_ref(font_family); + + return CSS_OK; +} + +/** + * Get a font-face's font-family name + * + * \param font_face The font-face + * \param result Pointer to location to receive result + * \return CSS_OK on success, + * CSS_BADPARM on bad parameters. + */ +css_error css_font_face_get_font_family(const css_font_face *font_face, + lwc_string **font_family) +{ + if (font_face == NULL || font_family == NULL) + return CSS_BADPARM; + + *font_family = font_face->font_family; + + return CSS_OK; +} + +/** + * Get the style of font for a font-face. + * + * \param src The font-face + * \return The style, as a css_font_style_e + */ +uint8_t css_font_face_font_style(const css_font_face *font_face) +{ + return font_face->bits[0] & 0x3; +} + +/** + * Get the weight of font for a font-face. + * + * \param src The font-face + * \return The style, as a css_font_weight_e + */ +uint8_t css_font_face_font_weight(const css_font_face *font_face) +{ + return (font_face->bits[0] >> 2) & 0xf; +} + +/** + * Get the number of potential src locations for a font-face + * + * \param font_face The font-face + * \param count Pointer to location to receive result + * \return CSS_OK on success, + * CSS_BADPARM on bad parameters. + */ +css_error css_font_face_count_srcs(const css_font_face *font_face, + uint32_t *count) +{ + if (font_face == NULL || count == NULL) + return CSS_BADPARM; + + *count = font_face->n_srcs; + return CSS_OK; +} + +/** + * Get a specific src location from a font-face + * + * \param font_face The font-face + * \param index The index for the wanted src. + * \param count Pointer to location to receive result + * \return CSS_OK on success, + * CSS_BADPARM on bad parameters. + */ +css_error css_font_face_get_src(const css_font_face *font_face, + uint32_t index, const css_font_face_src **src) +{ + if (font_face == NULL || src == NULL || index >= font_face->n_srcs) + return CSS_BADPARM; + + *src = &(font_face->srcs[index]); + + return CSS_OK; +} + +/** + * Get the location for a font-face src. + * + * \param font_face The font-face + * \param count Pointer to location to receive result + * \return CSS_OK on success, + * CSS_BADPARM on bad parameters. + * + * \note The type of location (local or URL) can be gathered from + * css_font_face_src_location_type, and the format of font (if specified) + * from css_font_face_src_format. + */ +css_error css_font_face_src_get_location(const css_font_face_src *src, + lwc_string **location) +{ + if (src == NULL || location == NULL) + return CSS_BADPARM; + + *location = src->location; + + return CSS_OK; +} + +/** + * Get the location type for a font-face src. + * + * \param src The font-face src + * \return The location type + */ +css_font_face_location_type css_font_face_src_location_type( + const css_font_face_src *src) +{ + return src->bits[0] & 0x3; +} + +/** + * Get the format of font for a font-face src. + * + * \param src The font-face src + * \return The format, if specified + */ +css_font_face_format css_font_face_src_format(const css_font_face_src *src) +{ + return (src->bits[0] >> 2) & 0x1f; +} + +/** + * Set a font-faces array of srcs. + * + * \param font_face The font-face + * \param srcs The array of css_font_face_srcs + * \param n_srcs The count of css_font_face_srcs in the array + * \return The format, if specified + */ +css_error css__font_face_set_srcs(css_font_face *font_face, + css_font_face_src *srcs, uint32_t n_srcs) +{ + if (font_face->srcs != NULL) + font_faces_srcs_destroy(font_face); + + font_face->srcs = srcs; + font_face->n_srcs = n_srcs; + + return CSS_OK; +} + + diff --git a/src/select/font_face.h b/src/select/font_face.h new file mode 100644 index 0000000..3572de8 --- /dev/null +++ b/src/select/font_face.h @@ -0,0 +1,53 @@ +/* + * This file is part of LibCSS. + * Licensed under the MIT License, + * http://www.opensource.org/licenses/mit-license.php + * Copyright 2011 Things Made Out Of Other Things Ltd. + * Written by James Montgomerie + */ + +#ifndef css_select_font_face_h_ +#define css_select_font_face_h_ + +#include + + +struct css_font_face_src { + lwc_string *location; + /* + * Bit allocations: + * + * 76543210 + * 1 _fffffll format | location type + */ + uint8_t bits[1]; +}; + +struct css_font_face { + lwc_string *font_family; + css_font_face_src *srcs; + uint32_t n_srcs; + + /* + * Bit allocations: + * + * 76543210 + * 1 __wwwwss font-weight | font-style + */ + uint8_t bits[1]; + + css_allocator_fn alloc; + void *pw; +}; + +css_error css__font_face_create(css_allocator_fn alloc, void *pw, + css_font_face **result); +css_error css__font_face_destroy(css_font_face *font_face); + +css_error css__font_face_set_font_family(css_font_face *font_face, + lwc_string *font_family); + +css_error css__font_face_set_srcs(css_font_face *font_face, + css_font_face_src *srcs, uint32_t n_srcs); + +#endif diff --git a/src/select/select.c b/src/select/select.c index 7baac03..4243140 100644 --- a/src/select/select.c +++ b/src/select/select.c @@ -8,6 +8,8 @@ #include #include +#include + #include #include "bytecode/bytecode.h" @@ -17,6 +19,7 @@ #include "select/dispatch.h" #include "select/hash.h" #include "select/propset.h" +#include "select/font_face.h" #include "select/select.h" #include "utils/parserutilserror.h" #include "utils/utils.h" @@ -74,6 +77,27 @@ struct css_select_ctx { lwc_string *after; }; +/** + * Container for selected font faces + */ +typedef struct css_select_font_faces_list { + const css_font_face **font_faces; + size_t count; +} css_select_font_faces_list; + +/** + * Font face selection state + */ +typedef struct css_select_font_faces_state { + lwc_string *font_family; + uint64_t media; + + css_select_font_faces_list ua_font_faces; + css_select_font_faces_list user_font_faces; + css_select_font_faces_list author_font_faces; +} css_select_font_faces_state; + + static css_error set_hint(css_select_state *state, uint32_t prop); static css_error set_initial(css_select_state *state, uint32_t prop, css_pseudo_element pseudo, @@ -104,6 +128,11 @@ static css_error match_detail(css_select_ctx *ctx, void *node, bool *match, css_pseudo_element *pseudo_element); static css_error cascade_style(const css_style *style, css_select_state *state); +static css_error select_font_faces_from_sheet(css_select_ctx *ctx, + const css_stylesheet *sheet, + css_origin origin, + css_select_font_faces_state *state); + #ifdef DEBUG_CHAIN_MATCHING static void dump_chain(const css_selector *selector); #endif @@ -558,6 +587,144 @@ css_error css_select_results_destroy(css_select_results *results) return CSS_OK; } +/** + * Search a selection context for defined font faces + * + * \param ctx Selection context + * \param media Currently active media types + * \param font_family Font family to search for + * \param result Pointer to location to receive result + * \return CSS_OK on success, appropriate error otherwise. + */ +css_error css_select_font_faces(css_select_ctx *ctx, + uint64_t media, lwc_string *font_family, + css_select_font_faces_results **result) +{ + uint32_t i; + css_error error; + css_select_font_faces_state state; + uint32_t n_font_faces; + + if (ctx == NULL || font_family == NULL || result == NULL) + return CSS_BADPARM; + + memset(&state, 0, sizeof(css_select_font_faces_state)); + state.font_family = font_family; + state.media = media; + + /* Iterate through the top-level stylesheets, selecting font-faces + * from those which apply to our current media requirements and + * are not disabled */ + for (i = 0; i < ctx->n_sheets; i++) { + const css_select_sheet s = ctx->sheets[i]; + + if ((s.media & media) != 0 && + s.sheet->disabled == false) { + error = select_font_faces_from_sheet(ctx, s.sheet, + s.origin, &state); + if (error != CSS_OK) + goto cleanup; + } + } + + n_font_faces = state.ua_font_faces.count + + state.user_font_faces.count + + state.author_font_faces.count; + + + if (n_font_faces > 0) { + /* We found some matching faces. Make a results structure with + * the font faces in priority order. */ + css_select_font_faces_results *results; + + results = ctx->alloc(NULL, + sizeof(css_select_font_faces_results), + ctx->pw); + if (results == NULL) { + error = CSS_NOMEM; + goto cleanup; + } + + results->alloc = ctx->alloc; + results->pw = ctx->pw; + + results->font_faces = ctx->alloc(NULL, + n_font_faces * sizeof(css_font_face *), + ctx->pw); + if (results->font_faces == NULL) { + ctx->alloc(results, 0, ctx->pw); + error = CSS_NOMEM; + goto cleanup; + } + + results->n_font_faces = n_font_faces; + + i = 0; + if (state.ua_font_faces.count != 0) { + memcpy(results->font_faces, + state.ua_font_faces.font_faces, + sizeof(css_font_face *) * + state.ua_font_faces.count); + + i += state.ua_font_faces.count; + } + + if (state.user_font_faces.count != 0) { + memcpy(results->font_faces + i, + state.user_font_faces.font_faces, + sizeof(css_font_face *) * + state.user_font_faces.count); + i += state.user_font_faces.count; + } + + if (state.author_font_faces.count != 0) { + memcpy(results->font_faces + i, + state.author_font_faces.font_faces, + sizeof(css_font_face *) * + state.author_font_faces.count); + } + + *result = results; + } + + error = CSS_OK; + +cleanup: + if (state.ua_font_faces.count != 0) + ctx->alloc(state.ua_font_faces.font_faces, 0, ctx->pw); + + if (state.user_font_faces.count != 0) + ctx->alloc(state.user_font_faces.font_faces, 0, ctx->pw); + + if (state.author_font_faces.count != 0) + ctx->alloc(state.author_font_faces.font_faces, 0, ctx->pw); + + return error; +} + +/** + * Destroy a font-face result set + * + * \param results Result set to destroy + * \return CSS_OK on success, appropriate error otherwise + */ +css_error css_select_font_faces_results_destroy( + css_select_font_faces_results *results) +{ + if (results == NULL) + return CSS_BADPARM; + + if (results->font_faces != NULL) { + /* Don't destroy the individual css_font_faces, they're owned + by their respective sheets */ + results->alloc(results->font_faces, 0, results->pw); + } + + results->alloc(results, 0, results->pw); + + return CSS_OK; +} + /****************************************************************************** * Selection engine internals below here * ******************************************************************************/ @@ -870,13 +1037,14 @@ css_error set_initial(css_select_state *state, return CSS_OK; } +#define IMPORT_STACK_SIZE 256 + css_error select_from_sheet(css_select_ctx *ctx, const css_stylesheet *sheet, css_origin origin, css_select_state *state) { const css_stylesheet *s = sheet; const css_rule *rule = s->rule_list; uint32_t sp = 0; -#define IMPORT_STACK_SIZE 256 const css_rule *import_stack[IMPORT_STACK_SIZE]; do { @@ -894,7 +1062,8 @@ css_error select_from_sheet(css_select_ctx *ctx, const css_stylesheet *sheet, if (import->sheet != NULL && (import->media & state->media) != 0) { /* It's applicable, so process it */ - assert(sp < IMPORT_STACK_SIZE - 1); + if (sp >= IMPORT_STACK_SIZE) + return CSS_NOMEM; import_stack[sp++] = rule; @@ -930,6 +1099,144 @@ css_error select_from_sheet(css_select_ctx *ctx, const css_stylesheet *sheet, return CSS_OK; } +static inline bool _rule_applies_to_media(const css_rule *rule, uint64_t media) +{ + bool applies = true; + const css_rule *ancestor = rule; + + while (ancestor != NULL) { + const css_rule_media *m = (const css_rule_media *) ancestor; + + if (ancestor->type == CSS_RULE_MEDIA && + (m->media & media) == 0) { + applies = false; + break; + } + + if (ancestor->ptype != CSS_RULE_PARENT_STYLESHEET) + ancestor = ancestor->parent; + else + ancestor = NULL; + } + + return applies; +} + +static css_error _select_font_face_from_rule(css_select_ctx *ctx, + const css_rule_font_face *rule, css_origin origin, + css_select_font_faces_state *state) +{ + if (_rule_applies_to_media((const css_rule *) rule, state->media)) { + bool correct_family = false; + + if (lwc_string_isequal( + rule->font_face->font_family, + state->font_family, + &correct_family) == lwc_error_ok && + correct_family) { + css_select_font_faces_list *faces = NULL; + const css_font_face **new_faces; + uint32_t index; + size_t new_size; + + switch (origin) { + case CSS_ORIGIN_UA: + faces = &state->ua_font_faces; + break; + case CSS_ORIGIN_USER: + faces = &state->user_font_faces; + break; + case CSS_ORIGIN_AUTHOR: + faces = &state->author_font_faces; + break; + } + + index = faces->count++; + new_size = faces->count * sizeof(css_font_face *); + + new_faces = ctx->alloc(faces->font_faces, + new_size, ctx->pw); + if (new_faces == NULL) { + faces->count = 0; + return CSS_NOMEM; + } + faces->font_faces = new_faces; + + faces->font_faces[index] = rule->font_face; + } + } + + return CSS_OK; +} + +static css_error select_font_faces_from_sheet(css_select_ctx *ctx, + const css_stylesheet *sheet, + css_origin origin, + css_select_font_faces_state *state) +{ + const css_stylesheet *s = sheet; + const css_rule *rule = s->rule_list; + uint32_t sp = 0; + const css_rule *import_stack[IMPORT_STACK_SIZE]; + + do { + /* Find first non-charset rule, if we're at the list head */ + if (rule == s->rule_list) { + while (rule != NULL && rule->type == CSS_RULE_CHARSET) + rule = rule->next; + } + + if (rule != NULL && rule->type == CSS_RULE_IMPORT) { + /* Current rule is an import */ + const css_rule_import *import = + (const css_rule_import *) rule; + + if (import->sheet != NULL && + (import->media & state->media) != 0) { + /* It's applicable, so process it */ + if (sp >= IMPORT_STACK_SIZE) + return CSS_NOMEM; + + import_stack[sp++] = rule; + + s = import->sheet; + rule = s->rule_list; + } else { + /* Not applicable; skip over it */ + rule = rule->next; + } + } else if (rule != NULL && rule->type == CSS_RULE_FONT_FACE) { + css_error error; + + error = _select_font_face_from_rule( + ctx, + (const css_rule_font_face *) rule, + origin, + state); + + if (error != CSS_OK) + return error; + + rule = rule->next; + } else if (rule == NULL) { + /* Find next sheet to process */ + if (sp > 0) { + sp--; + rule = import_stack[sp]->next; + s = import_stack[sp]->parent; + } else { + s = NULL; + } + } else { + rule = rule->next; + } + } while (s != NULL); + + return CSS_OK; +} + +#undef IMPORT_STACK_SIZE + static inline bool _selectors_pending(const css_selector **node, const css_selector **id, const css_selector ***classes, uint32_t n_classes, const css_selector **univ) @@ -1063,8 +1370,6 @@ css_error match_selectors_in_sheet(css_select_ctx *ctx, while (_selectors_pending(node_selectors, id_selectors, class_selectors, n_classes, univ_selectors)) { const css_selector *selector; - css_rule *rule, *parent; - bool process = true; /* Selectors must be matched in ascending order of specificity * and rule index. (c.f. css__outranks_existing()) @@ -1077,21 +1382,7 @@ css_error match_selectors_in_sheet(css_select_ctx *ctx, /* Ignore any selectors contained in rules which are a child * of an @media block that doesn't match the current media * requirements. */ - for (rule = selector->rule; rule != NULL; rule = parent) { - if (rule->type == CSS_RULE_MEDIA && - (((css_rule_media *) rule)->media & - state->media) == 0) { - process = false; - break; - } - - if (rule->ptype != CSS_RULE_PARENT_STYLESHEET) - parent = rule->parent; - else - parent = NULL; - } - - if (process) { + if (_rule_applies_to_media(selector->rule, state->media)) { error = match_selector_chain(ctx, selector, state); if (error != CSS_OK) goto cleanup; diff --git a/src/stylesheet.c b/src/stylesheet.c index f8dedac..e37a00a 100644 --- a/src/stylesheet.c +++ b/src/stylesheet.c @@ -15,6 +15,7 @@ #include "utils/parserutilserror.h" #include "utils/utils.h" #include "select/dispatch.h" +#include "select/font_face.h" static css_error _add_selectors(css_stylesheet *sheet, css_rule *rule); static css_error _remove_selectors(css_stylesheet *sheet, css_rule *rule); @@ -261,7 +262,7 @@ css_error css_stylesheet_create(const css_stylesheet_params *params, */ css_error css_stylesheet_destroy(css_stylesheet *sheet) { - uint32_t string_index; + uint32_t index; css_rule *r, *s; if (sheet == NULL) @@ -296,17 +297,17 @@ css_error css_stylesheet_destroy(css_stylesheet *sheet) css__stylesheet_style_destroy(sheet->cached_style); /* destroy string vector */ - for (string_index = 0; - string_index < sheet->string_vector_c; - string_index++) { - lwc_string_unref(sheet->string_vector[string_index]); + for (index = 0; + index < sheet->string_vector_c; + index++) { + lwc_string_unref(sheet->string_vector[index]); } if (sheet->string_vector != NULL) sheet->alloc(sheet->string_vector, 0, sheet->pw); css__propstrings_unref(); - + sheet->alloc(sheet, 0, sheet->pw); return CSS_OK; @@ -625,7 +626,7 @@ css_error css_stylesheet_size(css_stylesheet *sheet, size_t *size) bytes += hash_size; } - + *size = bytes; return CSS_OK; @@ -1194,10 +1195,10 @@ css_error css__stylesheet_rule_destroy(css_stylesheet *sheet, css_rule *rule) break; case CSS_RULE_FONT_FACE: { - css_rule_font_face *font_face = (css_rule_font_face *) rule; + css_rule_font_face *font_face_r = (css_rule_font_face *) rule; - if (font_face->style != NULL) - css__stylesheet_style_destroy(font_face->style); + if (font_face_r->font_face != NULL) + css__font_face_destroy(font_face_r->font_face); } break; case CSS_RULE_PAGE: @@ -1705,8 +1706,8 @@ size_t _rule_size(const css_rule *r) bytes += sizeof(css_rule_font_face); - if (rf->style != NULL) - bytes += (rf->style->used * sizeof(css_code_t)); + if (rf->font_face != NULL) + bytes += sizeof(css_font_face); } else if (r->type == CSS_RULE_PAGE) { const css_rule_page *rp = (const css_rule_page *) r; const css_selector *s = rp->selector; @@ -1731,4 +1732,3 @@ size_t _rule_size(const css_rule *r) return bytes; } - diff --git a/src/stylesheet.h b/src/stylesheet.h index 0f6deed..d48d0a9 100644 --- a/src/stylesheet.h +++ b/src/stylesheet.h @@ -141,7 +141,7 @@ typedef struct css_rule_media { typedef struct css_rule_font_face { css_rule base; - css_style *style; + css_font_face *font_face; } css_rule_font_face; typedef struct css_rule_page { -- cgit v1.2.3