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.
This commit is contained in:
Matthias Clasen 2024-06-15 16:54:59 -04:00
parent f9cd30a859
commit 8da70fec91
7 changed files with 214 additions and 63 deletions

View File

@ -244,6 +244,9 @@ gtk_css_image_linear_snapshot (GtkCssImage *image,
last = i; 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) if (linear->repeating)
{ {
gtk_snapshot_append_repeating_linear_gradient ( gtk_snapshot_append_repeating_linear_gradient (
@ -304,75 +307,102 @@ gtk_css_image_linear_parse_first_arg (GtkCssImageLinear *linear,
GArray *stop_array) GArray *stop_array)
{ {
guint i; 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 &&gtk_css_color_interpolation_method_can_parse (parser))
{ {
if (gtk_css_parser_try_ident (parser, "left")) if (!gtk_css_color_interpolation_method_parse (parser, &linear->color_space, &linear->hue_interp))
{ return 0;
if (linear->side & ((1 << GTK_CSS_LEFT) | (1 << GTK_CSS_RIGHT))) has_colorspace = TRUE;
{
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;
} }
else if (!has_side_or_angle && gtk_css_parser_try_ident (parser, "to"))
if (linear->side == 0)
{ {
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 0;
} }
return 1;
} }
else if (gtk_css_number_value_can_parse (parser)) while (!(has_colorspace && has_side_or_angle));
{
linear->angle = gtk_css_number_value_parse (parser, GTK_CSS_PARSE_ANGLE);
if (linear->angle == NULL)
return 0;
return 1; if (linear->angle == NULL && linear->side == 0)
} linear->side = (1 << GTK_CSS_BOTTOM);
else
{
linear->side = 1 << GTK_CSS_BOTTOM;
if (!gtk_css_image_linear_parse_color_stop (linear, parser, stop_array))
return 0;
return 2; return retval;
}
} }
typedef struct typedef struct
@ -437,6 +467,7 @@ gtk_css_image_linear_print (GtkCssImage *image,
{ {
GtkCssImageLinear *linear = GTK_CSS_IMAGE_LINEAR (image); GtkCssImageLinear *linear = GTK_CSS_IMAGE_LINEAR (image);
guint i; guint i;
gboolean has_printed = FALSE;
if (linear->repeating) if (linear->repeating)
g_string_append (string, "repeating-linear-gradient("); 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)) else if (linear->side & (1 << GTK_CSS_RIGHT))
g_string_append (string, " right"); g_string_append (string, " right");
g_string_append (string, ", "); has_printed = TRUE;
} }
} }
else else
{ {
gtk_css_value_print (linear->angle, string); 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++) for (i = 0; i < linear->n_stops; i++)
{ {
const GtkCssImageLinearColorStop *stop = &linear->color_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 = g_object_new (GTK_TYPE_CSS_IMAGE_LINEAR, NULL);
copy->repeating = linear->repeating; copy->repeating = linear->repeating;
copy->side = linear->side; copy->side = linear->side;
copy->color_space = linear->color_space;
copy->hue_interp = linear->hue_interp;
if (linear->angle) if (linear->angle)
copy->angle = gtk_css_value_compute (linear->angle, property_id, context); 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); end = GTK_CSS_IMAGE_LINEAR (end_image);
if ((start->repeating != end->repeating) 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); 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 = g_object_new (GTK_TYPE_CSS_IMAGE_LINEAR, NULL);
result->repeating = start->repeating; result->repeating = start->repeating;
result->color_space = start->color_space;
result->hue_interp = start->hue_interp;
if (start->side != end->side) if (start->side != end->side)
goto fail; goto fail;
@ -618,7 +669,9 @@ gtk_css_image_linear_equal (GtkCssImage *image1,
if (linear1->repeating != linear2->repeating || if (linear1->repeating != linear2->repeating ||
linear1->side != linear2->side || linear1->side != linear2->side ||
(linear1->side == 0 && !gtk_css_value_equal (linear1->angle, linear2->angle)) || (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; return FALSE;
for (i = 0; i < linear1->n_stops; i++) for (i = 0; i < linear1->n_stops; i++)

View File

@ -48,6 +48,9 @@ struct _GtkCssImageLinear
guint repeating :1; guint repeating :1;
GtkCssValue *angle; GtkCssValue *angle;
GtkCssColorSpace color_space;
GtkCssHueInterpolation hue_interp;
guint n_stops; guint n_stops;
GtkCssImageLinearColorStop *color_stops; GtkCssImageLinearColorStop *color_stops;
}; };

View File

@ -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%);
}

View File

@ -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

View File

@ -102,3 +102,23 @@ u {
background-image: linear-gradient(red -5px, green 20em, blue, purple -42%, pink 3pt); 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); 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%);
}

View File

@ -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); 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); 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%);
}