From 0524c406408d3eedbaf22e3981edafdf78fdfd5e Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sun, 2 Jun 2024 11:13:46 -0400 Subject: [PATCH] css: Support relative colors Parse the various color(from ...) syntaxes, and implement them. Add a new 'relative color' subtype for color values, and a new 'color coord' subtype for number values. Use these for relative colors where the original color can't be resolved at parse time. --- gtk/gtkcsscolorvalue.c | 232 ++++++++++++++++++++++++++++++--- gtk/gtkcssnumbervalue.c | 92 ++++++++++++- gtk/gtkcssnumbervalueprivate.h | 9 ++ 3 files changed, 311 insertions(+), 22 deletions(-) diff --git a/gtk/gtkcsscolorvalue.c b/gtk/gtkcsscolorvalue.c index 5571a65d8d..3298118a28 100644 --- a/gtk/gtkcsscolorvalue.c +++ b/gtk/gtkcsscolorvalue.c @@ -39,6 +39,7 @@ static GtkCssValue * gtk_css_color_value_new_color_from_rgba (const GdkRGBA *rgb typedef enum { COLOR_TYPE_COLOR, + COLOR_TYPE_RELATIVE, COLOR_TYPE_NAME, COLOR_TYPE_COLOR_MIX, COLOR_TYPE_SHADE, @@ -81,6 +82,14 @@ struct _GtkCssValue GtkCssValue *color2; double factor; } mix; + + struct + { + GtkCssValue *origin; + GtkCssColorSpace color_space; + gboolean legacy_srgb; + GtkCssValue *values[1]; + } relative; }; }; @@ -113,6 +122,15 @@ gtk_css_value_color_free (GtkCssValue *color) gtk_css_value_unref (color->mix.color2); break; + case COLOR_TYPE_RELATIVE: + for (guint i = 0; i < 4; i++) + { + if (color->relative.values[i]) + gtk_css_value_unref (color->relative.values[i]); + } + + break; + case COLOR_TYPE_COLOR: case COLOR_TYPE_CURRENT_COLOR: default: @@ -210,6 +228,14 @@ gtk_css_value_color_equal (const GtkCssValue *value1, case COLOR_TYPE_COLOR: return gtk_css_color_equal (&value1->color, &value2->color); + case COLOR_TYPE_RELATIVE: + return value1->relative.color_space == value2->relative.color_space && + value1->relative.legacy_srgb == value2->relative.legacy_srgb && + gtk_css_value_equal0 (value1->relative.values[0], value2->relative.values[0]) && + gtk_css_value_equal0 (value1->relative.values[1], value2->relative.values[1]) && + gtk_css_value_equal0 (value1->relative.values[2], value2->relative.values[2]) && + gtk_css_value_equal0 (value1->relative.values[3], value2->relative.values[3]); + case COLOR_TYPE_NAME: return g_str_equal (value1->name, value2->name); @@ -314,6 +340,71 @@ gtk_css_value_color_print (const GtkCssValue *value, gtk_css_color_print (&value->color, value->serialize_as_rgb, string); break; + case COLOR_TYPE_RELATIVE: + { + switch (value->relative.color_space) + { + case GTK_CSS_COLOR_SPACE_SRGB: + g_string_append (string, "color(from "); + gtk_css_value_print (value->relative.origin, string); + g_string_append (string, " srgb"); + break; + + case GTK_CSS_COLOR_SPACE_SRGB_LINEAR: + g_string_append (string, "color(from "); + gtk_css_value_print (value->relative.origin, string); + g_string_append (string, " srgb-linear"); + break; + + case GTK_CSS_COLOR_SPACE_HSL: + g_string_append (string, "hsl(from "); + gtk_css_value_print (value->relative.origin, string); + break; + + case GTK_CSS_COLOR_SPACE_HWB: + g_string_append (string, "hwb(from "); + gtk_css_value_print (value->relative.origin, string); + break; + + case GTK_CSS_COLOR_SPACE_OKLAB: + g_string_append (string, "oklab(from "); + gtk_css_value_print (value->relative.origin, string); + break; + + case GTK_CSS_COLOR_SPACE_OKLCH: + g_string_append (string, "oklch(from "); + gtk_css_value_print (value->relative.origin, string); + break; + + default: + g_assert_not_reached (); + } + + for (guint i = 0; i < 3; i++) + { + g_string_append_c (string, ' '); + + if (value->relative.values[i]) + gtk_css_value_print (value->relative.values[i], string); + else + g_string_append (string, "none"); + } + + if (value->relative.values[3] == NULL || + !gtk_css_value_is_computed (value->relative.values[3]) || + gtk_css_number_value_get_canonical (value->relative.values[3], 1) < 0.999) + { + g_string_append (string, " / "); + if (value->relative.values[3]) + gtk_css_value_print (value->relative.values[3], string); + else + g_string_append (string, "none"); + } + + g_string_append_c (string, ')'); + } + break; + case COLOR_TYPE_NAME: g_string_append (string, "@"); g_string_append (string, value->name); @@ -440,7 +531,6 @@ apply_shade (const GdkRGBA *in, _gdk_hsla_init_from_rgba (&hsla, in); _gdk_hsla_shade (&hsla, &hsla, factor); - _gdk_rgba_init_from_hsla (out, &hsla); } @@ -520,6 +610,44 @@ gtk_css_color_value_do_resolve (GtkCssValue *color, value = gtk_css_value_ref (color); break; + case COLOR_TYPE_RELATIVE: + { + float v[4]; + gboolean m[4]; + + for (guint i = 0; i < 4; i++) + { + if (color->relative.values[i]) + { + GtkCssValue *val; + + m[i] = FALSE; + val = gtk_css_value_compute (color->relative.values[i], property_id, context); + v[i] = gtk_css_number_value_get_canonical (val, 1); + + gtk_css_value_unref (val); + } + else + { + m[i] = TRUE; + v[i] = 0; + } + } + + if (color->relative.color_space == GTK_CSS_COLOR_SPACE_SRGB && + color->relative.legacy_srgb) + { + v[0] /= 255.; + v[1] /= 255.; + v[2] /= 255.; + } + + v[3] = CLAMP (v[3], 0, 1); /* clamp alpha */ + + value = gtk_css_color_value_new_color (color->relative.color_space, FALSE, v, m); + } + break; + case COLOR_TYPE_NAME: { GtkCssValue *named; @@ -716,6 +844,32 @@ gtk_css_color_value_new_color_from_rgba (const GdkRGBA *rgba) return value; } +static GtkCssValue * +gtk_css_color_value_new_relative (GtkCssValue *origin, + GtkCssColorSpace color_space, + gboolean legacy_srgb, + GtkCssValue *values[4]) +{ + GtkCssValue *value; + + value = gtk_css_value_alloc (>K_CSS_VALUE_COLOR, + sizeof (GtkCssValue) + sizeof (GtkCssValue *) * 3); + value->relative.color_space = color_space; + value->is_computed = FALSE; + value->type = COLOR_TYPE_RELATIVE; + + value->relative.origin = gtk_css_value_ref (origin); + value->relative.legacy_srgb = legacy_srgb; + + for (guint i = 0; i < 4; i++) + { + if (values[i]) + value->relative.values[i] = gtk_css_value_ref (values[i]); + } + + return value; +} + GtkCssValue * gtk_css_color_value_new_name (const char *name) { @@ -1039,7 +1193,6 @@ typedef struct float v[4]; gboolean alpha_specified; gboolean use_percentages; - GtkCssColorSpace color_space; GtkCssNumberParseContext ctx; } ParseData; @@ -1083,6 +1236,7 @@ parse_rgb_channel_value (GtkCssParser *parser, else { data->values[idx] = gtk_css_number_value_parse_with_context (parser, GTK_CSS_PARSE_NUMBER | GTK_CSS_PARSE_PERCENT, &data->ctx); + if (data->values[idx] == NULL) return FALSE; @@ -1427,13 +1581,13 @@ parse_color_color_channel (GtkCssParser *parser, case 0: if (gtk_css_parser_try_ident (parser, "srgb")) { - data->color_space = GTK_CSS_COLOR_SPACE_SRGB; + data->ctx.color_space = GTK_CSS_COLOR_SPACE_SRGB; return 1; } if (gtk_css_parser_try_ident (parser, "srgb-linear")) { - data->color_space = GTK_CSS_COLOR_SPACE_SRGB_LINEAR; + data->ctx.color_space = GTK_CSS_COLOR_SPACE_SRGB_LINEAR; return 1; } @@ -1454,12 +1608,12 @@ parse_color_color_channel (GtkCssParser *parser, } static gboolean -parse_color_function (GtkCssParser *self, - gboolean parse_color_space, - gboolean allow_alpha, - gboolean require_alpha, +parse_color_function (GtkCssParser *self, + gboolean parse_color_space, + gboolean allow_alpha, + gboolean require_alpha, guint (* parse_func) (GtkCssParser *, ParseData *, guint), - ParseData *data) + ParseData *data) { const GtkCssToken *token; gboolean result = FALSE; @@ -1480,6 +1634,12 @@ parse_color_function (GtkCssParser *self, g_strlcpy (function_name, gtk_css_token_get_string (token), 64); gtk_css_parser_start_block (self); + if (gtk_css_parser_try_ident (self, "from")) + { + data->ctx.color = gtk_css_color_value_parse (self); + data->syntax = COLOR_SYNTAX_MODERN; + } + arg = 0; while (TRUE) { @@ -1573,7 +1733,40 @@ gtk_css_color_value_new_from_parse_data (ParseData *data) if (!data->alpha_specified) data->v[3] = 1; - return gtk_css_color_value_new_color (data->color_space, + if (data->ctx.color) + { + if (!data->alpha_specified) + { + if (data->ctx.color->type == COLOR_TYPE_COLOR) + { + data->v[3] = gtk_css_color_value_get_coord (data->ctx.color, + data->ctx.color_space, + data->ctx.legacy_rgb_scale, + 3); + data->values[3] = gtk_css_number_value_new (data->v[3], GTK_CSS_NUMBER); + } + else + { + data->values[3] = gtk_css_number_value_new_color_component (data->ctx.color, + data->ctx.color_space, + data->ctx.legacy_rgb_scale, + 3); + } + } + + for (guint i = 0; i < 4; i++) + { + if (data->values[i] && !gtk_css_value_is_computed (data->values[i])) + { + return gtk_css_color_value_new_relative (data->ctx.color, + data->ctx.color_space, + data->ctx.legacy_rgb_scale, + data->values); + } + } + } + + return gtk_css_color_value_new_color (data->ctx.color_space, data->serialize_as_rgb, data->v, (gboolean[4]) { @@ -1587,6 +1780,9 @@ gtk_css_color_value_new_from_parse_data (ParseData *data) static void parse_data_clear (ParseData *data) { + if (data->ctx.color) + gtk_css_value_unref (data->ctx.color); + for (guint i = 0; i < 4; i++) { if (data->values[i]) @@ -1618,7 +1814,7 @@ gtk_css_color_value_parse (GtkCssParser *parser) else if (gtk_css_parser_has_function (parser, "rgb") || gtk_css_parser_has_function (parser, "rgba")) { gboolean has_alpha; - ParseData data = { COLOR_SYNTAX_DETECTING, TRUE, { NULL, }, { 0, }, FALSE, FALSE, GTK_CSS_COLOR_SPACE_SRGB, }; + ParseData data = { COLOR_SYNTAX_DETECTING, TRUE, { NULL, }, { 0, }, FALSE, FALSE, { NULL, GTK_CSS_COLOR_SPACE_SRGB, TRUE } }; has_alpha = gtk_css_parser_has_function (parser, "rgba"); @@ -1633,7 +1829,7 @@ gtk_css_color_value_parse (GtkCssParser *parser) } else if (gtk_css_parser_has_function (parser, "hsl") || gtk_css_parser_has_function (parser, "hsla")) { - ParseData data = { COLOR_SYNTAX_DETECTING, TRUE, { NULL, }, { 0, }, FALSE, FALSE, GTK_CSS_COLOR_SPACE_HSL, }; + ParseData data = { COLOR_SYNTAX_DETECTING, TRUE, { NULL, }, { 0, }, FALSE, FALSE, { NULL, GTK_CSS_COLOR_SPACE_HSL, FALSE } }; if (!parse_color_function (parser, FALSE, TRUE, FALSE, parse_hsla_color_channel, &data)) return NULL; @@ -1646,7 +1842,7 @@ gtk_css_color_value_parse (GtkCssParser *parser) } else if (gtk_css_parser_has_function (parser, "hwb")) { - ParseData data = { COLOR_SYNTAX_MODERN, TRUE, { NULL, }, { 0, }, FALSE, FALSE, GTK_CSS_COLOR_SPACE_HWB, }; + ParseData data = { COLOR_SYNTAX_MODERN, TRUE, { NULL, }, { 0, }, FALSE, FALSE, { NULL, GTK_CSS_COLOR_SPACE_HWB, FALSE } }; if (!parse_color_function (parser, FALSE, TRUE, FALSE, parse_hwb_color_channel, &data)) return NULL; @@ -1659,7 +1855,7 @@ gtk_css_color_value_parse (GtkCssParser *parser) } else if (gtk_css_parser_has_function (parser, "oklab")) { - ParseData data = { COLOR_SYNTAX_MODERN, FALSE, { NULL, }, { 0, }, FALSE, FALSE, GTK_CSS_COLOR_SPACE_OKLAB, }; + ParseData data = { COLOR_SYNTAX_MODERN, FALSE, { NULL, }, { 0, }, FALSE, FALSE, { NULL, GTK_CSS_COLOR_SPACE_OKLAB, FALSE } }; if (!parse_color_function (parser, FALSE, TRUE, FALSE, parse_oklab_color_channel, &data)) return NULL; @@ -1672,7 +1868,7 @@ gtk_css_color_value_parse (GtkCssParser *parser) } else if (gtk_css_parser_has_function (parser, "oklch")) { - ParseData data = { COLOR_SYNTAX_MODERN, FALSE, { NULL, }, { 0, }, FALSE, FALSE, GTK_CSS_COLOR_SPACE_OKLCH, }; + ParseData data = { COLOR_SYNTAX_MODERN, FALSE, { NULL, }, { 0, }, FALSE, FALSE, { NULL, GTK_CSS_COLOR_SPACE_OKLCH, FALSE } }; if (!parse_color_function (parser, FALSE, TRUE, FALSE, parse_oklch_color_channel, &data)) return NULL; @@ -1685,7 +1881,7 @@ gtk_css_color_value_parse (GtkCssParser *parser) } else if (gtk_css_parser_has_function (parser, "color")) { - ParseData data = { COLOR_SYNTAX_MODERN, FALSE, { NULL, }, { 0, }, FALSE, FALSE, 0, }; + ParseData data = { COLOR_SYNTAX_MODERN, FALSE, { NULL, }, { 0, }, FALSE, FALSE, { NULL, 0, FALSE } }; if (!parse_color_function (parser, TRUE, TRUE, FALSE, parse_color_color_channel, &data)) return NULL; @@ -1823,7 +2019,7 @@ gtk_css_color_value_get_color (const GtkCssValue *color) float gtk_css_color_value_get_coord (const GtkCssValue *color, GtkCssColorSpace color_space, - gboolean legacy_srgb, + gboolean legacy_rgb_scale, guint coord) { GtkCssColor origin; @@ -1846,7 +2042,7 @@ gtk_css_color_value_get_coord (const GtkCssValue *color, } /* Scale up r, g, b in legacy context */ - if (color_space == GTK_CSS_COLOR_SPACE_SRGB && legacy_srgb && coord < 3) + if (color_space == GTK_CSS_COLOR_SPACE_SRGB && legacy_rgb_scale && coord < 3) return origin.values[coord] * 255.; else return origin.values[coord]; diff --git a/gtk/gtkcssnumbervalue.c b/gtk/gtkcssnumbervalue.c index e4b6d37704..8fbb51b1c2 100644 --- a/gtk/gtkcssnumbervalue.c +++ b/gtk/gtkcssnumbervalue.c @@ -22,6 +22,7 @@ #include "gtkcsscalcvalueprivate.h" #include "gtkcssenumvalueprivate.h" #include "gtkcssdimensionvalueprivate.h" +#include "gtkcsscolorvalueprivate.h" #include "gtkcssstyleprivate.h" #include "gtkprivate.h" @@ -69,6 +70,7 @@ typedef enum { TYPE_EXP, TYPE_LOG, TYPE_HYPOT, + TYPE_COLOR_COORD, } NumberValueType; static const char *function_name[] = { @@ -110,6 +112,12 @@ struct _GtkCssValue { guint n_terms; GtkCssValue *terms[1]; } calc; + struct { + GtkCssValue *color; + GtkCssColorSpace color_space; + guint coord : 16; + guint legacy_rgb_scale : 1; + } color_coord; }; }; @@ -140,7 +148,11 @@ gtk_css_calc_value_new (guint type, static void gtk_css_value_number_free (GtkCssValue *number) { - if (number->type != TYPE_DIMENSION) + if (number->type == TYPE_COLOR_COORD) + { + gtk_css_value_unref (number->color_coord.color); + } + else if (number->type != TYPE_DIMENSION) { for (guint i = 0; i < number->calc.n_terms; i++) { @@ -310,7 +322,21 @@ gtk_css_value_number_compute (GtkCssValue *number, GtkCssStyle *style = context->style; GtkCssStyle *parent_style = context->parent_style; - if (number->type != TYPE_DIMENSION) + if (number->type == TYPE_COLOR_COORD) + { + GtkCssValue *color; + float v; + + color = gtk_css_value_compute (number->color_coord.color, property_id, context); + v = gtk_css_color_value_get_coord (color, + number->color_coord.color_space, + number->color_coord.legacy_rgb_scale, + number->color_coord.coord); + gtk_css_value_unref (color); + + return gtk_css_number_value_new (v, GTK_CSS_NUMBER); + } + else if (number->type != TYPE_DIMENSION) { const guint n_terms = number->calc.n_terms; GtkCssValue *result; @@ -520,6 +546,10 @@ gtk_css_value_number_print (const GtkCssValue *value, g_string_append_c (string, ')'); break; + case TYPE_COLOR_COORD: + g_string_append (string, gtk_css_color_space_get_coord_name (value->color_coord.color_space, value->color_coord.coord)); + break; + default: { const char *sep = value->type == TYPE_CALC ? " + " : (value->type == TYPE_PRODUCT ? " * " : ", "); @@ -871,6 +901,7 @@ gtk_css_number_value_get_dimension (const GtkCssValue *value) case TYPE_SQRT: case TYPE_POW: case TYPE_LOG: + case TYPE_COLOR_COORD: return GTK_CSS_DIMENSION_NUMBER; case TYPE_ASIN: @@ -887,7 +918,11 @@ gtk_css_number_value_get_dimension (const GtkCssValue *value) gboolean gtk_css_number_value_has_percent (const GtkCssValue *value) { - if (value->type == TYPE_DIMENSION) + if (value->type == TYPE_COLOR_COORD) + { + return FALSE; + } + else if (value->type == TYPE_DIMENSION) { return gtk_css_unit_get_dimension (value->dimension.unit) == GTK_CSS_DIMENSION_PERCENTAGE; } @@ -1326,6 +1361,7 @@ gtk_css_math_value_new (guint type, switch ((NumberValueType) type) { case TYPE_DIMENSION: + case TYPE_COLOR_COORD: g_assert_not_reached (); case TYPE_ROUND: @@ -1408,7 +1444,7 @@ GtkCssValue * gtk_css_number_value_parse (GtkCssParser *parser, GtkCssNumberParseFlags flags) { - GtkCssNumberParseContext ctx; + GtkCssNumberParseContext ctx = { NULL, 0, FALSE }; return gtk_css_number_value_parse_with_context (parser, flags, &ctx); } @@ -1489,6 +1525,18 @@ gtk_css_number_value_parse_with_context (GtkCssParser *parser, return gtk_css_number_value_new (constants[i].value, GTK_CSS_NUMBER); } } + + if (ctx->color) + { + for (guint i = 0; i < 4; i++) + { + if (g_ascii_strcasecmp (name, gtk_css_color_space_get_coord_name (ctx->color_space, i)) == 0) + { + gtk_css_parser_consume_token (parser); + return gtk_css_number_value_new_color_component (ctx->color, ctx->color_space, ctx->legacy_rgb_scale, i); + } + } + } } return gtk_css_dimension_value_parse (parser, flags); @@ -1720,6 +1768,12 @@ gtk_css_number_value_get (const GtkCssValue *value, return sqrt (acc); } + case TYPE_COLOR_COORD: + return gtk_css_color_value_get_coord (value->color_coord.color, + value->color_coord.color_space, + value->color_coord.legacy_rgb_scale, + value->color_coord.coord); + default: g_assert_not_reached (); } @@ -1831,3 +1885,33 @@ _sign (double a) else return 0; } + +GtkCssValue * +gtk_css_number_value_new_color_component (GtkCssValue *color, + GtkCssColorSpace color_space, + gboolean legacy_rgb_scale, + guint coord) +{ + if (gtk_css_value_is_computed (color)) + { + float v; + + v = gtk_css_color_value_get_coord (color, color_space, legacy_rgb_scale, coord); + + return gtk_css_number_value_new (v, GTK_CSS_NUMBER); + } + else + { + GtkCssValue *result; + + result = gtk_css_value_new (GtkCssValue, >K_CSS_VALUE_NUMBER); + result->type = TYPE_COLOR_COORD; + result->color_coord.color_space = color_space; + result->color_coord.color = gtk_css_value_ref (color); + result->color_coord.coord = coord; + result->color_coord.legacy_rgb_scale = legacy_rgb_scale; + result->is_computed = FALSE; + + return result; + } +} diff --git a/gtk/gtkcssnumbervalueprivate.h b/gtk/gtkcssnumbervalueprivate.h index 35b693bd76..6c51ad8469 100644 --- a/gtk/gtkcssnumbervalueprivate.h +++ b/gtk/gtkcssnumbervalueprivate.h @@ -24,6 +24,7 @@ #include "gtk/css/gtkcssparserprivate.h" #include "gtkcsstypesprivate.h" #include "gtkcssvalueprivate.h" +#include "gtkcsscolorprivate.h" G_BEGIN_DECLS @@ -39,6 +40,9 @@ typedef enum /*< skip >*/ { typedef struct { /* Context needed when parsing numbers */ + GtkCssValue *color; + GtkCssColorSpace color_space; + gboolean legacy_rgb_scale; /* r, g, b must be scaled to 255 */ } GtkCssNumberParseContext; #define GTK_CSS_PARSE_DIMENSION (GTK_CSS_PARSE_LENGTH|GTK_CSS_PARSE_ANGLE|GTK_CSS_PARSE_TIME) @@ -71,6 +75,11 @@ double gtk_css_number_value_get_canonical (GtkCssValue *num gboolean gtk_css_dimension_value_is_zero (const GtkCssValue *value) G_GNUC_PURE; +GtkCssValue * gtk_css_number_value_new_color_component (GtkCssValue *color, + GtkCssColorSpace color_space, + gboolean legacy_srgb, + guint coord); + enum { ROUND_NEAREST, ROUND_UP,