diff --git a/src/parse/properties/Makefile b/src/parse/properties/Makefile index df84d49..5d43564 100644 --- a/src/parse/properties/Makefile +++ b/src/parse/properties/Makefile @@ -51,6 +51,7 @@ DIR_SOURCES := \ font.c \ font_family.c \ font_weight.c \ + gap.c \ list_style.c \ list_style_type.c \ margin.c \ diff --git a/src/parse/properties/gap.c b/src/parse/properties/gap.c new file mode 100644 index 0000000..8e8027c --- /dev/null +++ b/src/parse/properties/gap.c @@ -0,0 +1,164 @@ +/* + * This file is part of LibCSS. + * Licensed under the MIT License, + * http://www.opensource.org/licenses/mit-license.php + * Copyright 2026 Cyril Hrubis + */ + +#include +#include + +#include "bytecode/bytecode.h" +#include "bytecode/opcodes.h" +#include "parse/properties/properties.h" +#include "parse/properties/utils.h" + +/** + * Parse a single gap component: normal | + * + * \param c Parsing context + * \param vector Vector of tokens to process + * \param ctx Pointer to vector iteration context + * \param normal Pointer to location to receive "is normal" flag + * \param length Pointer to location to receive the length + * \param unit Pointer to location to receive the length's unit + * \return CSS_OK on success, CSS_INVALID if the input is not a gap component + * + * Post condition: \a *ctx is updated with the next token to process + * If the input is invalid, then \a *ctx remains unchanged. + */ +static css_error parse_gap_component(css_language *c, + const parserutils_vector *vector, int32_t *ctx, + bool *normal, css_fixed *length, uint32_t *unit) +{ + int32_t orig_ctx = *ctx; + const css_token *token; + css_error error; + bool match; + + token = parserutils_vector_peek(vector, *ctx); + if (token == NULL) + return CSS_INVALID; + + if ((token->type == CSS_TOKEN_IDENT) && + (lwc_string_caseless_isequal(token->idata, + c->strings[NORMAL], &match) == lwc_error_ok && + match)) { + parserutils_vector_iterate(vector, ctx); + *normal = true; + return CSS_OK; + } + + error = css__parse_unit_specifier(c, vector, ctx, UNIT_PX, length, unit); + if (error != CSS_OK) { + *ctx = orig_ctx; + return error; + } + + if (*length < 0 || (*unit & UNIT_MASK_COLUMN_GAP) == 0) { + *ctx = orig_ctx; + return CSS_INVALID; + } + + *normal = false; + + return CSS_OK; +} + +/** + * Parse gap shorthand + * + * gap: ? — one value sets both axes. + * + * LibCSS has no row-gap property (column-gap comes from multicol), so only + * the column component reaches the cascade. The row component is still + * parsed and consumed, or the whole declaration would be rejected as + * invalid and the column gap lost with it. + * + * \param c Parsing context + * \param vector Vector of tokens to process + * \param ctx Pointer to vector iteration context + * \param result Pointer to location to receive resulting style + * \return CSS_OK on success, + * CSS_NOMEM on memory exhaustion, + * CSS_INVALID if the input is not valid + * + * Post condition: \a *ctx is updated with the next token to process + * If the input is invalid, then \a *ctx remains unchanged. + */ +css_error css__parse_gap(css_language *c, + const parserutils_vector *vector, int32_t *ctx, + css_style *result) +{ + int32_t orig_ctx = *ctx; + css_error error; + const css_token *token; + enum flag_value flag_value; + css_fixed length = 0; + uint32_t unit = 0; + bool normal = false; + + token = parserutils_vector_peek(vector, *ctx); + if (token == NULL) { + return CSS_INVALID; + } + + flag_value = get_css_flag_value(c, token); + + if (flag_value != FLAG_VALUE__NONE) { + parserutils_vector_iterate(vector, ctx); + + error = css_stylesheet_style_flag_value(result, flag_value, + CSS_PROP_COLUMN_GAP); + if (error != CSS_OK) + *ctx = orig_ctx; + + return error; + } + + /* Row component — parsed only to consume it. */ + error = parse_gap_component(c, vector, ctx, &normal, &length, &unit); + if (error != CSS_OK) { + *ctx = orig_ctx; + return error; + } + + consumeWhitespace(vector, ctx); + + /* Column component, if any — it overrides the row one. */ + token = parserutils_vector_peek(vector, *ctx); + if (token != NULL) { + css_fixed col_length = 0; + uint32_t col_unit = 0; + bool col_normal = false; + + if (parse_gap_component(c, vector, ctx, &col_normal, + &col_length, &col_unit) == CSS_OK) { + length = col_length; + unit = col_unit; + normal = col_normal; + } + } + + if (normal) { + error = css__stylesheet_style_appendOPV(result, + CSS_PROP_COLUMN_GAP, 0, COLUMN_GAP_NORMAL); + if (error != CSS_OK) + *ctx = orig_ctx; + + return error; + } + + error = css__stylesheet_style_appendOPV(result, CSS_PROP_COLUMN_GAP, + 0, COLUMN_GAP_SET); + if (error != CSS_OK) { + *ctx = orig_ctx; + return error; + } + + error = css__stylesheet_style_vappend(result, 2, length, unit); + if (error != CSS_OK) + *ctx = orig_ctx; + + return error; +} diff --git a/src/parse/properties/properties.c b/src/parse/properties/properties.c index 2cc849c..dcdf12f 100644 --- a/src/parse/properties/properties.c +++ b/src/parse/properties/properties.c @@ -89,6 +89,7 @@ const css_prop_handler property_handlers[LAST_PROP + 1 - FIRST_PROP] = css__parse_font_style, css__parse_font_variant, css__parse_font_weight, + css__parse_gap, css__parse_height, css__parse_justify_content, css__parse_left, diff --git a/src/parse/properties/properties.h b/src/parse/properties/properties.h index 17b7f41..1e27da5 100644 --- a/src/parse/properties/properties.h +++ b/src/parse/properties/properties.h @@ -253,6 +253,9 @@ css_error css__parse_font_variant(css_language *c, css_error css__parse_font_weight(css_language *c, const parserutils_vector *vector, int32_t *ctx, css_style *result); +css_error css__parse_gap(css_language *c, + const parserutils_vector *vector, int32_t *ctx, + css_style *result); css_error css__parse_height(css_language *c, const parserutils_vector *vector, int32_t *ctx, css_style *result); diff --git a/src/parse/propstrings.c b/src/parse/propstrings.c index 2935f4e..8c9a86b 100644 --- a/src/parse/propstrings.c +++ b/src/parse/propstrings.c @@ -163,6 +163,7 @@ const stringmap_entry stringmap[LAST_KNOWN] = { SMAP("font-style"), SMAP("font-variant"), SMAP("font-weight"), + SMAP("gap"), SMAP("height"), SMAP("justify-content"), SMAP("left"), diff --git a/src/parse/propstrings.h b/src/parse/propstrings.h index ade2af2..cee3ea4 100644 --- a/src/parse/propstrings.h +++ b/src/parse/propstrings.h @@ -54,7 +54,7 @@ enum { CURSOR, DIRECTION, DISPLAY, ELEVATION, EMPTY_CELLS, FILL_OPACITY, FLEX, FLEX_BASIS, FLEX_DIRECTION, FLEX_FLOW, FLEX_GROW, FLEX_SHRINK, FLEX_WRAP, LIBCSS_FLOAT, FONT, FONT_FAMILY, FONT_SIZE, FONT_STYLE, - FONT_VARIANT, FONT_WEIGHT, HEIGHT, JUSTIFY_CONTENT, LEFT, + FONT_VARIANT, FONT_WEIGHT, GAP, HEIGHT, JUSTIFY_CONTENT, LEFT, LETTER_SPACING, LINE_HEIGHT, LIST_STYLE, LIST_STYLE_IMAGE, LIST_STYLE_POSITION, LIST_STYLE_TYPE, MARGIN, MARGIN_BOTTOM, MARGIN_LEFT, MARGIN_RIGHT, MARGIN_TOP, MAX_HEIGHT, MAX_WIDTH, diff --git a/test/data/parse2/multicol.dat b/test/data/parse2/multicol.dat index b0bbfb6..1ec4396 100644 --- a/test/data/parse2/multicol.dat +++ b/test/data/parse2/multicol.dat @@ -1301,3 +1301,108 @@ #reset +#data +* { gap: 2em; } +#errors +#expected +| * +| column-gap: 2em +#reset + +#data +* { gap: 1em 2em; } +#errors +#expected +| * +| column-gap: 2em +#reset + +#data +* { gap: normal; } +#errors +#expected +| * +| column-gap: normal +#reset + +#data +* { gap: normal 2em; } +#errors +#expected +| * +| column-gap: 2em +#reset + +#data +* { gap: 1em normal; } +#errors +#expected +| * +| column-gap: normal +#reset + +#data +* { gap: 0; } +#errors +#expected +| * +| column-gap: 0px +#reset + +#data +* { gap: 1em 2em 3em; } +#errors +#expected +| * +#reset + +#data +* { gap: -1em; } +#errors +#expected +| * +#reset + +#data +* { gap: 2; } +#errors +#expected +| * +#reset + +#data +* { gap: 10%; } +#errors +#expected +| * +#reset + +#data +* { gap: auto; } +#errors +#expected +| * +#reset + +#data +* { gap: ; } +#errors +#expected +| * +#reset + +#data +* { gap: inherit; } +#errors +#expected +| * +| column-gap: inherit +#reset + +#data +* { gap: inherit !important; } +#errors +#expected +| * +| column-gap: inherit !important +#reset