mirror of
https://gitlab.gnome.org/GNOME/gtk.git
synced 2024-12-23 12:20:08 +00:00
nodeparser: Add support for cicp color states
Allow defining cicp color states with an @-rule: @cicp "jpeg" { primaries: 1; transfer: 13; matrix: 6; range: full; } And allow using them in color() like this: color("jpeg" 50% 0.5 1 / 75%) Note that custom color states use a string, unlike default color states which use an ident. Test included.
This commit is contained in:
parent
842949fcf3
commit
16431da3f2
@ -5,7 +5,8 @@ GSK render nodes can be serialized and deserialized using APIs such as `gsk_rend
|
||||
The format is a text format that follows the [CSS syntax rules](https://drafts.csswg.org/css-syntax-3/). In particular, this means that every array of bytes will produce a render node when parsed, as there is a defined error recovery method. For more details on error handling, please refer to the documentation of the parsing APIs.
|
||||
|
||||
The grammar of a node text representation using [the CSS value definition syntax](https://drafts.csswg.org/css-values-3/#value-defs) looks like this:
|
||||
**document**: `<node>\*`
|
||||
**document**: `<@-rule>\*<node>\*`
|
||||
**@-rule**: @cicp "name" { <property>* }
|
||||
**node**: container [ "name" ] { <document> } | `<node-type> [ "name" ] { <property>* }` | "name"
|
||||
**property**: `<property-name>: <node> | <value> ;`
|
||||
|
||||
@ -25,6 +26,45 @@ Nodes can be given a name by adding a string after the `<node-type>` in their de
|
||||
|
||||
Just like nodes, textures can be referenced by name. When defining a named texture, the name has to be placed in front of the URL.
|
||||
|
||||
# Color states
|
||||
|
||||
Color states are represented either by an ident (for builtin ones) or a string
|
||||
(for custom ones):
|
||||
|
||||
**color-state**: `<ident> | <string>`
|
||||
|
||||
Custom color states can be defined at the beginning of the document, with an `@cicp` rule.
|
||||
|
||||
The format for `@cicp` rules is
|
||||
|
||||
@cicp "name" {
|
||||
...
|
||||
}
|
||||
|
||||
The following properties can be set for custom color states:
|
||||
|
||||
| property | syntax | default | printed |
|
||||
| --------- | ---------------- | -------- | ----------- |
|
||||
| primaries | `<integer>` | 2 | always |
|
||||
| transfer | `<integer>` | 2 | always |
|
||||
| matrix | `<integer>` | 2 | always |
|
||||
| range | `narrow | full` | full | non-default |
|
||||
|
||||
Note that the primaries, transfer and matrix properties always need
|
||||
to be specified, since GTK does not allow creating color state objects
|
||||
with these being set to 2 (== unspecified).
|
||||
|
||||
# Colors
|
||||
|
||||
Colors can be specified with a variation of the modern CSS color syntax:
|
||||
|
||||
color(<color-state> <number> <number> <number> ["/" <number>])
|
||||
|
||||
The traditional syntax for sRGB colors still works as well:
|
||||
|
||||
rgba(<number>, <number>, <number>, <number)
|
||||
rgb(<number, <number>, <number>)
|
||||
|
||||
# Nodes
|
||||
|
||||
### container
|
||||
|
@ -33,6 +33,7 @@
|
||||
#include "gskprivate.h"
|
||||
|
||||
#include "gdk/gdkcolorstateprivate.h"
|
||||
#include "gdk/gdkcolorprivate.h"
|
||||
#include "gdk/gdkrgbaprivate.h"
|
||||
#include "gdk/gdktextureprivate.h"
|
||||
#include "gdk/gdkmemoryformatprivate.h"
|
||||
@ -65,6 +66,7 @@ struct _Context
|
||||
{
|
||||
GHashTable *named_nodes;
|
||||
GHashTable *named_textures;
|
||||
GHashTable *named_color_states;
|
||||
PangoFontMap *fontmap;
|
||||
};
|
||||
|
||||
@ -89,6 +91,7 @@ context_finish (Context *context)
|
||||
{
|
||||
g_clear_pointer (&context->named_nodes, g_hash_table_unref);
|
||||
g_clear_pointer (&context->named_textures, g_hash_table_unref);
|
||||
g_clear_pointer (&context->named_color_states, g_hash_table_unref);
|
||||
g_clear_object (&context->fontmap);
|
||||
}
|
||||
|
||||
@ -1468,6 +1471,100 @@ typedef struct
|
||||
float values[4];
|
||||
} Color;
|
||||
|
||||
static gboolean
|
||||
parse_cicp_range (GtkCssParser *parser,
|
||||
Context *context,
|
||||
gpointer out)
|
||||
{
|
||||
if (!parse_enum (parser, GDK_TYPE_CICP_RANGE, out))
|
||||
return FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
parse_unsigned (GtkCssParser *parser,
|
||||
Context *context,
|
||||
gpointer out)
|
||||
{
|
||||
const GtkCssToken *token;
|
||||
|
||||
token = gtk_css_parser_get_token (parser);
|
||||
if (gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNLESS_INTEGER))
|
||||
{
|
||||
gtk_css_parser_consume_token (parser);
|
||||
*((guint *)out) = (guint) token->number.number;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gtk_css_parser_error_value (parser, "Not an allowed value here");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
parse_color_state_rule (GtkCssParser *parser,
|
||||
Context *context)
|
||||
{
|
||||
char *name = NULL;
|
||||
GdkColorState *cs = NULL;
|
||||
GdkCicp cicp = { 2, 2, 2, GDK_CICP_RANGE_FULL };
|
||||
const Declaration declarations[] = {
|
||||
{ "primaries", parse_unsigned, NULL, &cicp.color_primaries },
|
||||
{ "transfer", parse_unsigned, NULL, &cicp.transfer_function },
|
||||
{ "matrix", parse_unsigned, NULL, &cicp.matrix_coefficients },
|
||||
{ "range", parse_cicp_range, NULL, &cicp.range },
|
||||
};
|
||||
const char *default_names[] = { "srgb", "srgb-linear", "rec2100-pq", "rec2100-linear", NULL};
|
||||
GError *error = NULL;
|
||||
GtkCssLocation start;
|
||||
GtkCssLocation end;
|
||||
|
||||
/* We only return FALSE when we see the wrong @ keyword, since the caller
|
||||
* throws an error in this case.
|
||||
*/
|
||||
if (!gtk_css_parser_try_at_keyword (parser, "cicp"))
|
||||
return FALSE;
|
||||
|
||||
name = gtk_css_parser_consume_string (parser);
|
||||
if (name == NULL)
|
||||
return TRUE;
|
||||
|
||||
if (g_strv_contains (default_names, name) ||
|
||||
(context->named_color_states &&
|
||||
g_hash_table_contains (context->named_color_states, name)))
|
||||
{
|
||||
gtk_css_parser_error_value (parser, "A color state named \"%s\" already exists", name);
|
||||
g_free (name);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
start = *gtk_css_parser_get_block_location (parser);
|
||||
end = *gtk_css_parser_get_end_location (parser);
|
||||
|
||||
gtk_css_parser_end_block_prelude (parser);
|
||||
|
||||
parse_declarations (parser, context, declarations, G_N_ELEMENTS (declarations));
|
||||
|
||||
cs = gdk_color_state_new_for_cicp (&cicp, &error);
|
||||
|
||||
if (!cs)
|
||||
{
|
||||
gtk_css_parser_error (parser,
|
||||
GTK_CSS_PARSER_ERROR_UNKNOWN_VALUE,
|
||||
&start, &end,
|
||||
"Not a valid cicp tuple: %s", error->message);
|
||||
g_error_free (error);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (context->named_color_states == NULL)
|
||||
context->named_color_states = g_hash_table_new_full (g_str_hash, g_str_equal,
|
||||
g_free, (GDestroyNotify) gdk_color_state_unref);
|
||||
g_hash_table_insert (context->named_color_states, name, cs);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
parse_color_state (GtkCssParser *parser,
|
||||
Context *context,
|
||||
@ -1483,13 +1580,29 @@ parse_color_state (GtkCssParser *parser,
|
||||
cs = gdk_color_state_get_rec2100_pq ();
|
||||
else if (gtk_css_parser_try_ident (parser, "rec2100-linear"))
|
||||
cs = gdk_color_state_get_rec2100_linear ();
|
||||
else if (gtk_css_token_is (gtk_css_parser_get_token (parser), GTK_CSS_TOKEN_STRING))
|
||||
{
|
||||
char *name = gtk_css_parser_consume_string (parser);
|
||||
|
||||
if (context->named_color_states)
|
||||
cs = g_hash_table_lookup (context->named_color_states, name);
|
||||
|
||||
if (!cs)
|
||||
{
|
||||
gtk_css_parser_error_value (parser, "No color state named \"%s\"", name);
|
||||
g_free (name);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
g_free (name);
|
||||
}
|
||||
else
|
||||
{
|
||||
gtk_css_parser_error_syntax (parser, "Expected a valid color state");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
*(GdkColorState **) color_state = cs;
|
||||
*(GdkColorState **) color_state = gdk_color_state_ref (cs);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@ -3045,6 +3158,16 @@ gsk_render_node_deserialize_from_bytes (GBytes *bytes,
|
||||
&error_func_pair, NULL);
|
||||
context_init (&context);
|
||||
|
||||
while (gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_AT_KEYWORD))
|
||||
{
|
||||
gtk_css_parser_start_semicolon_block (parser, GTK_CSS_TOKEN_OPEN_CURLY);
|
||||
if (!parse_color_state_rule (parser, &context))
|
||||
{
|
||||
gtk_css_parser_error_syntax (parser, "Unknown @ rule");
|
||||
}
|
||||
gtk_css_parser_end_block (parser);
|
||||
}
|
||||
|
||||
root = parse_container_node (parser, &context);
|
||||
|
||||
if (root && gsk_container_node_get_n_children (root) == 1)
|
||||
@ -3072,6 +3195,8 @@ typedef struct
|
||||
gsize named_node_counter;
|
||||
GHashTable *named_textures;
|
||||
gsize named_texture_counter;
|
||||
GHashTable *named_color_states;
|
||||
gsize named_color_state_counter;
|
||||
GHashTable *fonts;
|
||||
} Printer;
|
||||
|
||||
@ -3087,6 +3212,22 @@ printer_init_check_texture (Printer *printer,
|
||||
g_hash_table_insert (printer->named_textures, texture, g_strdup (""));
|
||||
}
|
||||
|
||||
static void
|
||||
printer_init_check_color_state (Printer *printer,
|
||||
GdkColorState *cs)
|
||||
{
|
||||
gpointer name;
|
||||
|
||||
if (GDK_IS_DEFAULT_COLOR_STATE (cs))
|
||||
return;
|
||||
|
||||
if (!g_hash_table_lookup_extended (printer->named_color_states, cs, NULL, &name))
|
||||
{
|
||||
name = g_strdup_printf ("cicp%zu", ++printer->named_color_state_counter);
|
||||
g_hash_table_insert (printer->named_color_states, cs, name);
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
hb_face_t *face;
|
||||
hb_subset_input_t *input;
|
||||
@ -3162,8 +3303,11 @@ printer_init_duplicates_for_node (Printer *printer,
|
||||
printer_init_collect_font_info (printer, node);
|
||||
break;
|
||||
|
||||
case GSK_CAIRO_NODE:
|
||||
case GSK_COLOR_NODE:
|
||||
printer_init_check_color_state (printer, gsk_color_node_get_color2 (node)->color_state);
|
||||
break;
|
||||
|
||||
case GSK_CAIRO_NODE:
|
||||
case GSK_LINEAR_GRADIENT_NODE:
|
||||
case GSK_REPEATING_LINEAR_GRADIENT_NODE:
|
||||
case GSK_RADIAL_GRADIENT_NODE:
|
||||
@ -3288,6 +3432,8 @@ printer_init (Printer *self,
|
||||
self->named_node_counter = 0;
|
||||
self->named_textures = g_hash_table_new_full (NULL, NULL, NULL, g_free);
|
||||
self->named_texture_counter = 0;
|
||||
self->named_color_states = g_hash_table_new_full (NULL, NULL, NULL, g_free);
|
||||
self->named_color_state_counter = 0;
|
||||
self->fonts = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, font_info_free);
|
||||
|
||||
printer_init_duplicates_for_node (self, node);
|
||||
@ -3300,6 +3446,7 @@ printer_clear (Printer *self)
|
||||
g_string_free (self->str, TRUE);
|
||||
g_hash_table_unref (self->named_nodes);
|
||||
g_hash_table_unref (self->named_textures);
|
||||
g_hash_table_unref (self->named_color_states);
|
||||
g_hash_table_unref (self->fonts);
|
||||
}
|
||||
|
||||
@ -3459,6 +3606,15 @@ append_float_param (Printer *p,
|
||||
g_string_append (p->str, ";\n");
|
||||
}
|
||||
|
||||
static void
|
||||
append_unsigned_param (Printer *p,
|
||||
const char *param_name,
|
||||
guint value)
|
||||
{
|
||||
_indent (p);
|
||||
g_string_append_printf (p->str, "%s: %u;\n", param_name, value);
|
||||
}
|
||||
|
||||
static void
|
||||
append_rgba_param (Printer *p,
|
||||
const char *param_name,
|
||||
@ -3481,8 +3637,19 @@ print_color (Printer *p,
|
||||
}
|
||||
else
|
||||
{
|
||||
g_string_append_printf (p->str, "color(%s ",
|
||||
gdk_color_state_get_name (color->color_state));
|
||||
if (GDK_IS_DEFAULT_COLOR_STATE (color->color_state))
|
||||
{
|
||||
g_string_append_printf (p->str, "color(%s ",
|
||||
gdk_color_state_get_name (color->color_state));
|
||||
}
|
||||
else
|
||||
{
|
||||
const char *name = g_hash_table_lookup (p->named_color_states,
|
||||
color->color_state);
|
||||
g_assert (name != NULL);
|
||||
g_string_append_printf (p->str, "color(\"%s\" ", name);
|
||||
}
|
||||
|
||||
string_append_double (p->str, color->r);
|
||||
g_string_append_c (p->str, ' ');
|
||||
string_append_double (p->str, color->g);
|
||||
@ -4715,6 +4882,24 @@ G_GNUC_END_IGNORE_DEPRECATIONS
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
serialize_color_state (Printer *p,
|
||||
GdkColorState *color_state,
|
||||
const char *name)
|
||||
{
|
||||
const GdkCicp *cicp = gdk_color_state_get_cicp (color_state);
|
||||
|
||||
g_string_append_printf (p->str, "@cicp \"%s\" {\n", name);
|
||||
p->indentation_level ++;
|
||||
append_unsigned_param (p, "primaries", cicp->color_primaries);
|
||||
append_unsigned_param (p, "transfer", cicp->transfer_function);
|
||||
append_unsigned_param (p, "matrix", cicp->matrix_coefficients);
|
||||
if (cicp->range != GDK_CICP_RANGE_FULL)
|
||||
append_enum_param (p, "range", GDK_TYPE_CICP_RANGE, cicp->range);
|
||||
p->indentation_level --;
|
||||
g_string_append (p->str, "}\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_render_node_serialize:
|
||||
* @node: a `GskRenderNode`
|
||||
@ -4736,9 +4921,16 @@ gsk_render_node_serialize (GskRenderNode *node)
|
||||
{
|
||||
Printer p;
|
||||
GBytes *res;
|
||||
GHashTableIter iter;
|
||||
GdkColorState *cs;
|
||||
const char *name;
|
||||
|
||||
printer_init (&p, node);
|
||||
|
||||
g_hash_table_iter_init (&iter, p.named_color_states);
|
||||
while (g_hash_table_iter_next (&iter, (gpointer *)&cs, (gpointer *)&name))
|
||||
serialize_color_state (&p, cs, name);
|
||||
|
||||
if (gsk_render_node_get_node_type (node) == GSK_CONTAINER_NODE)
|
||||
{
|
||||
guint i;
|
||||
|
@ -314,12 +314,15 @@ foreach renderer : renderers
|
||||
endforeach
|
||||
|
||||
node_parser_tests = [
|
||||
'at-rule.node',
|
||||
'blend.node',
|
||||
'blend-unknown-mode.errors',
|
||||
'blend-unknown-mode.node',
|
||||
'blend-unknown-mode.ref.node',
|
||||
'border.node',
|
||||
'color.node',
|
||||
'color2.node',
|
||||
'color3.node',
|
||||
'conic-gradient.node',
|
||||
'conic-gradient.ref.node',
|
||||
'crash1.errors',
|
||||
|
3
testsuite/gsk/nodeparser/at-rule.errors
Normal file
3
testsuite/gsk/nodeparser/at-rule.errors
Normal file
@ -0,0 +1,3 @@
|
||||
<data>:1:1-5: error: GTK_CSS_PARSER_ERROR_SYNTAX
|
||||
<data>:4:1-8: error: GTK_CSS_PARSER_ERROR_SYNTAX
|
||||
<data>:6:1-12: error: GTK_CSS_PARSER_ERROR_UNKNOWN_VALUE
|
7
testsuite/gsk/nodeparser/at-rule.node
Normal file
7
testsuite/gsk/nodeparser/at-rule.node
Normal file
@ -0,0 +1,7 @@
|
||||
@foo {
|
||||
}
|
||||
|
||||
@import "foo";
|
||||
|
||||
@cicp "foo" {
|
||||
}
|
0
testsuite/gsk/nodeparser/at-rule.ref.node
Normal file
0
testsuite/gsk/nodeparser/at-rule.ref.node
Normal file
11
testsuite/gsk/nodeparser/color2.node
Normal file
11
testsuite/gsk/nodeparser/color2.node
Normal file
@ -0,0 +1,11 @@
|
||||
@cicp "cs1" {
|
||||
primaries: 1;
|
||||
transfer: 1;
|
||||
matrix: 0;
|
||||
range: full;
|
||||
}
|
||||
|
||||
color {
|
||||
bounds: 100 100 200 300;
|
||||
color: color("cs1" 0.4 50% 1 / 20%);
|
||||
}
|
9
testsuite/gsk/nodeparser/color2.ref.node
Normal file
9
testsuite/gsk/nodeparser/color2.ref.node
Normal file
@ -0,0 +1,9 @@
|
||||
@cicp "cicp1" {
|
||||
primaries: 1;
|
||||
transfer: 1;
|
||||
matrix: 0;
|
||||
}
|
||||
color {
|
||||
bounds: 100 100 200 300;
|
||||
color: color("cicp1" 0.4 0.5 1 / 0.2);
|
||||
}
|
1
testsuite/gsk/nodeparser/color3.errors
Normal file
1
testsuite/gsk/nodeparser/color3.errors
Normal file
@ -0,0 +1 @@
|
||||
<data>:1:1-12: error: GTK_CSS_PARSER_ERROR_UNKNOWN_VALUE
|
2
testsuite/gsk/nodeparser/color3.node
Normal file
2
testsuite/gsk/nodeparser/color3.node
Normal file
@ -0,0 +1,2 @@
|
||||
@cicp "foo" {
|
||||
}
|
0
testsuite/gsk/nodeparser/color3.ref.node
Normal file
0
testsuite/gsk/nodeparser/color3.ref.node
Normal file
Loading…
Reference in New Issue
Block a user