From c804a3863e860de16f776544d3f632b484e0942c Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Mon, 30 Nov 2020 10:05:18 +0100 Subject: [PATCH] stroke: Add support for dashes ... and hook it up in the node parser and for Cairo rendering. --- gsk/gskrendernodeparser.c | 81 ++++++++++++++++++++++ gsk/gskstroke.c | 138 ++++++++++++++++++++++++++++++++++++++ gsk/gskstroke.h | 15 +++++ gsk/gskstrokeprivate.h | 9 +++ 4 files changed, 243 insertions(+) diff --git a/gsk/gskrendernodeparser.c b/gsk/gskrendernodeparser.c index 501a9f3c65..146b03fd81 100644 --- a/gsk/gskrendernodeparser.c +++ b/gsk/gskrendernodeparser.c @@ -1822,6 +1822,43 @@ clear_path (gpointer inout_path) g_clear_pointer ((GskPath **) inout_path, gsk_path_unref); } +static gboolean +parse_dash (GtkCssParser *parser, + gpointer out_dash) +{ + GArray *dash; + double d; + + /* because CSS does this, too */ + if (gtk_css_parser_try_ident (parser, "none")) + { + *((GArray **) out_dash) = NULL; + return TRUE; + } + + dash = g_array_new (FALSE, FALSE, sizeof (float)); + do { + if (!gtk_css_parser_consume_number (parser, &d)) + { + g_array_free (dash, TRUE); + return FALSE; + } + + g_array_append_vals (dash, (float[1]) { d }, 1); + } while (gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_SIGNLESS_NUMBER) || + gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_SIGNLESS_INTEGER)); + + *((GArray **) out_dash) = dash; + + return TRUE; +} + +static void +clear_dash (gpointer inout_array) +{ + g_clear_pointer ((GArray **) inout_array, g_array_unref); +} + static gboolean parse_enum (GtkCssParser *parser, GType type, @@ -1915,6 +1952,8 @@ parse_stroke_node (GtkCssParser *parser) int line_cap = GSK_LINE_CAP_BUTT; int line_join = GSK_LINE_JOIN_MITER; double miter_limit = 4.0; + GArray *dash = NULL; + double dash_offset = 0.0; GskStroke *stroke; const Declaration declarations[] = { @@ -1924,6 +1963,8 @@ parse_stroke_node (GtkCssParser *parser) { "line-cap", parse_line_cap, NULL, &line_cap }, { "line-join", parse_line_join, NULL, &line_join }, { "miter-limit", parse_double, NULL, &miter_limit }, + { "dash", parse_dash, clear_dash, &dash }, + { "dash-offset", parse_double, NULL, &dash_offset}, }; GskRenderNode *result; @@ -1937,6 +1978,12 @@ parse_stroke_node (GtkCssParser *parser) gsk_stroke_set_line_cap (stroke, line_cap); gsk_stroke_set_line_join (stroke, line_join); gsk_stroke_set_miter_limit (stroke, miter_limit); + if (dash) + { + gsk_stroke_set_dash (stroke, (float *) dash->data, dash->len); + g_array_free (dash, TRUE); + } + gsk_stroke_set_dash_offset (stroke, dash_offset); result = gsk_stroke_node_new (child, path, stroke); @@ -2627,6 +2674,34 @@ append_path_param (Printer *p, g_free (str); } +static void +append_dash_param (Printer *p, + const char *param_name, + const float *dash, + gsize n_dash) +{ + _indent (p); + g_string_append (p->str, "dash: "); + + if (n_dash == 0) + { + g_string_append (p->str, "none"); + } + else + { + gsize i; + + string_append_double (p->str, dash[0]); + for (i = 1; i < n_dash; i++) + { + g_string_append_c (p->str, ' '); + string_append_double (p->str, dash[i]); + } + } + + g_string_append (p->str, ";\n"); +} + static void render_node_print (Printer *p, GskRenderNode *node) @@ -2794,6 +2869,8 @@ render_node_print (Printer *p, case GSK_STROKE_NODE: { const GskStroke *stroke; + const float *dash; + gsize n_dash; start_node (p, "stroke"); @@ -2805,6 +2882,10 @@ render_node_print (Printer *p, append_enum_param (p, "line-cap", GSK_TYPE_LINE_CAP, gsk_stroke_get_line_cap (stroke)); append_enum_param (p, "line-join", GSK_TYPE_LINE_JOIN, gsk_stroke_get_line_join (stroke)); append_float_param (p, "miter-limit", gsk_stroke_get_miter_limit (stroke), 4.0f); + dash = gsk_stroke_get_dash (stroke, &n_dash); + if (dash) + append_dash_param (p, "dash", dash, n_dash); + append_float_param (p, "dash-offset", gsk_stroke_get_dash_offset (stroke), 0.0f); end_node (p); } diff --git a/gsk/gskstroke.c b/gsk/gskstroke.c index 4b0256f911..bb60684324 100644 --- a/gsk/gskstroke.c +++ b/gsk/gskstroke.c @@ -138,6 +138,20 @@ gsk_stroke_to_cairo (const GskStroke *self, } cairo_set_miter_limit (cr, self->miter_limit); + + if (self->dash_length) + { + gsize i; + double *dash = g_newa (double, self->n_dash); + + for (i = 0; i < self->n_dash; i++) + { + dash[i] = self->dash[i]; + } + cairo_set_dash (cr, dash, self->n_dash, self->dash_offset); + } + else + cairo_set_dash (cr, NULL, 0, 0.0); } /** @@ -162,6 +176,18 @@ gsk_stroke_equal (gconstpointer stroke1, self1->miter_limit != self2->miter_limit) return FALSE; + if (self1->n_dash != self2->n_dash) + return FALSE; + + for (int i = 0; i < self1->n_dash; i++) + { + if (self1->dash[i] != self2->dash[i]) + return FALSE; + } + + if (self1->dash_offset != self2->dash_offset) + return FALSE; + return TRUE; } @@ -309,3 +335,115 @@ gsk_stroke_get_miter_limit (const GskStroke *self) return self->miter_limit; } + +/** + * gsk_stroke_set_dash: + * @self: a `GskStroke` + * @dash: (array length=n_dash) (transfer none) (allow-none): the array of dashes + * @n_dash: number of elements in @dash + * + * Sets the dash pattern to use by this stroke. A dash pattern is specified by + * an array of alternating non-negative values. Each value provides the length + * of alternate "on" and "off" portions of the stroke. + * + * Each "on" segment will have caps applied as if the segment were a separate + * contour. In particular, it is valid to use an "on" length of 0 with + * `GSK_LINE_CAP_ROUND` or `GSK_LINE_CAP_SQUARE` to draw dots or squares along + * a path. + * + * If @n_dash is 0, if all elements in @dash are 0, or if there are negative + * values in @dash, then dashing is disabled. + * + * If @n_dash is 1, an alternating "on" and "off" pattern with the single + * dash length provided is assumed. + * + * If @n_dash is uneven, the dash array will be used with the first element + * in @dash defining an "on" or "off" in alternating passes through the array. + * + * You can specify a starting offset into the dash with [method@Gsk.Stroke.set_dash_offset]. + **/ +void +gsk_stroke_set_dash (GskStroke *self, + const float *dash, + gsize n_dash) +{ + float dash_length; + gsize i; + + g_return_if_fail (self != NULL); + g_return_if_fail (dash != NULL || n_dash == 0); + + dash_length = 0; + for (i = 0; i < n_dash; i++) + { + if (!(dash[i] >= 0)) /* should catch NaN */ + { + g_critical ("invalid value in dash array at position %zu", i); + return; + } + dash_length += dash[i]; + } + + self->dash_length = dash_length; + g_free (self->dash); + self->dash = g_memdup (dash, sizeof (gfloat) * n_dash); + self->n_dash = n_dash; + +} + +/** + * gsk_stroke_get_dash: + * @self: a `GskStroke` + * @n_dash: (out caller-allocates): number of elements + * in the array returned + * + * Gets the dash array in use or %NULL if dashing is disabled. + * + * Returns: (array length=n_dash) (transfer none) (allow-none): + * The dash array or %NULL if the dash array is empty. + **/ +const float * +gsk_stroke_get_dash (const GskStroke *self, + gsize *n_dash) +{ + g_return_val_if_fail (self != NULL, NULL); + g_return_val_if_fail (n_dash != NULL, NULL); + + *n_dash = self->n_dash; + + return self->dash; +} + +/** + * gsk_stroke_set_dash_offset: + * @self: a `GskStroke` + * @offset: offset into the dash pattern + * + * Sets the offset into the dash pattern set via [method@Gsk.Stroke.set_dash] + * where dashing should begin. + * + * This is an offset into the length of the path, not an index into + * the array values of the dash array. + **/ +void +gsk_stroke_set_dash_offset (GskStroke *self, + float offset) +{ + g_return_if_fail (self != NULL); + + self->dash_offset = offset; +} + +/** + * gsk_stroke_get_dash_offset: + * @self: a `GskStroke` + * + * Returns the dash offset of a `GskStroke`. + */ +float +gsk_stroke_get_dash_offset (const GskStroke *self) +{ + g_return_val_if_fail (self != NULL, 4.f); + + return self->dash_offset; +} diff --git a/gsk/gskstroke.h b/gsk/gskstroke.h index 0605262f3e..233de38f85 100644 --- a/gsk/gskstroke.h +++ b/gsk/gskstroke.h @@ -66,6 +66,21 @@ void gsk_stroke_set_miter_limit (GskStroke GDK_AVAILABLE_IN_ALL float gsk_stroke_get_miter_limit (const GskStroke *self); +GDK_AVAILABLE_IN_ALL +void gsk_stroke_set_dash (GskStroke *self, + const float *dash, + gsize n_dash); +GDK_AVAILABLE_IN_ALL +const float * gsk_stroke_get_dash (const GskStroke *self, + gsize *n_dash); + +GDK_AVAILABLE_IN_ALL +void gsk_stroke_set_dash_offset (GskStroke *self, + float offset); +GDK_AVAILABLE_IN_ALL +float gsk_stroke_get_dash_offset (const GskStroke *self); + + G_END_DECLS diff --git a/gsk/gskstrokeprivate.h b/gsk/gskstrokeprivate.h index 850356b1ec..24594b1e09 100644 --- a/gsk/gskstrokeprivate.h +++ b/gsk/gskstrokeprivate.h @@ -31,6 +31,11 @@ struct _GskStroke GskLineCap line_cap; GskLineJoin line_join; float miter_limit; + + float *dash; + gsize n_dash; + float dash_length; /* sum of all dashes in the array */ + float dash_offset; }; static inline void @@ -38,11 +43,15 @@ gsk_stroke_init_copy (GskStroke *stroke, const GskStroke *other) { *stroke = *other; + + stroke->dash = g_memdup (other->dash, stroke->n_dash * sizeof (float)); } static inline void gsk_stroke_clear (GskStroke *stroke) { + g_clear_pointer (&stroke->dash, g_free); + stroke->n_dash = 0; /* better safe than sorry */ } void gsk_stroke_to_cairo (const GskStroke *self,