diff options
author | Michael Drake <Michael Drake tlsa@netsurf-browser.org> | 2019-05-04 14:54:04 +0100 |
---|---|---|
committer | Michael Drake <Michael Drake tlsa@netsurf-browser.org> | 2019-05-04 14:54:04 +0100 |
commit | ace5978891ce0c2e1700945a296881fc62109701 (patch) | |
tree | cbc8d71fb5116b7d65b9b607b71335ecc61c6af2 | |
parent | 4591e4851068ab16afdecbaef5eccacc71344ffa (diff) | |
parent | 44feffba4ab7178cca1dbdb4e218d6a93861f804 (diff) | |
download | libcss-ace5978891ce0c2e1700945a296881fc62109701.tar.gz libcss-ace5978891ce0c2e1700945a296881fc62109701.tar.bz2 |
Merge branch 'tlsa/jmb/mq2'
-rw-r--r-- | docs/Bytecode | 9 | ||||
-rw-r--r-- | examples/example1.c | 7 | ||||
-rw-r--r-- | include/libcss/select.h | 8 | ||||
-rw-r--r-- | include/libcss/stylesheet.h | 5 | ||||
-rw-r--r-- | include/libcss/types.h | 137 | ||||
-rw-r--r-- | src/bytecode/bytecode.h | 8 | ||||
-rw-r--r-- | src/parse/Makefile | 2 | ||||
-rw-r--r-- | src/parse/language.c | 112 | ||||
-rw-r--r-- | src/parse/mq.c | 1150 | ||||
-rw-r--r-- | src/parse/mq.h | 100 | ||||
-rw-r--r-- | src/parse/parse.c | 204 | ||||
-rw-r--r-- | src/parse/parse.h | 2 | ||||
-rw-r--r-- | src/parse/properties/utils.c | 8 | ||||
-rw-r--r-- | src/parse/propstrings.c | 4 | ||||
-rw-r--r-- | src/parse/propstrings.h | 2 | ||||
-rw-r--r-- | src/select/computed.c | 2 | ||||
-rw-r--r-- | src/select/hash.h | 2 | ||||
-rw-r--r-- | src/select/mq.h | 53 | ||||
-rw-r--r-- | src/select/select.c | 60 | ||||
-rw-r--r-- | src/select/select.h | 2 | ||||
-rw-r--r-- | src/stylesheet.c | 19 | ||||
-rw-r--r-- | src/stylesheet.h | 9 | ||||
-rw-r--r-- | test/css21.c | 4 | ||||
-rw-r--r-- | test/data/parse2/illegal-values.dat | 2 | ||||
-rw-r--r-- | test/parse-auto.c | 4 | ||||
-rw-r--r-- | test/select.c | 27 |
26 files changed, 1698 insertions, 244 deletions
diff --git a/docs/Bytecode b/docs/Bytecode index f64656a..dd0f424 100644 --- a/docs/Bytecode +++ b/docs/Bytecode @@ -69,6 +69,7 @@ Length is a 32bit numeric value (as described above) and unit is as follows: 00000000 => deg 00000001 => grad 00000010 => rad + 00000011 => turn bit 10 set => time unit bits 11-31: MBZ @@ -84,6 +85,14 @@ Length is a 32bit numeric value (as described above) and unit is as follows: 00000000 => Hz 00000001 => kHz + bit 12 set => resolution unit + bits 13-31: MBZ + bits 8-11 : MBZ + bits 0-7 : + 00000000 => dpi + 00000001 => dpcm + 00000010 => dppx + CSS colours are stored as one 32bit value. See "Colour" for their format. Shorthand properties diff --git a/examples/example1.c b/examples/example1.c index 1c0dcf9..c36a94d 100644 --- a/examples/example1.c +++ b/examples/example1.c @@ -161,6 +161,9 @@ int main(int argc, char **argv) uint32_t count; unsigned int hh; css_stylesheet_params params; + css_media media = { + .type = CSS_MEDIA_SCREEN, + }; UNUSED(argc); UNUSED(argv); @@ -210,7 +213,7 @@ int main(int argc, char **argv) if (code != CSS_OK) die("css_select_ctx_create", code); code = css_select_ctx_append_sheet(select_ctx, sheet, CSS_ORIGIN_AUTHOR, - CSS_MEDIA_ALL); + NULL); if (code != CSS_OK) die("css_select_ctx_append_sheet", code); code = css_select_ctx_count_sheets(select_ctx, &count); @@ -234,7 +237,7 @@ int main(int argc, char **argv) lwc_intern_string(element, strlen(element), &element_name); code = css_select_style(select_ctx, element_name, - CSS_MEDIA_SCREEN, NULL, + &media, NULL, &select_handler, 0, &style); if (code != CSS_OK) diff --git a/include/libcss/select.h b/include/libcss/select.h index f1de409..ca57456 100644 --- a/include/libcss/select.h +++ b/include/libcss/select.h @@ -206,10 +206,10 @@ css_error css_select_ctx_destroy(css_select_ctx *ctx); css_error css_select_ctx_append_sheet(css_select_ctx *ctx, const css_stylesheet *sheet, - css_origin origin, uint64_t media); + css_origin origin, const char *media); css_error css_select_ctx_insert_sheet(css_select_ctx *ctx, const css_stylesheet *sheet, uint32_t index, - css_origin origin, uint64_t media); + css_origin origin, const char *media); css_error css_select_ctx_remove_sheet(css_select_ctx *ctx, const css_stylesheet *sheet); @@ -221,13 +221,13 @@ css_error css_select_default_style(css_select_ctx *ctx, css_select_handler *handler, void *pw, css_computed_style **style); css_error css_select_style(css_select_ctx *ctx, void *node, - uint64_t media, const css_stylesheet *inline_style, + const css_media *media, const css_stylesheet *inline_style, css_select_handler *handler, void *pw, css_select_results **result); css_error css_select_results_destroy(css_select_results *results); css_error css_select_font_faces(css_select_ctx *ctx, - uint64_t media, lwc_string *font_family, + const css_media *media, lwc_string *font_family, css_select_font_faces_results **result); css_error css_select_font_faces_results_destroy( css_select_font_faces_results *results); diff --git a/include/libcss/stylesheet.h b/include/libcss/stylesheet.h index 68c4dfc..542f199 100644 --- a/include/libcss/stylesheet.h +++ b/include/libcss/stylesheet.h @@ -36,7 +36,6 @@ typedef css_error (*css_url_resolution_fn)(void *pw, * \param pw Client data * \param parent Stylesheet requesting the import * \param url URL of the imported sheet - * \param media Applicable media for the imported sheet * \return CSS_OK on success, appropriate error otherwise * * \note This function will be invoked for notification purposes @@ -46,7 +45,7 @@ typedef css_error (*css_url_resolution_fn)(void *pw, * registration API. */ typedef css_error (*css_import_notification_fn)(void *pw, - css_stylesheet *parent, lwc_string *url, uint64_t media); + css_stylesheet *parent, lwc_string *url); /** * Callback use to resolve system colour names to RGB values @@ -145,7 +144,7 @@ css_error css_stylesheet_append_data(css_stylesheet *sheet, css_error css_stylesheet_data_done(css_stylesheet *sheet); css_error css_stylesheet_next_pending_import(css_stylesheet *parent, - lwc_string **url, uint64_t *media); + lwc_string **url); css_error css_stylesheet_register_import(css_stylesheet *parent, css_stylesheet *child); diff --git a/include/libcss/types.h b/include/libcss/types.h index 4f35737..44aef12 100644 --- a/include/libcss/types.h +++ b/include/libcss/types.h @@ -60,10 +60,10 @@ typedef enum css_media_type { CSS_MEDIA_TTY = (1 << 8), CSS_MEDIA_TV = (1 << 9), CSS_MEDIA_ALL = CSS_MEDIA_AURAL | CSS_MEDIA_BRAILLE | - CSS_MEDIA_EMBOSSED | CSS_MEDIA_HANDHELD | - CSS_MEDIA_PRINT | CSS_MEDIA_PROJECTION | - CSS_MEDIA_SCREEN | CSS_MEDIA_SPEECH | - CSS_MEDIA_TTY | CSS_MEDIA_TV + CSS_MEDIA_EMBOSSED | CSS_MEDIA_HANDHELD | + CSS_MEDIA_PRINT | CSS_MEDIA_PROJECTION | + CSS_MEDIA_SCREEN | CSS_MEDIA_SPEECH | + CSS_MEDIA_TTY | CSS_MEDIA_TV } css_media_type; /** @@ -116,6 +116,135 @@ typedef enum css_unit { } css_unit; /** + * Media orienations + */ +typedef enum css_media_orientation { + CSS_MEDIA_ORIENTATION_PORTRAIT = 0, + CSS_MEDIA_ORIENTATION_LANDSCAPE = 1 +} css_media_orientation; + +/** + * Media scans + */ +typedef enum css_media_scan { + CSS_MEDIA_SCAN_PROGRESSIVE = 0, + CSS_MEDIA_SCAN_INTERLACE = 1 +} css_media_scan; + +/** + * Media update-frequencies + */ +typedef enum css_media_update_frequency { + CSS_MEDIA_UPDATE_FREQUENCY_NORMAL = 0, + CSS_MEDIA_UPDATE_FREQUENCY_SLOW = 1, + CSS_MEDIA_UPDATE_FREQUENCY_NONE = 2 +} css_media_update_frequency; + +/** + * Media block overflows + */ +typedef enum css_media_overflow_block { + CSS_MEDIA_OVERFLOW_BLOCK_NONE = 0, + CSS_MEDIA_OVERFLOW_BLOCK_SCROLL = 1, + CSS_MEDIA_OVERFLOW_BLOCK_OPTIONAL_PAGED = 2, + CSS_MEDIA_OVERFLOW_BLOCK_PAGED = 3 +} css_media_overflow_block; + +/** + * Media inline overflows + */ +typedef enum css_media_overflow_inline { + CSS_MEDIA_OVERFLOW_INLINE_NONE = 0, + CSS_MEDIA_OVERFLOW_INLINE_SCROLL = 1 +} css_media_overflow_inline; + +/** + * Media pointers + */ +typedef enum css_media_pointer { + CSS_MEDIA_POINTER_NONE = 0, + CSS_MEDIA_POINTER_COARSE = 1, + CSS_MEDIA_POINTER_FINE = 2 +} css_media_pointer; + +/** + * Media hovers + */ +typedef enum css_media_hover { + CSS_MEDIA_HOVER_NONE = 0, + CSS_MEDIA_HOVER_ON_DEMAND = 1, + CSS_MEDIA_HOVER_HOVER = 2 +} css_media_hover; + +/** + * Media light-levels + */ +typedef enum css_media_light_level { + CSS_MEDIA_LIGHT_LEVEL_NORMAL = 0, + CSS_MEDIA_LIGHT_LEVEL_DIM = 1, + CSS_MEDIA_LIGHT_LEVEL_WASHED = 2 +} css_media_light_level; + +/** + * Media scriptings + */ +typedef enum css_media_scripting { + CSS_MEDIA_SCRIPTING_NONE = 0, + CSS_MEDIA_SCRIPTING_INITIAL_ONLY = 1, + CSS_MEDIA_SCRIPTING_ENABLED = 2 +} css_media_scripting; + +typedef struct css_media_length { + css_fixed value; + css_unit unit; +} css_media_length; + +typedef struct css_media_resolution { + css_fixed value; + css_unit unit; +} css_media_resolution; + +/** + * Media specification + */ +typedef struct css_media { + /* Media type */ + css_media_type type; + + /* Screen / Device media features */ + css_media_length width; + css_media_length height; + css_fixed aspect_ratio; + css_media_orientation orientation; + + /* Display quality media features */ + css_media_resolution resolution; + css_media_scan scan; + css_fixed grid; /** boolean: {0|1} */ + css_media_update_frequency update; + css_media_overflow_block overflow_block; + css_media_overflow_inline overflow_inline; + + /* Color media features */ + css_fixed color; /* colour bpp (0 for monochrome) */ + css_fixed color_index; + css_fixed monochrome; /* monochrome bpp (0 for colour) */ + css_fixed inverted_colors; /** boolean: {0|1} */ + + /* Interaction media features */ + css_media_pointer pointer; + css_media_pointer any_pointer; + css_media_hover hover; + css_media_hover any_hover; + + /* Environmental media features */ + css_media_light_level light_level; + + /* Scripting media features */ + css_media_scripting scripting; +} css_media; + +/** * Type of a qualified name */ typedef struct css_qname { diff --git a/src/bytecode/bytecode.h b/src/bytecode/bytecode.h index 422f141..22703f7 100644 --- a/src/bytecode/bytecode.h +++ b/src/bytecode/bytecode.h @@ -52,6 +52,7 @@ typedef enum unit { UNIT_DEG = (1 << 9) + 0, UNIT_GRAD = (1 << 9) + 1, UNIT_RAD = (1 << 9) + 2, + UNIT_TURN = (1 << 9) + 3, UNIT_TIME = (1 << 10), UNIT_MS = (1 << 10) + 0, @@ -59,7 +60,12 @@ typedef enum unit { UNIT_FREQ = (1 << 11), UNIT_HZ = (1 << 11) + 0, - UNIT_KHZ = (1 << 11) + 1 + UNIT_KHZ = (1 << 11) + 1, + + UNIT_RESOLUTION = (1 << 12), + UNIT_DPI = (1 << 12) + 0, + UNIT_DPCM = (1 << 12) + 1, + UNIT_DPPX = (1 << 12) + 2, } unit; typedef uint32_t colour; diff --git a/src/parse/Makefile b/src/parse/Makefile index 99f55d0..6d11096 100644 --- a/src/parse/Makefile +++ b/src/parse/Makefile @@ -1,4 +1,4 @@ # Sources -DIR_SOURCES := parse.c language.c important.c propstrings.c font_face.c +DIR_SOURCES := parse.c language.c important.c propstrings.c font_face.c mq.c include $(NSBUILD)/Makefile.subdir diff --git a/src/parse/language.c b/src/parse/language.c index 9af2547..54fac9a 100644 --- a/src/parse/language.c +++ b/src/parse/language.c @@ -15,6 +15,7 @@ #include "parse/font_face.h" #include "parse/important.h" #include "parse/language.h" +#include "parse/mq.h" #include "parse/parse.h" #include "parse/propstrings.h" #include "parse/properties/properties.h" @@ -53,9 +54,6 @@ static css_error handleDeclaration(css_language *c, const parserutils_vector *vector); /* At-rule parsing */ -static css_error parseMediaList(css_language *c, - const parserutils_vector *vector, int *ctx, - uint64_t *media); static css_error addNamespace(css_language *c, lwc_string *prefix, lwc_string *uri); static css_error lookupNamespace(css_language *c, @@ -416,10 +414,9 @@ css_error handleStartAtRule(css_language *c, const parserutils_vector *vector) match) { if (c->state <= IMPORT_PERMITTED) { lwc_string *url; - uint64_t media = 0; + css_mq_query *media = NULL; - /* any0 = (STRING | URI) ws - * (IDENT ws (',' ws IDENT ws)* )? */ + /* any0 = (STRING | URI) ws (media query)? */ const css_token *uri = parserutils_vector_iterate(vector, &ctx); if (uri == NULL || (uri->type != CSS_TOKEN_STRING && @@ -429,15 +426,18 @@ css_error handleStartAtRule(css_language *c, const parserutils_vector *vector) consumeWhitespace(vector, &ctx); /* Parse media list */ - error = parseMediaList(c, vector, &ctx, &media); + error = css__mq_parse_media_list( + c->strings, vector, &ctx, &media); if (error != CSS_OK) return error; /* Create rule */ error = css__stylesheet_rule_create(c->sheet, CSS_RULE_IMPORT, &rule); - if (error != CSS_OK) + if (error != CSS_OK) { + css__mq_query_destroy(media); return error; + } /* Resolve import URI */ error = c->sheet->resolve(c->sheet->resolve_pw, @@ -445,6 +445,7 @@ css_error handleStartAtRule(css_language *c, const parserutils_vector *vector) uri->idata, &url); if (error != CSS_OK) { css__stylesheet_rule_destroy(c->sheet, rule); + css__mq_query_destroy(media); return error; } @@ -454,13 +455,14 @@ css_error handleStartAtRule(css_language *c, const parserutils_vector *vector) if (error != CSS_OK) { lwc_string_unref(url); css__stylesheet_rule_destroy(c->sheet, rule); + css__mq_query_destroy(media); return error; } /* Inform client of need for import */ if (c->sheet->import != NULL) { error = c->sheet->import(c->sheet->import_pw, - c->sheet, url, media); + c->sheet, url); if (error != CSS_OK) { lwc_string_unref(url); css__stylesheet_rule_destroy(c->sheet, @@ -527,22 +529,25 @@ css_error handleStartAtRule(css_language *c, const parserutils_vector *vector) } } else if (lwc_string_caseless_isequal(atkeyword->idata, c->strings[MEDIA], &match) == lwc_error_ok && match) { - uint64_t media = 0; + css_mq_query *media = NULL; - /* any0 = IDENT ws (',' ws IDENT ws)* */ - - error = parseMediaList(c, vector, &ctx, &media); + /* any0 = media query */ + error = css__mq_parse_media_list( + c->strings, vector, &ctx, &media); if (error != CSS_OK) return error; error = css__stylesheet_rule_create(c->sheet, CSS_RULE_MEDIA, &rule); - if (error != CSS_OK) + if (error != CSS_OK) { + css__mq_query_destroy(media); return error; + } error = css__stylesheet_rule_set_media(c->sheet, rule, media); if (error != CSS_OK) { css__stylesheet_rule_destroy(c->sheet, rule); + css__mq_query_destroy(media); return error; } @@ -795,85 +800,6 @@ css_error handleDeclaration(css_language *c, const parserutils_vector *vector) * At-rule parsing functions * ******************************************************************************/ -css_error parseMediaList(css_language *c, - const parserutils_vector *vector, int *ctx, - uint64_t *media) -{ - uint64_t ret = 0; - bool match = false; - const css_token *token; - - token = parserutils_vector_iterate(vector, ctx); - - while (token != NULL) { - if (token->type != CSS_TOKEN_IDENT) - return CSS_INVALID; - - if (lwc_string_caseless_isequal(token->idata, c->strings[AURAL], - &match) == lwc_error_ok && match) { - ret |= CSS_MEDIA_AURAL; - } else if (lwc_string_caseless_isequal( - token->idata, c->strings[BRAILLE], - &match) == lwc_error_ok && match) { - ret |= CSS_MEDIA_BRAILLE; - } else if (lwc_string_caseless_isequal( - token->idata, c->strings[EMBOSSED], - &match) == lwc_error_ok && match) { - ret |= CSS_MEDIA_EMBOSSED; - } else if (lwc_string_caseless_isequal( - token->idata, c->strings[HANDHELD], - &match) == lwc_error_ok && match) { - ret |= CSS_MEDIA_HANDHELD; - } else if (lwc_string_caseless_isequal( - token->idata, c->strings[PRINT], - &match) == lwc_error_ok && match) { - ret |= CSS_MEDIA_PRINT; - } else if (lwc_string_caseless_isequal( - token->idata, c->strings[PROJECTION], - &match) == lwc_error_ok && match) { - ret |= CSS_MEDIA_PROJECTION; - } else if (lwc_string_caseless_isequal( - token->idata, c->strings[SCREEN], - &match) == lwc_error_ok && match) { - ret |= CSS_MEDIA_SCREEN; - } else if (lwc_string_caseless_isequal( - token->idata, c->strings[SPEECH], - &match) == lwc_error_ok && match) { - ret |= CSS_MEDIA_SPEECH; - } else if (lwc_string_caseless_isequal( - token->idata, c->strings[TTY], - &match) == lwc_error_ok && match) { - ret |= CSS_MEDIA_TTY; - } else if (lwc_string_caseless_isequal( - token->idata, c->strings[TV], - &match) == lwc_error_ok && match) { - ret |= CSS_MEDIA_TV; - } else if (lwc_string_caseless_isequal( - token->idata, c->strings[ALL], - &match) == lwc_error_ok && match) { - ret |= CSS_MEDIA_ALL; - } else - return CSS_INVALID; - - consumeWhitespace(vector, ctx); - - token = parserutils_vector_iterate(vector, ctx); - if (token != NULL && tokenIsChar(token, ',') == false) - return CSS_INVALID; - - consumeWhitespace(vector, ctx); - } - - /* If, after parsing the media list, we still have no media, - * then it must be ALL. */ - if (ret == 0) - ret = CSS_MEDIA_ALL; - - *media = ret; - - return CSS_OK; -} - /** * Add a namespace mapping * diff --git a/src/parse/mq.c b/src/parse/mq.c new file mode 100644 index 0000000..cb9345d --- /dev/null +++ b/src/parse/mq.c @@ -0,0 +1,1150 @@ +/* + * This file is part of LibCSS. + * Licensed under the MIT License, + * http://www.opensource.org/licenses/mit-license.php + * Copyright 2016 John-Mark Bell <jmb@netsurf-browser.org> + */ + +/* https://drafts.csswg.org/mediaqueries/ */ + +#include <string.h> + +#include <libcss/fpmath.h> + +#include "stylesheet.h" +#include "bytecode/bytecode.h" +#include "parse/mq.h" +#include "parse/properties/utils.h" +#include "utils/utils.h" + +static void css__mq_value_destroy(css_mq_value *value) +{ + assert(value != NULL); + + if (value->type == CSS_MQ_VALUE_TYPE_IDENT) { + lwc_string_unref(value->data.ident); + } +} + +static void css__mq_feature_destroy(css_mq_feature *feature) +{ + if (feature != NULL) { + lwc_string_unref(feature->name); + css__mq_value_destroy(&feature->value); + css__mq_value_destroy(&feature->value2); + free(feature); + } +} + +static void css__mq_cond_or_feature_destroy( + css_mq_cond_or_feature *cond_or_feature); + +static void css__mq_cond_parts_destroy(css_mq_cond_parts *cond_parts) +{ + if (cond_parts != NULL) { + for (uint32_t i = 0; i < cond_parts->nparts; i++) { + css__mq_cond_or_feature_destroy(cond_parts->parts[i]); + } + free(cond_parts->parts); + free(cond_parts); + } +} + +static void css__mq_cond_destroy(css_mq_cond *cond) +{ + if (cond != NULL) { + css__mq_cond_parts_destroy(cond->parts); + free(cond); + } +} + +static void css__mq_cond_or_feature_destroy( + css_mq_cond_or_feature *cond_or_feature) +{ + if (cond_or_feature != NULL) { + switch (cond_or_feature->type) { + case CSS_MQ_FEATURE: + css__mq_feature_destroy(cond_or_feature->data.feat); + break; + case CSS_MQ_COND: + css__mq_cond_destroy(cond_or_feature->data.cond); + break; + } + free(cond_or_feature); + } +} + +void css__mq_query_destroy(css_mq_query *media) +{ + while (media != NULL) { + css_mq_query *next = media->next; + + css__mq_cond_destroy(media->cond); + free(media); + + media = next; + } +} + +static css_error mq_parse_condition(lwc_string **strings, + const parserutils_vector *vector, int *ctx, + bool permit_or, css_mq_cond **cond); + +static css_error mq_parse_ratio( + const parserutils_vector *vector, int *ctx, + const css_token *numerator, css_fixed *ratio) +{ + const css_token *token; + css_fixed num, den; + size_t num_len, den_len; + + /* NUMBER ws* '/' ws* NUMBER */ + + /* numerator, ws* already consumed */ + + token = parserutils_vector_iterate(vector, ctx); + if (token == NULL || tokenIsChar(token, '/') == false) { + return CSS_INVALID; + } + + consumeWhitespace(vector, ctx); + + token = parserutils_vector_iterate(vector, ctx); + if (token == NULL || token->type != CSS_TOKEN_NUMBER) { + return CSS_INVALID; + } + + num = css__number_from_lwc_string(numerator->idata, true, &num_len); + den = css__number_from_lwc_string(token->idata, true, &den_len); + + *ratio = css_divide_fixed(num, den); + + return CSS_OK; +} + +static css_error mq_create_feature( + lwc_string *name, + css_mq_feature **feature) +{ + css_mq_feature *f; + + f = malloc(sizeof(*f)); + if (f == NULL) { + return CSS_NOMEM; + } + + memset(f, 0, sizeof(*f)); + + f->name = lwc_string_ref(name); + + *feature = f; + + return CSS_OK; +} + +static css_error mq_populate_value(css_mq_value *value, + const css_token *token) +{ + if (token->type == CSS_TOKEN_NUMBER) { + size_t num_len; + value->type = CSS_MQ_VALUE_TYPE_NUM; + value->data.num_or_ratio = css__number_from_lwc_string( + token->idata, false, &num_len); + } else if (token->type == CSS_TOKEN_DIMENSION) { + size_t len = lwc_string_length(token->idata); + const char *data = lwc_string_data(token->idata); + uint32_t unit = UNIT_PX; + size_t consumed; + css_error error; + + value->type = CSS_MQ_VALUE_TYPE_DIM; + value->data.dim.len = css__number_from_lwc_string( + token->idata, false, &consumed); + error = css__parse_unit_keyword(data + consumed, len - consumed, + &unit); + if (error != CSS_OK) { + return error; + } + value->data.dim.unit = unit; + } else if (token->type == CSS_TOKEN_IDENT) { + value->type = CSS_MQ_VALUE_TYPE_IDENT; + value->data.ident = lwc_string_ref(token->idata); + } + + return CSS_OK; +} + +static css_error mq_parse_op(const css_token *token, + css_mq_feature_op *op) +{ + size_t len; + const char *data; + + if (token == NULL || token->type != CSS_TOKEN_CHAR) + return CSS_INVALID; + + len = lwc_string_length(token->idata); + data = lwc_string_data(token->idata); + + if (len == 2) { + if (strncasecmp(data, "<=", 2) == 0) + *op = CSS_MQ_FEATURE_OP_LTE; + else if (strncasecmp(data, ">=", 2) == 0) + *op = CSS_MQ_FEATURE_OP_GTE; + else + return CSS_INVALID; + } else if (len == 1) { + if (*data == '<') + *op = CSS_MQ_FEATURE_OP_LT; + else if (*data == '=') + *op = CSS_MQ_FEATURE_OP_EQ; + else if (*data == '>') + *op = CSS_MQ_FEATURE_OP_GT; + else + return CSS_INVALID; + } else { + return CSS_INVALID; + } + + return CSS_OK; +} + +static css_error mq_parse_range(lwc_string **strings, + const parserutils_vector *vector, int *ctx, + const css_token *name_or_value, + css_mq_feature **feature) +{ + const css_token *token, *value_or_name, *name = NULL, *value2 = NULL; + css_mq_feature *result; + css_mq_feature_op op, op2; + css_fixed ratio, ratio2; + bool name_first = false, value_is_ratio = false, value2_is_ratio = false, match; + css_error error; + + /* <mf-range> = <mf-name> [ '<' | '>' ]? '='? <mf-value> + * | <mf-value> [ '<' | '>' ]? '='? <mf-name> + * | <mf-value> '<' '='? <mf-name> '<' '='? <mf-value> + * | <mf-value> '>' '='? <mf-name> '>' '='? <mf-value> + */ + + if (name_or_value == NULL || (name_or_value->type != CSS_TOKEN_NUMBER && + name_or_value->type != CSS_TOKEN_DIMENSION && + name_or_value->type != CSS_TOKEN_IDENT)) { + return CSS_INVALID; + } + + consumeWhitespace(vector, ctx); + + /* Name-or-value */ + if (name_or_value->type == CSS_TOKEN_NUMBER && + tokenIsChar(parserutils_vector_peek(vector, *ctx), '/')) { + /* ratio */ + error = mq_parse_ratio(vector, ctx, name_or_value, &ratio); + if (error != CSS_OK) { + return error; + } + + consumeWhitespace(vector, ctx); + + value_is_ratio = true; + } else if (name_or_value->type == CSS_TOKEN_IDENT && + lwc_string_caseless_isequal(name_or_value->idata, + strings[INFINITE], &match) == lwc_error_ok && + match == false) { + /* The only ident permitted for mf-value is 'infinite', thus must have name */ + name = name_or_value; + name_first = true; + } + + /* Op */ + token = parserutils_vector_iterate(vector, ctx); + error = mq_parse_op(token, &op); + if (error != CSS_OK) { + return error; + } + + consumeWhitespace(vector, ctx); + + /* Value-or-name */ + value_or_name = parserutils_vector_iterate(vector, ctx); + if (value_or_name == NULL || (value_or_name->type != CSS_TOKEN_NUMBER && + value_or_name->type != CSS_TOKEN_DIMENSION && + value_or_name->type != CSS_TOKEN_IDENT)) { + return CSS_INVALID; + } + + if (name == NULL) { + if (value_or_name->type != CSS_TOKEN_IDENT) { + return CSS_INVALID; + } else { + name = value_or_name; + } + } + + consumeWhitespace(vector, ctx); + + if (value_or_name->type == CSS_TOKEN_NUMBER && + tokenIsChar(parserutils_vector_peek(vector, *ctx), '/')) { + /* ratio */ + error = mq_parse_ratio(vector, ctx, token, &ratio); + if (error != CSS_OK) { + return error; + } + + consumeWhitespace(vector, ctx); + + value_is_ratio = true; + } + + token = parserutils_vector_peek(vector, *ctx); + if (name_first == false && token != NULL && tokenIsChar(token, ')') == false) { + /* Op2 */ + token = parserutils_vector_iterate(vector, ctx); + error = mq_parse_op(token, &op2); + if (error != CSS_OK) { + return error; + } + + consumeWhitespace(vector, ctx); + + /* Validate operators: must both be LT(E) or GT(E) */ + if (op == CSS_MQ_FEATURE_OP_LT || op == CSS_MQ_FEATURE_OP_LTE) { + if (op2 != CSS_MQ_FEATURE_OP_LT && op2 != CSS_MQ_FEATURE_OP_LTE) { + return CSS_INVALID; + } + } else if (op == CSS_MQ_FEATURE_OP_GT || op == CSS_MQ_FEATURE_OP_GTE) { + if (op2 != CSS_MQ_FEATURE_OP_GT && op2 != CSS_MQ_FEATURE_OP_GTE) { + return CSS_INVALID; + } + } else { + return CSS_INVALID; + } + + /* Value2 */ + value2 = parserutils_vector_iterate(vector, ctx); + if (value2 == NULL || (value2->type != CSS_TOKEN_NUMBER && + value2->type != CSS_TOKEN_DIMENSION && + value2->type != CSS_TOKEN_IDENT)) { + return CSS_INVALID; + } + + consumeWhitespace(vector, ctx); + + if (value_or_name->type == CSS_TOKEN_NUMBER && + tokenIsChar(parserutils_vector_peek(vector, *ctx), '/')) { + /* ratio */ + error = mq_parse_ratio(vector, ctx, token, &ratio2); + if (error != CSS_OK) { + return error; + } + + consumeWhitespace(vector, ctx); + + value2_is_ratio = true; + } + } + + error = mq_create_feature(name->idata, &result); + if (error != CSS_OK) { + return error; + } + if (name_first) { + /* Invert operator */ + if (op == CSS_MQ_FEATURE_OP_LT) { + op = CSS_MQ_FEATURE_OP_GTE; + } else if (op == CSS_MQ_FEATURE_OP_LTE) { + op = CSS_MQ_FEATURE_OP_GT; + } else if (op == CSS_MQ_FEATURE_OP_GT) { + op = CSS_MQ_FEATURE_OP_LTE; + } else if (op == CSS_MQ_FEATURE_OP_GTE) { + op = CSS_MQ_FEATURE_OP_LT; + } + } + result->op = op; + if (value_is_ratio) { + result->value.type = CSS_MQ_VALUE_TYPE_RATIO; + result->value.data.num_or_ratio = ratio; + } else { + /* num/dim/ident */ + error = mq_populate_value(&result->value, token); + if (error != CSS_OK) { + free(result); + return error; + } + } + if (value2 != NULL) { + result->op2 = op2; + if (value2_is_ratio) { + result->value2.type = CSS_MQ_VALUE_TYPE_RATIO; + result->value2.data.num_or_ratio = ratio; + } else { + /* num/dim/ident */ + error = mq_populate_value(&result->value2, token); + if (error != CSS_OK) { + css__mq_feature_destroy(result); + return error; + } + } + } + + *feature = result; + + return CSS_OK; +} + +static css_error mq_parse_media_feature(lwc_string **strings, + const parserutils_vector *vector, int *ctx, + css_mq_feature **feature) +{ + const css_token *name_or_value, *token; + css_mq_feature *result; + css_error error; + + /* <media-feature> = ( [ <mf-plain> | <mf-boolean> | <mf-range> ] ) + * <mf-plain> = <mf-name> : <mf-value> + * <mf-boolean> = <mf-name> + * <mf-name> = <ident> + * <mf-value> = <number> | <dimension> | <ident> | <ratio> + */ + + /* ( already consumed */ + + consumeWhitespace(vector, ctx); + + name_or_value = parserutils_vector_iterate(vector, ctx); + if (name_or_value == NULL) + return CSS_INVALID; + + if (name_or_value->type == CSS_TOKEN_IDENT) { + consumeWhitespace(vector, ctx); + + token = parserutils_vector_peek(vector, *ctx); + if (tokenIsChar(token, ')')) { + /* mf-boolean */ + error = mq_create_feature(name_or_value->idata, &result); + if (error != CSS_OK) { + return error; + } + + result->op = CSS_MQ_FEATURE_OP_BOOL; + } else if (tokenIsChar(token, ':')) { + /* mf-plain */ + parserutils_vector_iterate(vector, ctx); + + consumeWhitespace(vector, ctx); + + token = parserutils_vector_iterate(vector, ctx); + if (token == NULL || (token->type != CSS_TOKEN_NUMBER && + token->type != CSS_TOKEN_DIMENSION && + token->type != CSS_TOKEN_IDENT)) { + return CSS_INVALID; + } + + consumeWhitespace(vector, ctx); + + error = mq_create_feature(name_or_value->idata, &result); + if (error != CSS_OK) { + return error; + } + result->op = CSS_MQ_FEATURE_OP_EQ; + + if (token->type == CSS_TOKEN_NUMBER && + tokenIsChar(parserutils_vector_peek(vector, *ctx), '/')) { + /* ratio */ + css_fixed ratio; + + error = mq_parse_ratio(vector, ctx, token, &ratio); + if (error != CSS_OK) { + free(result); + return error; + } + + result->value.type = CSS_MQ_VALUE_TYPE_RATIO; + result->value.data.num_or_ratio = ratio; + } else { + /* num/dim/ident */ + error = mq_populate_value(&result->value, token); + if (error != CSS_OK) { + free(result); + return error; + } + } + + consumeWhitespace(vector, ctx); + } else { + /* mf-range */ + error = mq_parse_range(strings, vector, ctx, + name_or_value, &result); + if (error != CSS_OK) { + return error; + } + + consumeWhitespace(vector, ctx); + } + } else { + /* mf-range */ + error = mq_parse_range(strings, vector, ctx, + name_or_value, &result); + if (error != CSS_OK) { + return error; + } + + consumeWhitespace(vector, ctx); + } + + token = parserutils_vector_iterate(vector, ctx); + if (tokenIsChar(token, ')') == false) { + css__mq_feature_destroy(result); + return CSS_INVALID; + } + + *feature = result; + + return CSS_OK; +} + +/* + * Consume any value + * + * CSS Syntax Module Level 3: 8.2 + */ +static css_error mq_parse_consume_any_value(lwc_string **strings, + const parserutils_vector *vector, int *ctx, + bool until, const char until_char) +{ + const css_token *token; + css_error error; + + while (true) { + consumeWhitespace(vector, ctx); + + token = parserutils_vector_iterate(vector, ctx); + if (token == NULL) { + return CSS_INVALID; + } + + switch (token->type) { + case CSS_TOKEN_INVALID_STRING: + return CSS_INVALID; + + case CSS_TOKEN_CHAR: + if (until && tokenIsChar(token, until_char)) { + /* Found matching close bracket */ + return CSS_OK; + + } else if (tokenIsChar(token, ')') || + tokenIsChar(token, ']') || + tokenIsChar(token, '}')) { + /* Non-matching close bracket */ + return CSS_INVALID; + } + if (tokenIsChar(token, '(')) { + /* Need to consume until matching bracket. */ + error = mq_parse_consume_any_value(strings, + vector, ctx, true, ')'); + if (error != CSS_OK) { + return error; + } + } else if (tokenIsChar(token, '[')) { + /* Need to consume until matching bracket. */ + error = mq_parse_consume_any_value(strings, + vector, ctx, true, ']'); + if (error != CSS_OK) { + return error; + } + } else if (tokenIsChar(token, '{')) { + /* Need to consume until matching bracket. */ + error = mq_parse_consume_any_value(strings, + vector, ctx, true, '}'); + if (error != CSS_OK) { + return error; + } + } + break; + + default: + break; + } + } + + return CSS_OK; +} + +static css_error mq_parse_general_enclosed(lwc_string **strings, + const parserutils_vector *vector, int *ctx) +{ + const css_token *token; + css_error error; + + /* <general-enclosed> = [ <function-token> <any-value> ) ] + * | ( <ident> <any-value> ) + */ + + token = parserutils_vector_iterate(vector, ctx); + if (token == NULL) { + return CSS_INVALID; + } + + switch (token->type) { + case CSS_TOKEN_FUNCTION: + error = mq_parse_consume_any_value(strings, vector, ctx, + true, ')'); + if (error != CSS_OK) { + return error; + } + + token = parserutils_vector_peek(vector, *ctx); + if (!tokenIsChar(token, ')')) { + return CSS_INVALID; + } + break; + + case CSS_TOKEN_IDENT: + error = mq_parse_consume_any_value(strings, vector, ctx, + false, '\0'); + if (error != CSS_OK) { + return error; + } + break; + + default: + return CSS_INVALID; + } + + return CSS_OK; +} + +static css_error mq_parse_media_in_parens(lwc_string **strings, + const parserutils_vector *vector, int *ctx, + css_mq_cond_or_feature **cond_or_feature) +{ + const css_token *token; + bool match; + int old_ctx; + css_mq_cond_or_feature *result = NULL; + css_error error = CSS_OK; + + /* <media-in-parens> = ( <media-condition> ) | <media-feature> | <general-enclosed> + */ + + //LPAREN -> condition-or-feature + // "not" or LPAREN -> condition + // IDENT | NUMBER | DIMENSION | RATIO -> feature + + token = parserutils_vector_iterate(vector, ctx); + if (token == NULL || tokenIsChar(token, '(') == false) { + return CSS_INVALID; + } + + consumeWhitespace(vector, ctx); + + token = parserutils_vector_peek(vector, *ctx); + if (token == NULL) { + return CSS_INVALID; + } + + old_ctx = *ctx; + + if (tokenIsChar(token, '(') || (token->type == CSS_TOKEN_IDENT && + lwc_string_caseless_isequal(token->idata, + strings[NOT], &match) == lwc_error_ok && + match)) { + css_mq_cond *cond; + error = mq_parse_condition(strings, vector, ctx, true, &cond); + if (error == CSS_OK) { + token = parserutils_vector_iterate(vector, ctx); + if (tokenIsChar(token, ')') == false) { + return CSS_INVALID; + } + + result = malloc(sizeof(*result)); + if (result == NULL) { + css__mq_cond_destroy(cond); + return CSS_NOMEM; + } + memset(result, 0, sizeof(*result)); + result->type = CSS_MQ_COND; + result->data.cond = cond; + *cond_or_feature = result; + return CSS_OK; + } + } else if (token->type == CSS_TOKEN_IDENT || + token->type == CSS_TOKEN_NUMBER || + token->type == CSS_TOKEN_DIMENSION) { + css_mq_feature *feature; + error = mq_parse_media_feature(strings, vector, ctx, &feature); + if (error == CSS_OK) { + result = malloc(sizeof(*result)); + if (result == NULL) { + css__mq_feature_destroy(feature); + return CSS_NOMEM; + } + memset(result, 0, sizeof(*result)); + result->type = CSS_MQ_FEATURE; + result->data.feat = feature; + *cond_or_feature = result; + return CSS_OK; + } + } + + *ctx = old_ctx; + error = mq_parse_general_enclosed(strings, vector, ctx); + + return error; +} + +static css_error mq_parse_condition(lwc_string **strings, + const parserutils_vector *vector, int *ctx, + bool permit_or, css_mq_cond **cond) +{ + const css_token *token; + bool match = false; + int op = 0; /* Will be AND | OR once we've had one */ + css_mq_cond_or_feature *cond_or_feature, **parts; + css_mq_cond *result; + css_error error; + + /* <media-condition> = <media-not> | <media-in-parens> [ <media-and>* | <media-or>* ] + * <media-condition-without-or> = <media-not> | <media-in-parens> <media-and>* + * <media-not> = not <media-in-parens> + * <media-and> = and <media-in-parens> + * <media-or> = or <media-in-parens> + */ + + token = parserutils_vector_peek(vector, *ctx); + if (token == NULL || + (tokenIsChar(token, '(') == false && + token->type != CSS_TOKEN_IDENT && + lwc_string_caseless_isequal(token->idata, + strings[NOT], &match) != lwc_error_ok && + match == false)) { + return CSS_INVALID; + } + + result = malloc(sizeof(*result)); + if (result == NULL) { + return CSS_NOMEM; + } + memset(result, 0, sizeof(*result)); + result->parts = malloc(sizeof(*result->parts)); + if (result->parts == NULL) { + free(result); + return CSS_NOMEM; + } + memset(result->parts, 0, sizeof(*result->parts)); + + if (tokenIsChar(token, '(') == false) { + /* Must be "not" */ + parserutils_vector_iterate(vector, ctx); + consumeWhitespace(vector, ctx); + + error = mq_parse_media_in_parens(strings, + vector, ctx, &cond_or_feature); + if (error != CSS_OK) { + css__mq_cond_destroy(result); + return CSS_INVALID; + } + + result->negate = 1; + result->parts->nparts = 1; + result->parts->parts = malloc(sizeof(*result->parts->parts)); + if (result->parts->parts == NULL) { + css__mq_cond_or_feature_destroy(cond_or_feature); + css__mq_cond_destroy(result); + return CSS_NOMEM; + } + result->parts->parts[0] = cond_or_feature; + + *cond = result; + + return CSS_OK; + } + + /* FOLLOW(media-condition) := RPAREN | COMMA | EOF */ + while (token != NULL && tokenIsChar(token, ')') == false && + tokenIsChar(token, ',') == false) { + error = mq_parse_media_in_parens(strings, vector, ctx, + &cond_or_feature); + if (error != CSS_OK) { + css__mq_cond_destroy(result); + return CSS_INVALID; + } + + parts = realloc(result->parts->parts, + (result->parts->nparts+1)*sizeof(*result->parts->parts)); + if (parts == NULL) { + css__mq_cond_or_feature_destroy(cond_or_feature); + css__mq_cond_destroy(result); + return CSS_NOMEM; + } + parts[result->parts->nparts] = cond_or_feature; + result->parts->parts = parts; + result->parts->nparts++; + + consumeWhitespace(vector, ctx); + + token = parserutils_vector_peek(vector, *ctx); + if (token != NULL && tokenIsChar(token, ')') == false && + tokenIsChar(token, ',') == false) { + if (token->type != CSS_TOKEN_IDENT) { + css__mq_cond_destroy(result); + return CSS_INVALID; + } else if (lwc_string_caseless_isequal(token->idata, + strings[AND], &match) == lwc_error_ok && + match) { + if (op != 0 && op != AND) { + css__mq_cond_destroy(result); + return CSS_INVALID; + } + op = AND; + } else if (lwc_string_caseless_isequal(token->idata, + strings[OR], &match) == lwc_error_ok && + match) { + if (permit_or == false || (op != 0 && op != OR)) { + css__mq_cond_destroy(result); + return CSS_INVALID; + } + op = OR; + } else { + /* Neither AND nor OR */ + css__mq_cond_destroy(result); + return CSS_INVALID; + } + + parserutils_vector_iterate(vector, ctx); + consumeWhitespace(vector, ctx); + } + } + + if (op == OR) { + result->op = 1; + } + + *cond = result; + + return CSS_OK; +} + +/** + * Parse a media query type. + */ +static uint64_t mq_parse_type(lwc_string **strings, lwc_string *type) +{ + bool match; + + if (type == NULL) { + return CSS_MEDIA_ALL; + } else if (lwc_string_caseless_isequal( + type, strings[AURAL], + &match) == lwc_error_ok && match) { + return CSS_MEDIA_AURAL; + } else if (lwc_string_caseless_isequal( + type, strings[BRAILLE], + &match) == lwc_error_ok && match) { + return CSS_MEDIA_BRAILLE; + } else if (lwc_string_caseless_isequal( + type, strings[EMBOSSED], + &match) == lwc_error_ok && match) { + return CSS_MEDIA_EMBOSSED; + } else if (lwc_string_caseless_isequal( + type, strings[HANDHELD], + &match) == lwc_error_ok && match) { + return CSS_MEDIA_HANDHELD; + } else if (lwc_string_caseless_isequal( + type, strings[PRINT], + &match) == lwc_error_ok && match) { + return CSS_MEDIA_PRINT; + } else if (lwc_string_caseless_isequal( + type, strings[PROJECTION], + &match) == lwc_error_ok && match) { + return CSS_MEDIA_PROJECTION; + } else if (lwc_string_caseless_isequal( + type, strings[SCREEN], + &match) == lwc_error_ok && match) { + return CSS_MEDIA_SCREEN; + } else if (lwc_string_caseless_isequal( + type, strings[SPEECH], + &match) == lwc_error_ok && match) { + return CSS_MEDIA_SPEECH; + } else if (lwc_string_caseless_isequal( + type, strings[TTY], + &match) == lwc_error_ok && match) { + return CSS_MEDIA_TTY; + } else if (lwc_string_caseless_isequal( + type, strings[TV], + &match) == lwc_error_ok && match) { + return CSS_MEDIA_TV; + } else if (lwc_string_caseless_isequal( + type, strings[ALL], + &match) == lwc_error_ok && match) { + return CSS_MEDIA_ALL; + } + + return 0; +} + +static css_error mq_parse_media_query(lwc_string **strings, + const parserutils_vector *vector, int *ctx, + css_mq_query **query) +{ + const css_token *token; + bool match, is_condition = false; + css_mq_query *result; + css_error error; + + /* <media-query> = <media-condition> + * | [ not | only ]? <media-type> [ and <media-condition-without-or> ]? + * <media-type> = <ident> (except "not", "and", "or", "only") + */ + + // LPAREN -> media-condition + // not LPAREN -> media-condition + + consumeWhitespace(vector, ctx); + + token = parserutils_vector_peek(vector, *ctx); + if (tokenIsChar(token, '(')) { + is_condition = true; + } else if (token->type == CSS_TOKEN_IDENT && + lwc_string_caseless_isequal(token->idata, + strings[NOT], &match) == lwc_error_ok && + match) { + int old_ctx = *ctx; + + parserutils_vector_iterate(vector, ctx); + consumeWhitespace(vector, ctx); + + token = parserutils_vector_peek(vector, *ctx); + if (tokenIsChar(token, '(')) { + is_condition = true; + } + + *ctx = old_ctx; + } + + result = malloc(sizeof(*result)); + if (result == NULL) { + return CSS_NOMEM; + } + memset(result, 0, sizeof(*result)); + + if (is_condition) { + /* media-condition */ + error = mq_parse_condition(strings, vector, ctx, true, + &result->cond); + if (error != CSS_OK) { + free(result); + return error; + } + + *query = result; + return CSS_OK; + } + + token = parserutils_vector_iterate(vector, ctx); + if (token == NULL || token->type != CSS_TOKEN_IDENT) { + free(result); + return CSS_INVALID; + } + + if (lwc_string_caseless_isequal(token->idata, + strings[NOT], &match) == lwc_error_ok && match) { + result->negate_type = 1; + consumeWhitespace(vector, ctx); + token = parserutils_vector_iterate(vector, ctx); + } else if (lwc_string_caseless_isequal(token->idata, + strings[ONLY], &match) == lwc_error_ok && match) { + consumeWhitespace(vector, ctx); + token = parserutils_vector_iterate(vector, ctx); + } + + if (token == NULL || token->type != CSS_TOKEN_IDENT) { + free(result); + return CSS_INVALID; + } + + result->type = mq_parse_type(strings, token->idata); + + consumeWhitespace(vector, ctx); + + token = parserutils_vector_iterate(vector, ctx); + if (token != NULL) { + if (token->type != CSS_TOKEN_IDENT || + lwc_string_caseless_isequal(token->idata, + strings[AND], &match) != lwc_error_ok || + match == false) { + free(result); + return CSS_INVALID; + } + + consumeWhitespace(vector, ctx); + + error = mq_parse_condition(strings, vector, ctx, false, + &result->cond); + if (error != CSS_OK) { + free(result); + return error; + } + } + + *query = result; + return CSS_OK; +} + +css_error css__mq_parse_media_list(lwc_string **strings, + const parserutils_vector *vector, int *ctx, + css_mq_query **media) +{ + css_mq_query *result = NULL, *last = NULL; + const css_token *token; + css_error error; + + /* <media-query-list> = <media-query> [ COMMA <media-query> ]* */ + + /* if {[(, push }]) to stack + * if func, push ) to stack + * on error, scan forward until stack is empty (or EOF), popping matching tokens off stack + * if stack is empty, the next input token must be comma or EOF + * if comma, consume, and start again from the next input token + */ + + token = parserutils_vector_peek(vector, *ctx); + while (token != NULL) { + css_mq_query *query; + + error = mq_parse_media_query(strings, vector, ctx, &query); + if (error != CSS_OK) { + /* TODO: error recovery (see above) */ + css__mq_query_destroy(result); + return error; + } else { + if (result == NULL) { + result = last = query; + } else { + assert(last != NULL); + last->next = query; + last = query; + } + } + + consumeWhitespace(vector, ctx); + + token = parserutils_vector_iterate(vector, ctx); + if (token != NULL && tokenIsChar(token, ',') == false) { + /* Give up */ + break; + } + } + + *media = result; + + return CSS_OK; +} + +typedef struct css_mq_parse_ctx { + lwc_string **strings; + css_mq_query *media; +} css_mq_parse_ctx; + +static css_error css_parse_media_query_handle_event( + css_parser_event type, + const parserutils_vector *tokens, + void *pw) +{ + int idx = 0; + css_error err; + css_mq_query *media; + const css_token *tok; + css_mq_parse_ctx *ctx = pw; + lwc_string **strings = ctx->strings; + + UNUSED(type); + + /* Skip @media */ + tok = parserutils_vector_iterate(tokens, &idx); + assert(tok->type == CSS_TOKEN_ATKEYWORD); + UNUSED(tok); + + /* Skip whitespace */ + tok = parserutils_vector_iterate(tokens, &idx); + assert(tok->type == CSS_TOKEN_S); + UNUSED(tok); + + err = css__mq_parse_media_list(strings, tokens, &idx, &media); + if (err != CSS_OK) { + return CSS_OK; + } + + ctx->media = media; + return CSS_OK; +} + +css_error css_parse_media_query(lwc_string **strings, + const uint8_t *mq, size_t len, + css_mq_query **media_out) +{ + css_error err; + css_parser *parser; + css_mq_parse_ctx ctx = { + .strings = strings, + }; + css_parser_optparams params_quirks = { + .quirks = false, + }; + css_parser_optparams params_handler = { + .event_handler = { + .handler = css_parse_media_query_handle_event, + .pw = &ctx, + }, + }; + + if (mq == NULL || len == 0) { + return CSS_BADPARM; + } + + err = css__parser_create_for_media_query(NULL, + CSS_CHARSET_DEFAULT, &parser); + if (err != CSS_OK) { + return err; + } + + err = css__parser_setopt(parser, CSS_PARSER_QUIRKS, + ¶ms_quirks); + if (err != CSS_OK) { + css__parser_destroy(parser); + return err; + } + + err = css__parser_setopt(parser, CSS_PARSER_EVENT_HANDLER, + ¶ms_handler); + if (err != CSS_OK) { + css__parser_destroy(parser); + return err; + } + + err = css__parser_parse_chunk(parser, + (const uint8_t *)"@media ", + strlen("@media ")); + if (err != CSS_OK && err != CSS_NEEDDATA) { + css__parser_destroy(parser); + return err; + } + + err = css__parser_parse_chunk(parser, mq, len); + if (err != CSS_OK && err != CSS_NEEDDATA) { + css__parser_destroy(parser); + return err; + } + + err = css__parser_completed(parser); + if (err != CSS_OK) { + css__parser_destroy(parser); + return err; + } + + css__parser_destroy(parser); + + *media_out = ctx.media; + return CSS_OK; +} + diff --git a/src/parse/mq.h b/src/parse/mq.h new file mode 100644 index 0000000..0e2f845 --- /dev/null +++ b/src/parse/mq.h @@ -0,0 +1,100 @@ +/* + * This file is part of LibCSS. + * Licensed under the MIT License, + * http://www.opensource.org/licenses/mit-license.php + * Copyright 2016 John-Mark Bell <jmb@netsurf-browser.org> + */ + +#ifndef css_parse_mq_h_ +#define css_parse_mq_h_ + +#include <parserutils/utils/vector.h> +#include "parse/language.h" + +typedef struct { + enum { + CSS_MQ_VALUE_TYPE_NUM, + CSS_MQ_VALUE_TYPE_DIM, + CSS_MQ_VALUE_TYPE_IDENT, + CSS_MQ_VALUE_TYPE_RATIO + } type; + union { + css_fixed num_or_ratio; /* Where ratio is the result of a/b */ + struct { + css_fixed len; + uint32_t unit; + } dim; + lwc_string *ident; + } data; +} css_mq_value; + +/* + * "name : value" is encoded as "name = value" + * "name" is encoded by setting the operator to "bool" + * "value op name" is encoded verbatim (with op2 set to "unused") + * "name op value" inverts the operator to encode (i.e < becomes >=) (and sets op2 to "unused") + * "value op name op value" is encoded using op2 and value2 + */ +typedef enum { + CSS_MQ_FEATURE_OP_BOOL, /* op only */ + CSS_MQ_FEATURE_OP_UNUSED = CSS_MQ_FEATURE_OP_BOOL, /* op2 only */ + + CSS_MQ_FEATURE_OP_LT, + CSS_MQ_FEATURE_OP_LTE, + CSS_MQ_FEATURE_OP_EQ, /* op only */ + CSS_MQ_FEATURE_OP_GTE, + CSS_MQ_FEATURE_OP_GT +} css_mq_feature_op; + +typedef struct { + lwc_string *name; + css_mq_feature_op op; + css_mq_feature_op op2; + css_mq_value value; + css_mq_value value2; +} css_mq_feature; + +typedef struct css_mq_cond_or_feature css_mq_cond_or_feature; + +typedef struct { + uint32_t nparts; + css_mq_cond_or_feature **parts; +} css_mq_cond_parts; + +typedef struct { + uint32_t negate : 1, /* set if "not" */ + op : 1; /* clear if "and", set if "or" */ + css_mq_cond_parts *parts; +} css_mq_cond; + +struct css_mq_cond_or_feature { + enum { + CSS_MQ_FEATURE, + CSS_MQ_COND + } type; + union { + css_mq_cond *cond; + css_mq_feature *feat; + } data; +}; + +typedef struct css_mq_query { + struct css_mq_query *next; + + uint32_t negate_type : 1; /* set if "not type" */ + uint64_t type; /* or NULL */ + + css_mq_cond *cond; +} css_mq_query; + +css_error css_parse_media_query(lwc_string **strings, + const uint8_t *mq, size_t len, + css_mq_query **media_out); + +css_error css__mq_parse_media_list(lwc_string **strings, + const parserutils_vector *vector, int *ctx, + css_mq_query **media); + +void css__mq_query_destroy(css_mq_query *media); + +#endif diff --git a/src/parse/parse.c b/src/parse/parse.c index 4cc1c98..cbd8b56 100644 --- a/src/parse/parse.c +++ b/src/parse/parse.c @@ -67,7 +67,8 @@ enum { sMalformedAtRule = 22, sInlineStyle = 23, sISBody0 = 24, - sISBody = 25 + sISBody = 25, + sMediaQuery = 26, }; /** @@ -99,8 +100,6 @@ struct css_parser bool parseError; /**< A parse error has occurred */ parserutils_stack *open_items; /**< Stack of open brackets */ - uint8_t match_char; /**< Close bracket type for parseAny */ - bool last_was_ws; /**< Last token was whitespace */ css_parser_event_handler event; /**< Client's event handler */ @@ -146,8 +145,9 @@ static css_error parseMalformedAtRule(css_parser *parser); static css_error parseInlineStyle(css_parser *parser); static css_error parseISBody0(css_parser *parser); static css_error parseISBody(css_parser *parser); +static css_error parseMediaQuery(css_parser *parser); -static void unref_interned_strings_in_tokens(css_parser *parser); +static void discard_tokens(css_parser *parser); /** * Dispatch table for parsing, indexed by major state number @@ -178,7 +178,8 @@ static css_error (*parseFuncs[])(css_parser *parser) = { parseMalformedAtRule, parseInlineStyle, parseISBody0, - parseISBody + parseISBody, + parseMediaQuery, }; /** @@ -220,6 +221,25 @@ css_error css__parser_create_for_inline_style(const char *charset, } /** + * Create a CSS parser for a media query + * + * \param charset Charset of data, if known, or NULL + * \param cs_source Source of charset information, or CSS_CHARSET_DEFAULT + * \param parser Pointer to location to receive parser instance + * \return CSS_OK on success, + * CSS_BADPARM on bad parameters, + * CSS_NOMEM on memory exhaustion + */ +css_error css__parser_create_for_media_query(const char *charset, + css_charset_source cs_source, css_parser **parser) +{ + parser_state initial = { sMediaQuery, 0 }; + + return css__parser_create_internal(charset, cs_source, + initial, parser); +} + +/** * Destroy a CSS parser * * \param parser The parser instance to destroy @@ -459,7 +479,6 @@ css_error css__parser_create_internal(const char *charset, p->quirks = false; p->pushback = NULL; p->parseError = false; - p->match_char = 0; p->event = NULL; p->last_was_ws = false; p->event_pw = NULL; @@ -746,8 +765,7 @@ css_error parseStart(css_parser *parser) parser->event_pw); } - unref_interned_strings_in_tokens(parser); - parserutils_vector_clear(parser->tokens); + discard_tokens(parser); return done(parser); } @@ -778,8 +796,7 @@ css_error parseStylesheet(css_parser *parser) if (error != CSS_OK) return error; - unref_interned_strings_in_tokens(parser); - parserutils_vector_clear(parser->tokens); + discard_tokens(parser); return done(parser); case CSS_TOKEN_CDO: @@ -849,8 +866,7 @@ css_error parseRuleset(css_parser *parser) switch (state->substate) { case Initial: - unref_interned_strings_in_tokens(parser); - parserutils_vector_clear(parser->tokens); + discard_tokens(parser); error = getToken(parser, &token); if (error != CSS_OK) @@ -890,17 +906,6 @@ css_error parseRuleset(css_parser *parser) } break; case Brace: -#if !defined(NDEBUG) && defined(DEBUG_EVENTS) - printf("Begin ruleset\n"); - parserutils_vector_dump(parser->tokens, __func__, tprinter); -#endif - if (parser->parseError == false && parser->event != NULL) { - if (parser->event(CSS_PARSER_START_RULESET, - parser->tokens, parser->event_pw) == - CSS_INVALID) - parser->parseError = true; - } - if (parser->parseError == true) { parser_state to = { sMalformedSelector, Initial }; @@ -911,22 +916,40 @@ css_error parseRuleset(css_parser *parser) if (error != CSS_OK) return error; - if (token->type == CSS_TOKEN_EOF) { + if (token->type != CSS_TOKEN_CHAR || + lwc_string_length(token->idata) != 1 || + lwc_string_data(token->idata)[0] != '{') { + /* FOLLOW(selector) contains only '{', but we may + * also have seen EOF, which is a parse error. */ error = pushBack(parser, token); if (error != CSS_OK) return error; + parser->parseError = true; return done(parser); } - if (token->type != CSS_TOKEN_CHAR || - lwc_string_length(token->idata) != 1 || - lwc_string_data(token->idata)[0] != '{') { - /* This should never happen, as FOLLOW(selector) - * contains only '{' */ - assert(0 && "Expected {"); + /* We don't want to emit the brace, so push it back */ + error = pushBack(parser, token); + if (error != CSS_OK) + return error; + +#if !defined(NDEBUG) && defined(DEBUG_EVENTS) + printf("Begin ruleset\n"); + parserutils_vector_dump(parser->tokens, __func__, tprinter); +#endif + if (parser->parseError == false && parser->event != NULL) { + if (parser->event(CSS_PARSER_START_RULESET, + parser->tokens, parser->event_pw) == + CSS_INVALID) + parser->parseError = true; } + /* Re-read the brace */ + error = getToken(parser, &token); + if (error != CSS_OK) + return error; + state->substate = WS; /* Fall through */ case WS: @@ -1041,8 +1064,7 @@ css_error parseAtRule(css_parser *parser) switch (state->substate) { case Initial: - unref_interned_strings_in_tokens(parser); - parserutils_vector_clear(parser->tokens); + discard_tokens(parser); error = getToken(parser, &token); if (error != CSS_OK) @@ -1212,8 +1234,7 @@ css_error parseBlock(css_parser *parser) assert(0 && "Expected {"); } - unref_interned_strings_in_tokens(parser); - parserutils_vector_clear(parser->tokens); + discard_tokens(parser); state->substate = WS; /* Fall through */ @@ -1269,8 +1290,7 @@ css_error parseBlock(css_parser *parser) parser->event(CSS_PARSER_END_BLOCK, NULL, parser->event_pw); } - unref_interned_strings_in_tokens(parser); - parserutils_vector_clear(parser->tokens); + discard_tokens(parser); return done(parser); } @@ -1322,10 +1342,7 @@ css_error parseBlockContent(css_parser *parser) parser->event_pw); } - unref_interned_strings_in_tokens( - parser); - parserutils_vector_clear( - parser->tokens); + discard_tokens(parser); return transition(parser, to, subsequent); @@ -1353,10 +1370,7 @@ css_error parseBlockContent(css_parser *parser) if (error != CSS_OK) return error; - unref_interned_strings_in_tokens( - parser); - parserutils_vector_clear( - parser->tokens); + discard_tokens(parser); state->substate = WS; } else if (lwc_string_length( @@ -1379,10 +1393,7 @@ css_error parseBlockContent(css_parser *parser) parser->event_pw); } - unref_interned_strings_in_tokens( - parser); - parserutils_vector_clear( - parser->tokens); + discard_tokens(parser); return done(parser); } @@ -1401,8 +1412,7 @@ css_error parseBlockContent(css_parser *parser) parser->event_pw); } - unref_interned_strings_in_tokens(parser); - parserutils_vector_clear(parser->tokens); + discard_tokens(parser); return done(parser); } @@ -1445,8 +1455,7 @@ css_error parseSelector(css_parser *parser) parser_state to = { sAny1, Initial }; parser_state subsequent = { sSelector, AfterAny1 }; - unref_interned_strings_in_tokens(parser); - parserutils_vector_clear(parser->tokens); + discard_tokens(parser); return transition(parser, to, subsequent); } @@ -1472,8 +1481,7 @@ css_error parseDeclaration(css_parser *parser) parser_state to = { sProperty, Initial }; parser_state subsequent = { sDeclaration, Colon }; - unref_interned_strings_in_tokens(parser); - parserutils_vector_clear(parser->tokens); + discard_tokens(parser); return transition(parser, to, subsequent); } @@ -1945,6 +1953,9 @@ css_error parseAny1(css_parser *parser) if (error != CSS_OK) return error; + if (token->type == CSS_TOKEN_EOF) + return done(parser); + /* Grammar ambiguity: any0 can be followed by * '{', ';', ')', ']'. any1 can only be followed by '{'. */ if (token->type == CSS_TOKEN_CHAR && @@ -2025,18 +2036,19 @@ css_error parseAny(css_parser *parser) } if (token->type == CSS_TOKEN_FUNCTION) { - parser->match_char = ')'; + parserutils_stack_push(parser->open_items, &")"[0]); state->substate = WS; } else if (token->type == CSS_TOKEN_CHAR && lwc_string_length(token->idata) == 1 && (lwc_string_data(token->idata)[0] == '(' || lwc_string_data(token->idata)[0] == '[')) { - parser->match_char = lwc_string_data( - token->idata)[0] == '(' ? ')' : ']'; + parserutils_stack_push(parser->open_items, + &(lwc_string_data( + token->idata)[0] == '(' ? ")" : "]")[0]); state->substate = WS; + } else { + state->substate = WS2; } - - state->substate = WS2; /* Fall through */ case WS: case WS2: @@ -2063,11 +2075,24 @@ css_error parseAny(css_parser *parser) if (error != CSS_OK) return error; + if (token->type == CSS_TOKEN_EOF) { + error = pushBack(parser, token); + if (error != CSS_OK) + return error; + + /* parse error */ + parser->parseError = true; + + return done(parser); + } + /* Match correct close bracket (grammar ambiguity) */ if (token->type == CSS_TOKEN_CHAR && lwc_string_length(token->idata) == 1 && lwc_string_data(token->idata)[0] == - parser->match_char) { + ((uint8_t *) parserutils_stack_get_current( + parser->open_items))[0]) { + parserutils_stack_pop(parser->open_items, NULL); state->substate = WS2; goto ws2; } @@ -2174,8 +2199,7 @@ css_error parseMalformedDeclaration(css_parser *parser) return error; /* Discard the tokens we've read */ - unref_interned_strings_in_tokens(parser); - parserutils_vector_clear(parser->tokens); + discard_tokens(parser); return done(parser); } @@ -2270,8 +2294,7 @@ css_error parseMalformedSelector(css_parser *parser) return error; /* Discard the tokens we've read */ - unref_interned_strings_in_tokens(parser); - parserutils_vector_clear(parser->tokens); + discard_tokens(parser); return done(parser); } @@ -2379,8 +2402,7 @@ css_error parseMalformedAtRule(css_parser *parser) return error; /* Discard the tokens we've read */ - unref_interned_strings_in_tokens(parser); - parserutils_vector_clear(parser->tokens); + discard_tokens(parser); return done(parser); } @@ -2425,7 +2447,7 @@ css_error parseInlineStyle(css_parser *parser) } case AfterISBody0: /* Clean up any remaining tokens */ - unref_interned_strings_in_tokens(parser); + discard_tokens(parser); /* Emit remaining fake events to end the parse */ if (parser->event != NULL) { @@ -2571,22 +2593,48 @@ css_error parseISBody(css_parser *parser) return done(parser); } +css_error parseMediaQuery(css_parser *parser) +{ + enum { Initial = 0, AfterAtRule = 1 }; + parser_state *state = parserutils_stack_get_current(parser->states); + + /* media-query = at-rule */ + + switch (state->substate) { + case Initial: + { + parser_state to = { sAtRule, Initial }; + parser_state subsequent = { sMediaQuery, AfterAtRule }; + + return transition(parser, to, subsequent); + } + case AfterAtRule: + /* Clean up any remaining tokens */ + discard_tokens(parser); + break; + } + + return done(parser); +} + /** - * Iterate the token vector and unref any interned strings in the tokens. + * Discard the contents of the token vector * * \param parser The parser whose tokens we are cleaning up. */ -void unref_interned_strings_in_tokens(css_parser *parser) +void discard_tokens(css_parser *parser) { - int32_t ctx = 0; - const css_token *tok; + int32_t ctx = 0; + const css_token *tok; - while ((tok = parserutils_vector_iterate( + while ((tok = parserutils_vector_iterate( parser->tokens, &ctx)) != NULL) { - if (tok->idata != NULL) { - lwc_string_unref(tok->idata); + if (tok->idata != NULL) { + lwc_string_unref(tok->idata); } - } + } + + parserutils_vector_clear(parser->tokens); } #ifndef NDEBUG @@ -2604,7 +2652,11 @@ static void tprinter(void *token) { css_token *t = token; - if (t->data.data) + if (t->idata) { + printf("%d: %.*s", t->type, + (int) lwc_string_length(t->idata), + lwc_string_data(t->idata)); + } else if (t->data.data) printf("%d: %.*s", t->type, (int) t->data.len, t->data.data); else printf("%d", t->type); diff --git a/src/parse/parse.h b/src/parse/parse.h index 833aa51..e65f055 100644 --- a/src/parse/parse.h +++ b/src/parse/parse.h @@ -61,6 +61,8 @@ css_error css__parser_create(const char *charset, css_charset_source cs_source, css_parser **parser); css_error css__parser_create_for_inline_style(const char *charset, css_charset_source cs_source, css_parser **parser); +css_error css__parser_create_for_media_query(const char *charset, + css_charset_source cs_source, css_parser **parser); css_error css__parser_destroy(css_parser *parser); css_error css__parser_setopt(css_parser *parser, css_parser_opttype type, diff --git a/src/parse/properties/utils.c b/src/parse/properties/utils.c index 7abef24..0e49853 100644 --- a/src/parse/properties/utils.c +++ b/src/parse/properties/utils.c @@ -1007,6 +1007,12 @@ css_error css__parse_unit_keyword(const char *ptr, size_t len, uint32_t *unit) if (len == 4) { if (strncasecmp(ptr, "grad", 4) == 0) *unit = UNIT_GRAD; + else if (strncasecmp(ptr, "turn", 4) == 0) + *unit = UNIT_TURN; + else if (strncasecmp(ptr, "dppx", 4) == 0) + *unit = UNIT_DPPX; + else if (strncasecmp(ptr, "dpcm", 4) == 0) + *unit = UNIT_DPCM; else if (strncasecmp(ptr, "vmin", 4) == 0) *unit = UNIT_VMIN; else if (strncasecmp(ptr, "vmax", 4) == 0) @@ -1026,6 +1032,8 @@ css_error css__parse_unit_keyword(const char *ptr, size_t len, uint32_t *unit) *unit = UNIT_REM; else if (strncasecmp(ptr, "rlh", 3) == 0) *unit = UNIT_RLH; + else if (strncasecmp(ptr, "dpi", 3) == 0) + *unit = UNIT_DPI; else return CSS_INVALID; } else if (len == 2) { diff --git a/src/parse/propstrings.c b/src/parse/propstrings.c index bfd2965..3c9401b 100644 --- a/src/parse/propstrings.c +++ b/src/parse/propstrings.c @@ -439,6 +439,10 @@ const stringmap_entry stringmap[LAST_KNOWN] = { { "column-reverse", SLEN("column-reverse") }, { "wrap", SLEN("wrap") }, { "wrap-reverse", SLEN("wrap-reverse") }, + { "and", SLEN("and") }, + { "or", SLEN("or") }, + { "only", SLEN("only") }, + { "infinite", SLEN("infinite") }, { "aliceblue", SLEN("aliceblue") }, { "antiquewhite", SLEN("antiquewhite") }, diff --git a/src/parse/propstrings.h b/src/parse/propstrings.h index 67eaa5f..24b681b 100644 --- a/src/parse/propstrings.h +++ b/src/parse/propstrings.h @@ -101,7 +101,7 @@ enum { AVOID_PAGE, AVOID_COLUMN, BALANCE, HORIZONTAL_TB, VERTICAL_RL, VERTICAL_LR, CONTENT_BOX, BORDER_BOX, STRETCH, INLINE_FLEX, FLEX_START, FLEX_END, SPACE_BETWEEN, SPACE_AROUND, SPACE_EVENLY, ROW, ROW_REVERSE, - COLUMN_REVERSE, WRAP_STRING, WRAP_REVERSE, + COLUMN_REVERSE, WRAP_STRING, WRAP_REVERSE, AND, OR, ONLY, INFINITE, /* Named colours */ FIRST_COLOUR, diff --git a/src/select/computed.c b/src/select/computed.c index ebb2b29..506b079 100644 --- a/src/select/computed.c +++ b/src/select/computed.c @@ -250,7 +250,7 @@ css_error css__computed_style_initialise(css_computed_style *style, return CSS_BADPARM; state.node = NULL; - state.media = CSS_MEDIA_ALL; + state.media = NULL; state.results = NULL; state.computed = style; state.handler = handler; diff --git a/src/select/hash.h b/src/select/hash.h index 71f610f..aecf15a 100644 --- a/src/select/hash.h +++ b/src/select/hash.h @@ -25,7 +25,7 @@ struct css_hash_selection_requirments { lwc_string *class; /* Name of class, or NULL */ lwc_string *id; /* Name of id, or NULL */ lwc_string *uni; /* Universal element string "*" */ - uint64_t media; /* Media type(s) we're selecting for */ + const css_media *media; /* Media spec we're selecting for */ const css_bloom *node_bloom; /* Node's bloom filter */ }; diff --git a/src/select/mq.h b/src/select/mq.h index a0a9f6d..290505c 100644 --- a/src/select/mq.h +++ b/src/select/mq.h @@ -10,13 +10,51 @@ #define css_select_mq_h_ /** + * Match media query conditions. + * + * \param[in] cond Condition to match. + * \return true if condition matches, otherwise false. + */ +static inline bool mq_match_condition(css_mq_cond *cond) +{ + /* TODO: Implement this. */ + (void) cond; + return true; +} + +/** + * Test whether media query list matches current media. + * + * If anything in the list matches, the list matches. If none match + * it doesn't match. + * + * \param m Media query list. + * \meaid media Current media spec, to check against m. + * \return true if media query list matches media + */ +static inline bool mq__list_match(const css_mq_query *m, const css_media *media) +{ + for (; m != NULL; m = m->next) { + /* Check type */ + if (!!(m->type & media->type) != m->negate_type) { + if (mq_match_condition(m->cond)) { + /* We have a match, no need to look further. */ + return true; + } + } + } + + return false; +} + +/** * Test whether the rule applies for current media. * - * \param rule Rule to test - * \meaid media Current media type(s) + * \param rule Rule to test + * \param media Current media type(s) * \return true iff chain's rule applies for media */ -static inline bool mq_rule_good_for_media(const css_rule *rule, uint64_t media) +static inline bool mq_rule_good_for_media(const css_rule *rule, const css_media *media) { bool applies = true; const css_rule *ancestor = rule; @@ -24,10 +62,11 @@ static inline bool mq_rule_good_for_media(const css_rule *rule, uint64_t media) 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->type == CSS_RULE_MEDIA) { + applies = mq__list_match(m->media, media); + if (applies == false) { + break; + } } if (ancestor->ptype != CSS_RULE_PARENT_STYLESHEET) { diff --git a/src/select/select.c b/src/select/select.c index 644369a..1b0cadd 100644 --- a/src/select/select.c +++ b/src/select/select.c @@ -38,7 +38,7 @@ typedef struct css_select_sheet { const css_stylesheet *sheet; /**< Stylesheet */ css_origin origin; /**< Stylesheet origin */ - uint64_t media; /**< Applicable media */ + css_mq_query *media; /**< Applicable media */ } css_select_sheet; /** @@ -97,7 +97,7 @@ typedef struct css_select_font_faces_list { */ typedef struct css_select_font_faces_state { lwc_string *font_family; - uint64_t media; + const css_media *media; css_select_font_faces_list ua_font_faces; css_select_font_faces_list user_font_faces; @@ -289,8 +289,12 @@ css_error css_select_ctx_destroy(css_select_ctx *ctx) if (ctx->default_style != NULL) css_computed_style_destroy(ctx->default_style); - if (ctx->sheets != NULL) + if (ctx->sheets != NULL) { + for (uint32_t index = 0; index < ctx->n_sheets; index++) { + css__mq_query_destroy(ctx->sheets[index].media); + } free(ctx->sheets); + } free(ctx); @@ -303,12 +307,12 @@ css_error css_select_ctx_destroy(css_select_ctx *ctx) * \param ctx The context to append to * \param sheet The sheet to append * \param origin Origin of the sheet - * \param media Media types to which the sheet applies + * \param media Media string for the stylesheet * \return CSS_OK on success, appropriate error otherwise */ css_error css_select_ctx_append_sheet(css_select_ctx *ctx, const css_stylesheet *sheet, css_origin origin, - uint64_t media) + const char *media) { if (ctx == NULL || sheet == NULL) return CSS_BADPARM; @@ -324,14 +328,16 @@ css_error css_select_ctx_append_sheet(css_select_ctx *ctx, * \param sheet Sheet to insert * \param index Index in context to insert sheet * \param origin Origin of the sheet - * \param media Media types to which the sheet applies + * \param media Media string for the stylesheet * \return CSS_OK on success, appropriate error otherwise */ css_error css_select_ctx_insert_sheet(css_select_ctx *ctx, const css_stylesheet *sheet, uint32_t index, - css_origin origin, uint64_t media) + css_origin origin, const char *media) { css_select_sheet *temp; + css_mq_query *mq; + css_error error; if (ctx == NULL || sheet == NULL) return CSS_BADPARM; @@ -357,9 +363,23 @@ css_error css_select_ctx_insert_sheet(css_select_ctx *ctx, (ctx->n_sheets - index) * sizeof(css_select_sheet)); } + error = css_parse_media_query(sheet->propstrings, + (const uint8_t *)media, + (media == NULL) ? 0 : strlen(media), &mq); + if (error == CSS_NOMEM) { + return error; + } else if (error != CSS_OK) { + /* Fall back to default media: "all". */ + mq = calloc(1, sizeof(*mq)); + if (mq == NULL) { + return CSS_NOMEM; + } + mq->type = CSS_MEDIA_ALL; + } + ctx->sheets[index].sheet = sheet; ctx->sheets[index].origin = origin; - ctx->sheets[index].media = media; + ctx->sheets[index].media = mq; ctx->n_sheets++; @@ -389,6 +409,8 @@ css_error css_select_ctx_remove_sheet(css_select_ctx *ctx, if (index == ctx->n_sheets) return CSS_INVALID; + css__mq_query_destroy(ctx->sheets[index].media); + ctx->n_sheets--; memmove(&ctx->sheets[index], &ctx->sheets[index + 1], @@ -1032,7 +1054,7 @@ static void css_select__finalise_selection_state( * \param[in] state The selection state to initialise * \param[in] node The node we are selecting for. * \param[in] parent The node's parent node, or NULL. - * \param[in] media The media type we're selecting for. + * \param[in] media The media specification we're selecting for. * \param[in] handler The client selection callback table. * \param[in] pw The client private data, passsed out to callbacks. * \return CSS_OK or appropriate error otherwise. @@ -1041,7 +1063,7 @@ static css_error css_select__initialise_selection_state( css_select_state *state, void *node, void *parent, - uint64_t media, + const css_media *media, css_select_handler *handler, void *pw) { @@ -1142,7 +1164,7 @@ failed: * * \param ctx Selection context to use * \param node Node to select style for - * \param media Currently active media types + * \param media Currently active media specification * \param inline_style Corresponding inline style for node, or NULL * \param handler Dispatch table of handler functions * \param pw Client-specific private data for handler functions @@ -1159,7 +1181,7 @@ failed: * update the fully computed style for a node when layout changes. */ css_error css_select_style(css_select_ctx *ctx, void *node, - uint64_t media, const css_stylesheet *inline_style, + const css_media *media, const css_stylesheet *inline_style, css_select_handler *handler, void *pw, css_select_results **result) { @@ -1244,7 +1266,7 @@ css_error css_select_style(css_select_ctx *ctx, void *node, for (i = 0; i < ctx->n_sheets; i++) { const css_select_sheet s = ctx->sheets[i]; - if ((s.media & media) != 0 && + if (mq__list_match(s.media, media) && s.sheet->disabled == false) { error = select_from_sheet(ctx, s.sheet, s.origin, &state); @@ -1394,13 +1416,13 @@ css_error css_select_results_destroy(css_select_results *results) * Search a selection context for defined font faces * * \param ctx Selection context - * \param media Currently active media types + * \param media Currently active media spec * \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, + const css_media *media, lwc_string *font_family, css_select_font_faces_results **result) { uint32_t i; @@ -1421,7 +1443,7 @@ css_error css_select_font_faces(css_select_ctx *ctx, for (i = 0; i < ctx->n_sheets; i++) { const css_select_sheet s = ctx->sheets[i]; - if ((s.media & media) != 0 && + if (mq__list_match(s.media, media) && s.sheet->disabled == false) { error = select_font_faces_from_sheet(s.sheet, s.origin, &state); @@ -1847,7 +1869,8 @@ css_error select_from_sheet(css_select_ctx *ctx, const css_stylesheet *sheet, (const css_rule_import *) rule; if (import->sheet != NULL && - (import->media & state->media) != 0) { + mq__list_match(import->media, + state->media)) { /* It's applicable, so process it */ if (sp >= IMPORT_STACK_SIZE) return CSS_NOMEM; @@ -1954,7 +1977,8 @@ static css_error select_font_faces_from_sheet( (const css_rule_import *) rule; if (import->sheet != NULL && - (import->media & state->media) != 0) { + mq__list_match(import->media, + state->media)) { /* It's applicable, so process it */ if (sp >= IMPORT_STACK_SIZE) return CSS_NOMEM; diff --git a/src/select/select.h b/src/select/select.h index 70f1ced..dc9aa4a 100644 --- a/src/select/select.h +++ b/src/select/select.h @@ -63,7 +63,7 @@ struct css_node_data { */ typedef struct css_select_state { void *node; /* Node we're selecting for */ - uint64_t media; /* Currently active media types */ + const css_media *media; /* Currently active media spec */ css_select_results *results; /* Result set to populate */ css_pseudo_element current_pseudo; /* Current pseudo element */ diff --git a/src/stylesheet.c b/src/stylesheet.c index 7c6728b..22c7681 100644 --- a/src/stylesheet.c +++ b/src/stylesheet.c @@ -12,6 +12,7 @@ #include "stylesheet.h" #include "bytecode/bytecode.h" #include "parse/language.h" +#include "parse/mq.h" #include "utils/parserutilserror.h" #include "utils/utils.h" #include "select/dispatch.h" @@ -377,8 +378,6 @@ css_error css_stylesheet_data_done(css_stylesheet *sheet) * \param parent Parent stylesheet * \param url Pointer to object to be populated with details of URL of * imported stylesheet (potentially relative) - * \param media Pointer to location to receive applicable media types for - * imported sheet, * \return CSS_OK on success, * CSS_INVALID if there are no pending imports remaining * @@ -396,11 +395,11 @@ css_error css_stylesheet_data_done(css_stylesheet *sheet) * register an empty stylesheet with the parent in its place. */ css_error css_stylesheet_next_pending_import(css_stylesheet *parent, - lwc_string **url, uint64_t *media) + lwc_string **url) { const css_rule *r; - if (parent == NULL || url == NULL || media == NULL) + if (parent == NULL || url == NULL) return CSS_BADPARM; for (r = parent->rule_list; r != NULL; r = r->next) { @@ -413,7 +412,6 @@ css_error css_stylesheet_next_pending_import(css_stylesheet *parent, if (r->type == CSS_RULE_IMPORT && i->sheet == NULL) { *url = lwc_string_ref(i->url); - *media = i->media; return CSS_OK; } @@ -1154,6 +1152,9 @@ css_error css__stylesheet_rule_destroy(css_stylesheet *sheet, css_rule *rule) css_rule_import *import = (css_rule_import *) rule; lwc_string_unref(import->url); + if (import->media != NULL) { + css__mq_query_destroy(import->media); + } /* Do not destroy imported sheet: it is owned by the client */ } @@ -1163,6 +1164,10 @@ css_error css__stylesheet_rule_destroy(css_stylesheet *sheet, css_rule *rule) css_rule_media *media = (css_rule_media *) rule; css_rule *c, *d; + if (media->media != NULL) { + css__mq_query_destroy(media->media); + } + for (c = media->first_child; c != NULL; c = d) { d = c->next; @@ -1326,7 +1331,7 @@ css_error css__stylesheet_rule_set_charset(css_stylesheet *sheet, */ css_error css__stylesheet_rule_set_nascent_import(css_stylesheet *sheet, css_rule *rule, lwc_string *url, - uint64_t media) + css_mq_query *media) { css_rule_import *r = (css_rule_import *) rule; @@ -1352,7 +1357,7 @@ css_error css__stylesheet_rule_set_nascent_import(css_stylesheet *sheet, * \return CSS_OK on success, appropriate error otherwise */ css_error css__stylesheet_rule_set_media(css_stylesheet *sheet, - css_rule *rule, uint64_t media) + css_rule *rule, css_mq_query *media) { css_rule_media *r = (css_rule_media *) rule; diff --git a/src/stylesheet.h b/src/stylesheet.h index 18e077e..a44ad1f 100644 --- a/src/stylesheet.h +++ b/src/stylesheet.h @@ -20,6 +20,7 @@ #include "bytecode/bytecode.h" #include "parse/parse.h" +#include "parse/mq.h" #include "select/hash.h" typedef struct css_rule css_rule; @@ -132,7 +133,7 @@ typedef struct css_rule_selector { typedef struct css_rule_media { css_rule base; - uint64_t media; + css_mq_query *media; css_rule *first_child; css_rule *last_child; @@ -155,7 +156,7 @@ typedef struct css_rule_import { css_rule base; lwc_string *url; - uint64_t media; + css_mq_query *media; css_stylesheet *sheet; } css_rule_import; @@ -268,10 +269,10 @@ css_error css__stylesheet_rule_set_charset(css_stylesheet *sheet, css_rule *rule, lwc_string *charset); css_error css__stylesheet_rule_set_nascent_import(css_stylesheet *sheet, - css_rule *rule, lwc_string *url, uint64_t media); + css_rule *rule, lwc_string *url, css_mq_query *media); css_error css__stylesheet_rule_set_media(css_stylesheet *sheet, - css_rule *rule, uint64_t media); + css_rule *rule, css_mq_query *media); css_error css__stylesheet_rule_set_page_selector(css_stylesheet *sheet, css_rule *rule, css_selector *sel); diff --git a/test/css21.c b/test/css21.c index a29fae1..cdd66f7 100644 --- a/test/css21.c +++ b/test/css21.c @@ -99,10 +99,8 @@ int main(int argc, char **argv) while (error == CSS_IMPORTS_PENDING) { lwc_string *url; - uint64_t media; - error = css_stylesheet_next_pending_import(sheet, - &url, &media); + error = css_stylesheet_next_pending_import(sheet, &url); assert(error == CSS_OK || error == CSS_INVALID); if (error == CSS_OK) { diff --git a/test/data/parse2/illegal-values.dat b/test/data/parse2/illegal-values.dat index 3187e18..2d58b54 100644 --- a/test/data/parse2/illegal-values.dat +++ b/test/data/parse2/illegal-values.dat @@ -864,7 +864,7 @@ #reset #data -* { display: +* { display: #errors #expected | * diff --git a/test/parse-auto.c b/test/parse-auto.c index 58ccf9a..5f926e3 100644 --- a/test/parse-auto.c +++ b/test/parse-auto.c @@ -395,10 +395,8 @@ void run_test(const uint8_t *data, size_t len, exp_entry *exp, size_t explen) while (error == CSS_IMPORTS_PENDING) { lwc_string *url; - uint64_t media; - error = css_stylesheet_next_pending_import(sheet, - &url, &media); + error = css_stylesheet_next_pending_import(sheet, &url); assert(error == CSS_OK || error == CSS_INVALID); if (error == CSS_OK) { diff --git a/test/select.c b/test/select.c index f21d937..664994e 100644 --- a/test/select.c +++ b/test/select.c @@ -42,7 +42,7 @@ typedef struct node { typedef struct sheet_ctx { css_stylesheet *sheet; css_origin origin; - uint64_t media; + char *media; } sheet_ctx; typedef struct line_ctx { @@ -62,7 +62,7 @@ typedef struct line_ctx { uint32_t n_sheets; sheet_ctx *sheets; - uint64_t media; + css_media media; uint32_t pseudo_element; node *target; @@ -77,7 +77,7 @@ static bool handle_line(const char *data, size_t datalen, void *pw); static void css__parse_tree(line_ctx *ctx, const char *data, size_t len); static void css__parse_tree_data(line_ctx *ctx, const char *data, size_t len); static void css__parse_sheet(line_ctx *ctx, const char *data, size_t len); -static void css__parse_media_list(const char **data, size_t *len, uint64_t *media); +static void css__parse_media_list(const char **data, size_t *len, css_media *media); static void css__parse_pseudo_list(const char **data, size_t *len, uint32_t *element); static void css__parse_expected(line_ctx *ctx, const char *data, size_t len); @@ -363,7 +363,7 @@ void css__parse_tree(line_ctx *ctx, const char *data, size_t len) /* [ <media_list> <pseudo>? ] ? */ - ctx->media = CSS_MEDIA_ALL; + ctx->media.type = CSS_MEDIA_ALL; ctx->pseudo_element = CSS_PSEUDO_ELEMENT_NONE; /* Consume any leading whitespace */ @@ -515,9 +515,9 @@ void css__parse_sheet(line_ctx *ctx, const char *data, size_t len) const char *p; const char *end = data + len; css_origin origin = CSS_ORIGIN_AUTHOR; - uint64_t media = CSS_MEDIA_ALL; css_stylesheet *sheet; sheet_ctx *temp; + char *media = NULL; /* <origin> <media_list>? */ @@ -540,11 +540,11 @@ void css__parse_sheet(line_ctx *ctx, const char *data, size_t len) while (p < end && isspace(*p)) p++; - if (p < end) { - size_t ignored = end - p; - - css__parse_media_list(&p, &ignored, &media); - } + assert(end >= p); + media = malloc(end - p + 1); + assert(media != NULL); + memcpy(media, p, end - p); + media[end - p] = '\0'; params.params_version = CSS_STYLESHEET_PARAMS_VERSION_1; params.level = CSS_LEVEL_21; @@ -579,7 +579,7 @@ void css__parse_sheet(line_ctx *ctx, const char *data, size_t len) ctx->n_sheets++; } -void css__parse_media_list(const char **data, size_t *len, uint64_t *media) +void css__parse_media_list(const char **data, size_t *len, css_media *media) { const char *p = *data; const char *end = p + *len; @@ -646,7 +646,7 @@ void css__parse_media_list(const char **data, size_t *len, uint64_t *media) p++; } - *media = result; + media->type = result; *data = p; *len = end - p; @@ -765,7 +765,7 @@ static void run_test_select_tree(css_select_ctx *select, css_select_results *sr; struct node *n = NULL; - assert(css_select_style(select, node, ctx->media, NULL, + assert(css_select_style(select, node, &ctx->media, NULL, &select_handler, ctx, &sr) == CSS_OK); if (node->parent != NULL) { @@ -841,6 +841,7 @@ void run_test(line_ctx *ctx, const char *exp, size_t explen) for (i = 0; i < ctx->n_sheets; i++) { css_stylesheet_destroy(ctx->sheets[i].sheet); + free(ctx->sheets[i].media); } ctx->tree = NULL; |