From 8da70fec91a92d7adea2bfb2522f7a5a63f3b8c1 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sat, 15 Jun 2024 16:54:59 -0400 Subject: [PATCH] css: Parse interpolation for linear gradients Parse things like "in hsl hue longer". For details, see the CSS Images Module Level 4, https://www.w3.org/TR/css-images-4. Tests included. Gradient interpolation color spaces aren't supported for rendering yet. --- gtk/gtkcssimagelinear.c | 179 ++++++++++++------ gtk/gtkcssimagelinearprivate.h | 3 + .../css/parser/linear-gradient-invalid.css | 39 ++++ .../css/parser/linear-gradient-invalid.errors | 16 ++ .../parser/linear-gradient-invalid.ref.css | 0 testsuite/css/parser/linear-gradient.css | 20 ++ testsuite/css/parser/linear-gradient.ref.css | 20 ++ 7 files changed, 214 insertions(+), 63 deletions(-) create mode 100644 testsuite/css/parser/linear-gradient-invalid.css create mode 100644 testsuite/css/parser/linear-gradient-invalid.errors create mode 100644 testsuite/css/parser/linear-gradient-invalid.ref.css diff --git a/gtk/gtkcssimagelinear.c b/gtk/gtkcssimagelinear.c index 939d9b2fb4..046299801c 100644 --- a/gtk/gtkcssimagelinear.c +++ b/gtk/gtkcssimagelinear.c @@ -244,6 +244,9 @@ gtk_css_image_linear_snapshot (GtkCssImage *image, last = i; } + if (linear->color_space != GTK_CSS_COLOR_SPACE_SRGB) + g_warning_once ("Gradient interpolation color spaces are not supported yet"); + if (linear->repeating) { gtk_snapshot_append_repeating_linear_gradient ( @@ -304,75 +307,102 @@ gtk_css_image_linear_parse_first_arg (GtkCssImageLinear *linear, GArray *stop_array) { guint i; + gboolean has_colorspace = FALSE; + gboolean has_side_or_angle = FALSE; + guint retval = 1; - if (gtk_css_parser_try_ident (parser, "to")) + do { - for (i = 0; i < 2; i++) + if (!has_colorspace &>k_css_color_interpolation_method_can_parse (parser)) { - if (gtk_css_parser_try_ident (parser, "left")) - { - if (linear->side & ((1 << GTK_CSS_LEFT) | (1 << GTK_CSS_RIGHT))) - { - gtk_css_parser_error_syntax (parser, "Expected 'top', 'bottom' or comma"); - return 0; - } - linear->side |= (1 << GTK_CSS_LEFT); - } - else if (gtk_css_parser_try_ident (parser, "right")) - { - if (linear->side & ((1 << GTK_CSS_LEFT) | (1 << GTK_CSS_RIGHT))) - { - gtk_css_parser_error_syntax (parser, "Expected 'top', 'bottom' or comma"); - return 0; - } - linear->side |= (1 << GTK_CSS_RIGHT); - } - else if (gtk_css_parser_try_ident (parser, "top")) - { - if (linear->side & ((1 << GTK_CSS_TOP) | (1 << GTK_CSS_BOTTOM))) - { - gtk_css_parser_error_syntax (parser, "Expected 'left', 'right' or comma"); - return 0; - } - linear->side |= (1 << GTK_CSS_TOP); - } - else if (gtk_css_parser_try_ident (parser, "bottom")) - { - if (linear->side & ((1 << GTK_CSS_TOP) | (1 << GTK_CSS_BOTTOM))) - { - gtk_css_parser_error_syntax (parser, "Expected 'left', 'right' or comma"); - return 0; - } - linear->side |= (1 << GTK_CSS_BOTTOM); - } - else - break; + if (!gtk_css_color_interpolation_method_parse (parser, &linear->color_space, &linear->hue_interp)) + return 0; + has_colorspace = TRUE; } - - if (linear->side == 0) + else if (!has_side_or_angle && gtk_css_parser_try_ident (parser, "to")) { - gtk_css_parser_error_syntax (parser, "Expected side that gradient should go to"); + gtk_css_parser_consume_token (parser); + + for (i = 0; i < 2; i++) + { + if (gtk_css_parser_try_ident (parser, "left")) + { + if (linear->side & ((1 << GTK_CSS_LEFT) | (1 << GTK_CSS_RIGHT))) + { + gtk_css_parser_error_syntax (parser, "Expected 'top', 'bottom' or comma"); + return 0; + } + linear->side |= (1 << GTK_CSS_LEFT); + } + else if (gtk_css_parser_try_ident (parser, "right")) + { + if (linear->side & ((1 << GTK_CSS_LEFT) | (1 << GTK_CSS_RIGHT))) + { + gtk_css_parser_error_syntax (parser, "Expected 'top', 'bottom' or comma"); + return 0; + } + linear->side |= (1 << GTK_CSS_RIGHT); + } + else if (gtk_css_parser_try_ident (parser, "top")) + { + if (linear->side & ((1 << GTK_CSS_TOP) | (1 << GTK_CSS_BOTTOM))) + { + gtk_css_parser_error_syntax (parser, "Expected 'left', 'right' or comma"); + return 0; + } + linear->side |= (1 << GTK_CSS_TOP); + } + else if (gtk_css_parser_try_ident (parser, "bottom")) + { + if (linear->side & ((1 << GTK_CSS_TOP) | (1 << GTK_CSS_BOTTOM))) + { + gtk_css_parser_error_syntax (parser, "Expected 'left', 'right' or comma"); + return 0; + } + linear->side |= (1 << GTK_CSS_BOTTOM); + } + else + break; + } + + if (linear->side == 0) + { + gtk_css_parser_error_syntax (parser, "Expected side that gradient should go to"); + return 0; + } + + has_side_or_angle = TRUE; + } + else if (!has_side_or_angle && gtk_css_number_value_can_parse (parser)) + { + linear->angle = gtk_css_number_value_parse (parser, GTK_CSS_PARSE_ANGLE); + if (linear->angle == NULL) + return 0; + + has_side_or_angle = TRUE; + } + else if (gtk_css_token_is (gtk_css_parser_get_token (parser), GTK_CSS_TOKEN_COMMA)) + { + retval = 1; + break; + } + else + { + if (gtk_css_image_linear_parse_color_stop (linear, parser, stop_array)) + { + retval = 2; + break; + } + return 0; } - - return 1; } - else if (gtk_css_number_value_can_parse (parser)) - { - linear->angle = gtk_css_number_value_parse (parser, GTK_CSS_PARSE_ANGLE); - if (linear->angle == NULL) - return 0; + while (!(has_colorspace && has_side_or_angle)); - return 1; - } - else - { - linear->side = 1 << GTK_CSS_BOTTOM; - if (!gtk_css_image_linear_parse_color_stop (linear, parser, stop_array)) - return 0; + if (linear->angle == NULL && linear->side == 0) + linear->side = (1 << GTK_CSS_BOTTOM); - return 2; - } + return retval; } typedef struct @@ -437,6 +467,7 @@ gtk_css_image_linear_print (GtkCssImage *image, { GtkCssImageLinear *linear = GTK_CSS_IMAGE_LINEAR (image); guint i; + gboolean has_printed = FALSE; if (linear->repeating) g_string_append (string, "repeating-linear-gradient("); @@ -459,15 +490,29 @@ gtk_css_image_linear_print (GtkCssImage *image, else if (linear->side & (1 << GTK_CSS_RIGHT)) g_string_append (string, " right"); - g_string_append (string, ", "); + has_printed = TRUE; } } else { gtk_css_value_print (linear->angle, string); - g_string_append (string, ", "); + has_printed = TRUE; } + if (linear->color_space != GTK_CSS_COLOR_SPACE_SRGB) + { + if (has_printed) + g_string_append_c (string, ' '); + + gtk_css_color_interpolation_method_print (linear->color_space, + linear->hue_interp, + string); + has_printed = TRUE; + } + + if (has_printed) + g_string_append (string, ", "); + for (i = 0; i < linear->n_stops; i++) { const GtkCssImageLinearColorStop *stop = &linear->color_stops[i]; @@ -499,6 +544,8 @@ gtk_css_image_linear_compute (GtkCssImage *image, copy = g_object_new (GTK_TYPE_CSS_IMAGE_LINEAR, NULL); copy->repeating = linear->repeating; copy->side = linear->side; + copy->color_space = linear->color_space; + copy->hue_interp = linear->hue_interp; if (linear->angle) copy->angle = gtk_css_value_compute (linear->angle, property_id, context); @@ -545,11 +592,15 @@ gtk_css_image_linear_transition (GtkCssImage *start_image, end = GTK_CSS_IMAGE_LINEAR (end_image); if ((start->repeating != end->repeating) - || (start->n_stops != end->n_stops)) + || (start->n_stops != end->n_stops) + || (start->color_space != end->color_space) + || (start->hue_interp != end->hue_interp)) return GTK_CSS_IMAGE_CLASS (_gtk_css_image_linear_parent_class)->transition (start_image, end_image, property_id, progress); result = g_object_new (GTK_TYPE_CSS_IMAGE_LINEAR, NULL); result->repeating = start->repeating; + result->color_space = start->color_space; + result->hue_interp = start->hue_interp; if (start->side != end->side) goto fail; @@ -618,7 +669,9 @@ gtk_css_image_linear_equal (GtkCssImage *image1, if (linear1->repeating != linear2->repeating || linear1->side != linear2->side || (linear1->side == 0 && !gtk_css_value_equal (linear1->angle, linear2->angle)) || - linear1->n_stops != linear2->n_stops) + linear1->n_stops != linear2->n_stops || + linear1->color_space != linear2->color_space || + linear1->hue_interp != linear2->hue_interp) return FALSE; for (i = 0; i < linear1->n_stops; i++) diff --git a/gtk/gtkcssimagelinearprivate.h b/gtk/gtkcssimagelinearprivate.h index 4ddc4eb424..0e93807b2d 100644 --- a/gtk/gtkcssimagelinearprivate.h +++ b/gtk/gtkcssimagelinearprivate.h @@ -48,6 +48,9 @@ struct _GtkCssImageLinear guint repeating :1; GtkCssValue *angle; + GtkCssColorSpace color_space; + GtkCssHueInterpolation hue_interp; + guint n_stops; GtkCssImageLinearColorStop *color_stops; }; diff --git a/testsuite/css/parser/linear-gradient-invalid.css b/testsuite/css/parser/linear-gradient-invalid.css new file mode 100644 index 0000000000..162a9292e8 --- /dev/null +++ b/testsuite/css/parser/linear-gradient-invalid.css @@ -0,0 +1,39 @@ +a { + background-image: linear-gradient(to top bottom, red 0% blue 100%); + border-image-source: repeating-linear-gradient(to top bottom, red 0% blue 100%); +} + +b { + background-image: linear-gradient(to top left bottom, red 0% blue 100%); + border-image-source: repeating-linear-gradient(to top left bottom, red 0% blue 100%); +} + +c { + background-image: linear-gradient(to top top, red 0% blue 100%); + border-image-source: repeating-linear-gradient(to top top, red 0% blue 100%); +} + +d { + background-image: linear-gradient(to top to left, red 0% blue 100%); + border-image-source: repeating-linear-gradient(to top to left, red 0% blue 100%); +} + +e { + background-image: linear-gradient(to top left 0turn, red 0% blue 100%); + border-image-source: repeating-linear-gradient(to top left 0turn, red 0% blue 100%); +} + +f { + background-image: linear-gradient(in swishy, red 0% blue 100%); + border-image-source: repeating-linear-gradient(in swishy, red 0% blue 100%); +} + +g { + background-image: linear-gradient(in srgb longer hue, red 0% blue 100%); + border-image-source: repeating-linear-gradient(in srgb longer hue, red 0% blue 100%); +} + +h { + background-image: linear-gradient(in srgb in srgb, red 0% blue 100%); + border-image-source: repeating-linear-gradient(in srgb in srgb, red 0% blue 100%); +} diff --git a/testsuite/css/parser/linear-gradient-invalid.errors b/testsuite/css/parser/linear-gradient-invalid.errors new file mode 100644 index 0000000000..30b7715b30 --- /dev/null +++ b/testsuite/css/parser/linear-gradient-invalid.errors @@ -0,0 +1,16 @@ +linear-gradient-invalid.css:2:44-50: error: GTK_CSS_PARSER_ERROR_SYNTAX +linear-gradient-invalid.css:3:57-63: error: GTK_CSS_PARSER_ERROR_SYNTAX +linear-gradient-invalid.css:7:49-55: error: GTK_CSS_PARSER_ERROR_SYNTAX +linear-gradient-invalid.css:8:62-68: error: GTK_CSS_PARSER_ERROR_SYNTAX +linear-gradient-invalid.css:12:44-47: error: GTK_CSS_PARSER_ERROR_SYNTAX +linear-gradient-invalid.css:13:57-60: error: GTK_CSS_PARSER_ERROR_SYNTAX +linear-gradient-invalid.css:17:44-46: error: GTK_CSS_PARSER_ERROR_SYNTAX +linear-gradient-invalid.css:18:57-59: error: GTK_CSS_PARSER_ERROR_SYNTAX +linear-gradient-invalid.css:22:49-54: error: GTK_CSS_PARSER_ERROR_SYNTAX +linear-gradient-invalid.css:23:62-67: error: GTK_CSS_PARSER_ERROR_SYNTAX +linear-gradient-invalid.css:27:40-46: error: GTK_CSS_PARSER_ERROR_SYNTAX +linear-gradient-invalid.css:28:53-59: error: GTK_CSS_PARSER_ERROR_SYNTAX +linear-gradient-invalid.css:32:45-51: error: GTK_CSS_PARSER_ERROR_SYNTAX +linear-gradient-invalid.css:33:58-64: error: GTK_CSS_PARSER_ERROR_SYNTAX +linear-gradient-invalid.css:37:45-47: error: GTK_CSS_PARSER_ERROR_SYNTAX +linear-gradient-invalid.css:38:58-60: error: GTK_CSS_PARSER_ERROR_SYNTAX diff --git a/testsuite/css/parser/linear-gradient-invalid.ref.css b/testsuite/css/parser/linear-gradient-invalid.ref.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/testsuite/css/parser/linear-gradient.css b/testsuite/css/parser/linear-gradient.css index 91dc474321..939c5bd665 100644 --- a/testsuite/css/parser/linear-gradient.css +++ b/testsuite/css/parser/linear-gradient.css @@ -102,3 +102,23 @@ u { background-image: linear-gradient(red -5px, green 20em, blue, purple -42%, pink 3pt); border-image-source: repeating-linear-gradient(red -5px, green 20em, blue, purple -42%, pink 3pt); } + +v { + background-image: linear-gradient(to left top in srgb, red 0, green 100%); + border-image-source: repeating-linear-gradient(to left top in srgb, red 0, green 100%); +} + +w { + background-image: linear-gradient(to left top in hsl longer hue, red 0, green 100%); + border-image-source: repeating-linear-gradient(to left top in hsl longer hue, red 0, green 100%); +} + +x { + background-image: linear-gradient(25deg in rec2020, red 0, green 100%); + border-image-source: repeating-linear-gradient(25deg in rec2020, red 0, green 100%); +} + +y { + background-image: linear-gradient(in hwb to bottom, red 0, green 100%); + border-image-source: repeating-linear-gradient(in hwb to bottom, red 0, green 100%); +} diff --git a/testsuite/css/parser/linear-gradient.ref.css b/testsuite/css/parser/linear-gradient.ref.css index f20227dbcf..2e87b44934 100644 --- a/testsuite/css/parser/linear-gradient.ref.css +++ b/testsuite/css/parser/linear-gradient.ref.css @@ -102,3 +102,23 @@ u { background-image: linear-gradient(rgb(255,0,0) -5px, rgb(0,128,0) 20em, rgb(0,0,255), rgb(128,0,128) -42%, rgb(255,192,203) 3pt); border-image-source: repeating-linear-gradient(rgb(255,0,0) -5px, rgb(0,128,0) 20em, rgb(0,0,255), rgb(128,0,128) -42%, rgb(255,192,203) 3pt); } + +v { + background-image: linear-gradient(to top left, rgb(255,0,0) 0, rgb(0,128,0) 100%); + border-image-source: repeating-linear-gradient(to top left, rgb(255,0,0) 0, rgb(0,128,0) 100%); +} + +w { + background-image: linear-gradient(to top left in hsl longer hue, rgb(255,0,0) 0, rgb(0,128,0) 100%); + border-image-source: repeating-linear-gradient(to top left in hsl longer hue, rgb(255,0,0) 0, rgb(0,128,0) 100%); +} + +x { + background-image: linear-gradient(25deg in rec2020, rgb(255,0,0) 0, rgb(0,128,0) 100%); + border-image-source: repeating-linear-gradient(25deg in rec2020, rgb(255,0,0) 0, rgb(0,128,0) 100%); +} + +y { + background-image: linear-gradient(in hwb, rgb(255,0,0) 0, rgb(0,128,0) 100%); + border-image-source: repeating-linear-gradient(in hwb, rgb(255,0,0) 0, rgb(0,128,0) 100%); +}