From 1e14b42454d76a69c3572a0602ed952b00702fcd Mon Sep 17 00:00:00 2001 From: Michael Drake Date: Sun, 29 Jun 2025 18:13:39 +0100 Subject: [PATCH 1/2] parse: properties: utils: Split out RGB and HSL parsing --- src/parse/properties/utils.c | 432 +++++++++++++++++++---------------- 1 file changed, 241 insertions(+), 191 deletions(-) diff --git a/src/parse/properties/utils.c b/src/parse/properties/utils.c index d479c73..d9558f6 100644 --- a/src/parse/properties/utils.c +++ b/src/parse/properties/utils.c @@ -351,6 +351,244 @@ static void HSL_to_RGB(css_fixed hue, css_fixed sat, css_fixed lit, uint8_t *r, #undef ORGB } +/** + * Parse a RGB(A) colour specifier + * + * It's up to the caller to reset the ctx if this fails. + * + * \param vector Vector of tokens to process + * \param ctx Pointer to vector iteration context + * \param colour_channels Number of colour channels to expect + * \param result Pointer to location to receive result (AARRGGBB) + * \return true on success, false on error. + */ +static bool parse_rgb( + const parserutils_vector *vector, + int32_t *ctx, + int colour_channels, + uint32_t *result) +{ + const css_token *token; + css_token_type valid = CSS_TOKEN_NUMBER; + uint8_t r = 0, g = 0, b = 0, a = 0xff; + uint8_t *components[4] = { &r, &g, &b, &a }; + + for (int i = 0; i < colour_channels; i++) { + uint8_t *component; + css_fixed num; + size_t consumed = 0; + int32_t intval; + bool int_only; + + component = components[i]; + + consumeWhitespace(vector, ctx); + + token = parserutils_vector_peek(vector, *ctx); + if (token == NULL || (token->type != + CSS_TOKEN_NUMBER && + token->type != + CSS_TOKEN_PERCENTAGE)) + return false; + + if (i == 0) + valid = token->type; + else if (i < 3 && token->type != valid) + return false; + + /* The alpha channel may be a float */ + if (i < 3) + int_only = (valid == CSS_TOKEN_NUMBER); + else + int_only = false; + + num = css__number_from_lwc_string(token->idata, + int_only, &consumed); + if (consumed != lwc_string_length(token->idata)) + return false; + + if (valid == CSS_TOKEN_NUMBER) { + if (i == 3) { + /* alpha channel */ + intval = FIXTOINT( + FMUL(num, F_255)); + } else { + /* colour channels */ + intval = FIXTOINT(num); + } + } else { + intval = FIXTOINT( + FDIV(FMUL(num, F_255), F_100)); + } + + if (intval > 255) + *component = 255; + else if (intval < 0) + *component = 0; + else + *component = intval; + + parserutils_vector_iterate(vector, ctx); + + consumeWhitespace(vector, ctx); + + token = parserutils_vector_peek(vector, *ctx); + if (token == NULL) + return false; + + if (i != (colour_channels - 1) && + tokenIsChar(token, ',')) { + parserutils_vector_iterate(vector, ctx); + } else if (i == (colour_channels - 1) && + tokenIsChar(token, ')')) { + parserutils_vector_iterate(vector, ctx); + } else { + return false; + } + } + + *result = ((unsigned)a << 24) | (r << 16) | (g << 8) | b; + + return true; +} + +/** + * Parse a HSL(A) colour specifier (hue, saturation, lightness) + * + * It's up to the caller to reset the ctx if this fails. + * + * \param vector Vector of tokens to process + * \param ctx Pointer to vector iteration context + * \param colour_channels Number of colour channels to expect + * \param result Pointer to location to receive result (AARRGGBB) + * \return true on success, false on error. + */ +static bool parse_hsl( + const parserutils_vector *vector, + int32_t *ctx, + int colour_channels, + uint32_t *result) +{ + const css_token *token; + size_t consumed = 0; + css_fixed hue, sat, lit; + int32_t alpha = 255; + uint8_t r = 0, g = 0, b = 0, a = 0xff; + + /* hue is a number without a unit representing an + * angle (0-360) degrees + */ + consumeWhitespace(vector, ctx); + + token = parserutils_vector_iterate(vector, ctx); + if ((token == NULL) || (token->type != CSS_TOKEN_NUMBER)) + return false; + + hue = css__number_from_lwc_string(token->idata, false, &consumed); + if (consumed != lwc_string_length(token->idata)) + return false; /* failed to consume the whole string as a number */ + + /* Normalise hue to the range [0, 360) */ + while (hue < 0) + hue += F_360; + while (hue >= F_360) + hue -= F_360; + + consumeWhitespace(vector, ctx); + + token = parserutils_vector_iterate(vector, ctx); + if (!tokenIsChar(token, ',')) + return false; + + + /* saturation */ + consumeWhitespace(vector, ctx); + + token = parserutils_vector_iterate(vector, ctx); + if ((token == NULL) || (token->type != CSS_TOKEN_PERCENTAGE)) + return false; + + sat = css__number_from_lwc_string(token->idata, false, &consumed); + if (consumed != lwc_string_length(token->idata)) + return false; /* failed to consume the whole string as a number */ + + /* Normalise saturation to the range [0, 100] */ + if (sat < INTTOFIX(0)) + sat = INTTOFIX(0); + else if (sat > INTTOFIX(100)) + sat = INTTOFIX(100); + + consumeWhitespace(vector, ctx); + + token = parserutils_vector_iterate(vector, ctx); + if (!tokenIsChar(token, ',')) + return false; + + + /* lightness */ + consumeWhitespace(vector, ctx); + + token = parserutils_vector_iterate(vector, ctx); + if ((token == NULL) || (token->type != CSS_TOKEN_PERCENTAGE)) + return false; + + lit = css__number_from_lwc_string(token->idata, false, &consumed); + if (consumed != lwc_string_length(token->idata)) + return false; /* failed to consume the whole string as a number */ + + /* Normalise lightness to the range [0, 100] */ + if (lit < INTTOFIX(0)) + lit = INTTOFIX(0); + else if (lit > INTTOFIX(100)) + lit = INTTOFIX(100); + + consumeWhitespace(vector, ctx); + + token = parserutils_vector_iterate(vector, ctx); + + if (colour_channels == 6) { + /* alpha */ + + if (!tokenIsChar(token, ',')) + return false; + + consumeWhitespace(vector, ctx); + + token = parserutils_vector_iterate(vector, ctx); + if ((token == NULL) || (token->type != CSS_TOKEN_NUMBER)) + return false; + + alpha = css__number_from_lwc_string(token->idata, false, &consumed); + if (consumed != lwc_string_length(token->idata)) + return false; /* failed to consume the whole string as a number */ + + alpha = FIXTOINT(FMUL(alpha, F_255)); + + consumeWhitespace(vector, ctx); + + token = parserutils_vector_iterate(vector, ctx); + + } + + if (!tokenIsChar(token, ')')) + return false; + + /* have a valid HSV entry, convert to RGB */ + HSL_to_RGB(hue, sat, lit, &r, &g, &b); + + /* apply alpha */ + if (alpha > 255) { + a = 255; + } else if (alpha < 0) { + a = 0; + } else { + a = alpha; + } + + *result = ((unsigned)a << 24) | (r << 16) | (g << 8) | b; + return true; +} + /** * Parse a colour specifier * @@ -440,7 +678,6 @@ css_error css__parse_colour_specifier(css_language *c, else goto invalid; } else if (token->type == CSS_TOKEN_FUNCTION) { - uint8_t r = 0, g = 0, b = 0, a = 0xff; int colour_channels = 0; if ((lwc_string_caseless_isequal( @@ -462,203 +699,16 @@ css_error css__parse_colour_specifier(css_language *c, } if (colour_channels == 3 || colour_channels == 4) { - int i; - css_token_type valid = CSS_TOKEN_NUMBER; - uint8_t *components[4] = { &r, &g, &b, &a }; - - for (i = 0; i < colour_channels; i++) { - uint8_t *component; - css_fixed num; - size_t consumed = 0; - int32_t intval; - bool int_only; - - component = components[i]; - - consumeWhitespace(vector, ctx); - - token = parserutils_vector_peek(vector, *ctx); - if (token == NULL || (token->type != - CSS_TOKEN_NUMBER && - token->type != - CSS_TOKEN_PERCENTAGE)) - goto invalid; - - if (i == 0) - valid = token->type; - else if (i < 3 && token->type != valid) - goto invalid; - - /* The alpha channel may be a float */ - if (i < 3) - int_only = (valid == CSS_TOKEN_NUMBER); - else - int_only = false; - - num = css__number_from_lwc_string(token->idata, - int_only, &consumed); - if (consumed != lwc_string_length(token->idata)) - goto invalid; - - if (valid == CSS_TOKEN_NUMBER) { - if (i == 3) { - /* alpha channel */ - intval = FIXTOINT( - FMUL(num, F_255)); - } else { - /* colour channels */ - intval = FIXTOINT(num); - } - } else { - intval = FIXTOINT( - FDIV(FMUL(num, F_255), F_100)); - } - - if (intval > 255) - *component = 255; - else if (intval < 0) - *component = 0; - else - *component = intval; - - parserutils_vector_iterate(vector, ctx); - - consumeWhitespace(vector, ctx); - - token = parserutils_vector_peek(vector, *ctx); - if (token == NULL) - goto invalid; - - if (i != (colour_channels - 1) && - tokenIsChar(token, ',')) { - parserutils_vector_iterate(vector, ctx); - } else if (i == (colour_channels - 1) && - tokenIsChar(token, ')')) { - parserutils_vector_iterate(vector, ctx); - } else { - goto invalid; - } + if (!parse_rgb(vector, ctx, colour_channels, result)) { + goto invalid; } } else if (colour_channels == 5 || colour_channels == 6) { - /* hue - saturation - lightness */ - size_t consumed = 0; - css_fixed hue, sat, lit; - int32_t alpha = 255; - - /* hue is a number without a unit representing an - * angle (0-360) degrees - */ - consumeWhitespace(vector, ctx); - - token = parserutils_vector_iterate(vector, ctx); - if ((token == NULL) || (token->type != CSS_TOKEN_NUMBER)) + if (!parse_hsl(vector, ctx, colour_channels, result)) { goto invalid; - - hue = css__number_from_lwc_string(token->idata, false, &consumed); - if (consumed != lwc_string_length(token->idata)) - goto invalid; /* failed to consume the whole string as a number */ - - /* Normalise hue to the range [0, 360) */ - while (hue < 0) - hue += F_360; - while (hue >= F_360) - hue -= F_360; - - consumeWhitespace(vector, ctx); - - token = parserutils_vector_iterate(vector, ctx); - if (!tokenIsChar(token, ',')) - goto invalid; - - - /* saturation */ - consumeWhitespace(vector, ctx); - - token = parserutils_vector_iterate(vector, ctx); - if ((token == NULL) || (token->type != CSS_TOKEN_PERCENTAGE)) - goto invalid; - - sat = css__number_from_lwc_string(token->idata, false, &consumed); - if (consumed != lwc_string_length(token->idata)) - goto invalid; /* failed to consume the whole string as a number */ - - /* Normalise saturation to the range [0, 100] */ - if (sat < INTTOFIX(0)) - sat = INTTOFIX(0); - else if (sat > INTTOFIX(100)) - sat = INTTOFIX(100); - - consumeWhitespace(vector, ctx); - - token = parserutils_vector_iterate(vector, ctx); - if (!tokenIsChar(token, ',')) - goto invalid; - - - /* lightness */ - consumeWhitespace(vector, ctx); - - token = parserutils_vector_iterate(vector, ctx); - if ((token == NULL) || (token->type != CSS_TOKEN_PERCENTAGE)) - goto invalid; - - lit = css__number_from_lwc_string(token->idata, false, &consumed); - if (consumed != lwc_string_length(token->idata)) - goto invalid; /* failed to consume the whole string as a number */ - - /* Normalise lightness to the range [0, 100] */ - if (lit < INTTOFIX(0)) - lit = INTTOFIX(0); - else if (lit > INTTOFIX(100)) - lit = INTTOFIX(100); - - consumeWhitespace(vector, ctx); - - token = parserutils_vector_iterate(vector, ctx); - - if (colour_channels == 6) { - /* alpha */ - - if (!tokenIsChar(token, ',')) - goto invalid; - - consumeWhitespace(vector, ctx); - - token = parserutils_vector_iterate(vector, ctx); - if ((token == NULL) || (token->type != CSS_TOKEN_NUMBER)) - goto invalid; - - alpha = css__number_from_lwc_string(token->idata, false, &consumed); - if (consumed != lwc_string_length(token->idata)) - goto invalid; /* failed to consume the whole string as a number */ - - alpha = FIXTOINT(FMUL(alpha, F_255)); - - consumeWhitespace(vector, ctx); - - token = parserutils_vector_iterate(vector, ctx); - } - - if (!tokenIsChar(token, ')')) - goto invalid; - - /* have a valid HSV entry, convert to RGB */ - HSL_to_RGB(hue, sat, lit, &r, &g, &b); - - /* apply alpha */ - if (alpha > 255) - a = 255; - else if (alpha < 0) - a = 0; - else - a = alpha; - } else { goto invalid; } - - *result = ((unsigned)a << 24) | (r << 16) | (g << 8) | b; } *value = COLOR_SET; From c16ad84aa1917b0ebefcd4f613bfb9264b6003dd Mon Sep 17 00:00:00 2001 From: Michael Drake Date: Mon, 7 Jul 2025 19:19:03 +0100 Subject: [PATCH 2/2] parse: properties: utils: Update rgb(a) handling to css-color-4 These changes * make rgb and rgba interchangable * add support for the "modern" style --- src/parse/properties/utils.c | 116 ++++++++++++++++++++++------------- test/data/parse/colours.dat | 91 +++++++++++++++++++++++++++ 2 files changed, 163 insertions(+), 44 deletions(-) diff --git a/src/parse/properties/utils.c b/src/parse/properties/utils.c index d9558f6..687e8d9 100644 --- a/src/parse/properties/utils.c +++ b/src/parse/properties/utils.c @@ -356,94 +356,122 @@ static void HSL_to_RGB(css_fixed hue, css_fixed sat, css_fixed lit, uint8_t *r, * * It's up to the caller to reset the ctx if this fails. * + * \param c Parsing context * \param vector Vector of tokens to process * \param ctx Pointer to vector iteration context - * \param colour_channels Number of colour channels to expect * \param result Pointer to location to receive result (AARRGGBB) * \return true on success, false on error. */ static bool parse_rgb( + css_language *c, const parserutils_vector *vector, int32_t *ctx, - int colour_channels, uint32_t *result) { const css_token *token; css_token_type valid = CSS_TOKEN_NUMBER; uint8_t r = 0, g = 0, b = 0, a = 0xff; uint8_t *components[4] = { &r, &g, &b, &a }; + bool legacy = false; + bool had_none = false; - for (int i = 0; i < colour_channels; i++) { + for (int i = 0; i < 4; i++) { uint8_t *component; css_fixed num; size_t consumed = 0; int32_t intval; bool int_only; + bool match; component = components[i]; consumeWhitespace(vector, ctx); token = parserutils_vector_peek(vector, *ctx); - if (token == NULL || (token->type != - CSS_TOKEN_NUMBER && - token->type != - CSS_TOKEN_PERCENTAGE)) + if (token == NULL) { return false; + } else if (!legacy && token->type == CSS_TOKEN_IDENT && + lwc_string_caseless_isequal( + token->idata, c->strings[NONE], + &match) == lwc_error_ok && match) { + had_none = true; + } else { + if (token->type != CSS_TOKEN_NUMBER && + token->type != CSS_TOKEN_PERCENTAGE) { + return false; + } - if (i == 0) - valid = token->type; - else if (i < 3 && token->type != valid) - return false; + if (i == 0) { + valid = token->type; + } else if (legacy && i < 3 && token->type != valid) { + return false; + } else { + valid = token->type; + } - /* The alpha channel may be a float */ - if (i < 3) - int_only = (valid == CSS_TOKEN_NUMBER); - else - int_only = false; + /* The alpha channel may be a float */ + if (i < 3) { + int_only = (valid == CSS_TOKEN_NUMBER); + } else { + int_only = false; + } - num = css__number_from_lwc_string(token->idata, - int_only, &consumed); - if (consumed != lwc_string_length(token->idata)) - return false; + num = css__number_from_lwc_string(token->idata, + int_only, &consumed); + if (consumed != lwc_string_length(token->idata)) { + return false; + } - if (valid == CSS_TOKEN_NUMBER) { - if (i == 3) { - /* alpha channel */ + if (valid == CSS_TOKEN_NUMBER) { + if (i == 3) { + /* alpha channel */ + intval = FIXTOINT(FMUL(num, F_255)); + } else { + /* colour channels */ + intval = FIXTOINT(num); + } + } else { intval = FIXTOINT( - FMUL(num, F_255)); + FDIV(FMUL(num, F_255), F_100)); + } + + if (intval > 255) { + *component = 255; + } else if (intval < 0) { + *component = 0; } else { - /* colour channels */ - intval = FIXTOINT(num); + *component = intval; } - } else { - intval = FIXTOINT( - FDIV(FMUL(num, F_255), F_100)); } - if (intval > 255) - *component = 255; - else if (intval < 0) - *component = 0; - else - *component = intval; - parserutils_vector_iterate(vector, ctx); consumeWhitespace(vector, ctx); token = parserutils_vector_peek(vector, *ctx); - if (token == NULL) + if (token == NULL) { return false; + } + + if (i == 0 && tokenIsChar(token, ',') && !had_none) { + legacy = true; + } - if (i != (colour_channels - 1) && - tokenIsChar(token, ',')) { + if (i >= 2 && tokenIsChar(token, ')')) { parserutils_vector_iterate(vector, ctx); - } else if (i == (colour_channels - 1) && - tokenIsChar(token, ')')) { + break; + + } else if (legacy) { + if (!tokenIsChar(token, ',')) { + return false; + } + parserutils_vector_iterate(vector, ctx); + + } else if (i == 2) { + if (!tokenIsChar(token, '/')) { + return false; + } parserutils_vector_iterate(vector, ctx); - } else { - return false; } } @@ -699,7 +727,7 @@ css_error css__parse_colour_specifier(css_language *c, } if (colour_channels == 3 || colour_channels == 4) { - if (!parse_rgb(vector, ctx, colour_channels, result)) { + if (!parse_rgb(c, vector, ctx, result)) { goto invalid; } } else if (colour_channels == 5 || colour_channels == 6) { diff --git a/test/data/parse/colours.dat b/test/data/parse/colours.dat index edcde8a..312845e 100644 --- a/test/data/parse/colours.dat +++ b/test/data/parse/colours.dat @@ -32,6 +32,97 @@ | 0x02000018 0xffff0000 #reset +#data +* { color: rgb(255 0 0) } +#errors +#expected +| 1 * +| 0x02000018 0xffff0000 +#reset + +#data +* { color: rgb(255 none none) } +#errors +#expected +| 1 * +| 0x02000018 0xffff0000 +#reset + +#data +* { color: rgb(255 0% none) } +#errors +#expected +| 1 * +| 0x02000018 0xffff0000 +#reset + +#data +* { color: rgb(none 0% 255) } +#errors +#expected +| 1 * +| 0x02000018 0xff0000ff +#reset + +#data +* { color: rgb(none 0% 255 / none) } +#errors +#expected +| 1 * +| 0x02000018 0xff0000ff +#reset + +#data +* { color: rgb(none 0% 255 / 0.5) } +#errors +#expected +| 1 * +| 0x02000018 0x7f0000ff +#reset + +#data +* { color: rgb(none 0% 255 none) } +#errors +#expected +| 1 * +#reset + +#data +* { color: rgba(255, 0, 0) } +#errors +#expected +| 1 * +| 0x02000018 0xffff0000 +#reset + +#data +* { color: rgba(none, 0, 0) } +#errors +#expected +| 1 * +#reset + +#data +* { color: rgba(0, none, 0) } +#errors +#expected +| 1 * +#reset + +#data +* { color: rgb(255, 0%, 0) } +#errors +#expected +| 1 * +#reset + +#data +* { color: rgba(255, 0%, 0) } +#errors +#expected +| 1 * +#reset + #data * { color: rgb(100%, 0%, 0%) } #errors