From e98d9d62ebe8937cbfc8f9bb0b28c4eeda2fac7b Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sun, 17 Sep 2023 10:19:05 -0400 Subject: [PATCH 1/2] inspector: Don't set a NULL fontdesc The font dialog button does not like that. Fixes: #5988 --- gtk/inspector/prop-editor.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/gtk/inspector/prop-editor.c b/gtk/inspector/prop-editor.c index e33b88432f..15de3d4d23 100644 --- a/gtk/inspector/prop-editor.c +++ b/gtk/inspector/prop-editor.c @@ -797,9 +797,12 @@ font_changed (GObject *object, GParamSpec *pspec, gpointer data) font_desc = g_value_get_boxed (&val); - block_controller (G_OBJECT (fb)); - gtk_font_dialog_button_set_font_desc (fb, font_desc); - unblock_controller (G_OBJECT (fb)); + if (font_desc != NULL) + { + block_controller (G_OBJECT (fb)); + gtk_font_dialog_button_set_font_desc (fb, font_desc); + unblock_controller (G_OBJECT (fb)); + } g_value_unset (&val); } From 8f1d8d7cb58a1fc0a54a8ada2d1301e247793050 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sun, 17 Sep 2023 12:29:46 -0400 Subject: [PATCH 2/2] path: Split off gskpathparse.c This is a standalone piece of code, and nicely fits into its own source file. --- gsk/gskpath.c | 797 ------------------------------------------ gsk/gskpathparser.c | 823 ++++++++++++++++++++++++++++++++++++++++++++ gsk/meson.build | 1 + 3 files changed, 824 insertions(+), 797 deletions(-) create mode 100644 gsk/gskpathparser.c diff --git a/gsk/gskpath.c b/gsk/gskpath.c index 9bec06637a..e656aabbd5 100644 --- a/gsk/gskpath.c +++ b/gsk/gskpath.c @@ -817,803 +817,6 @@ gsk_path_foreach_with_tolerance (GskPath *self, return TRUE; } -/* }}} */ -/* {{{ Parser and utilities */ - -static void -skip_whitespace (const char **p) -{ - while (g_ascii_isspace (**p)) - (*p)++; -} - -static void -skip_optional_comma (const char **p) -{ - skip_whitespace (p); - if (**p == ',') - (*p)++; -} - -static gboolean -parse_number (const char **p, - double *c) -{ - char *e; - *c = g_ascii_strtod (*p, &e); - if (e == *p) - return FALSE; - *p = e; - skip_optional_comma (p); - return TRUE; -} - -static gboolean -parse_coordinate (const char **p, - double *c) -{ - return parse_number (p, c); -} - -static gboolean -parse_coordinate_pair (const char **p, - double *x, - double *y) -{ - double xx, yy; - const char *o = *p; - - if (!parse_coordinate (p, &xx)) - { - *p = o; - return FALSE; - } - if (!parse_coordinate (p, &yy)) - { - *p = o; - return FALSE; - } - - *x = xx; - *y = yy; - - return TRUE; -} - -static gboolean -parse_nonnegative_number (const char **p, - double *x) -{ - const char *o = *p; - double n; - - if (!parse_number (p, &n)) - return FALSE; - - if (n < 0) - { - *p = o; - return FALSE; - } - - *x = n; - - return TRUE; -} - -/* This fixes a flaw in our use of strchr() below: - * - * If p already points at the end of the string, - * we misinterpret strchr ("xyz", *p) returning - * non-NULL to mean that we can increment p. - * - * But strchr() will return a pointer to the - * final NUL byte in this case, and we walk off - * the end of the string. Oops - */ -static inline char * -_strchr (const char *str, - int c) -{ - if (c == 0) - return NULL; - else - return strchr (str, c); -} - -static gboolean -parse_flag (const char **p, - gboolean *f) -{ - skip_whitespace (p); - if (_strchr ("01", **p)) - { - *f = **p == '1'; - (*p)++; - skip_optional_comma (p); - return TRUE; - } - - return FALSE; -} - -static gboolean -parse_command (const char **p, - char *cmd) -{ - char *s; - const char *allowed; - - if (*cmd == 'X') - allowed = "mM"; - else - allowed = "mMhHvVzZlLcCsStTqQaAoO"; - - skip_whitespace (p); - s = _strchr (allowed, **p); - if (s) - { - *cmd = *s; - (*p)++; - return TRUE; - } - return FALSE; -} - -static gboolean -parse_string (const char **p, - const char *s) -{ - int len = strlen (s); - if (strncmp (*p, s, len) != 0) - return FALSE; - (*p) += len; - return TRUE; -} - -#define NEAR(x, y) (fabs ((x) - (y)) < 0.001) - -static gboolean -is_rect (double x0, double y0, - double x1, double y1, - double x2, double y2, - double x3, double y3) -{ - return NEAR (x0, x3) && NEAR (x1, x2) && - NEAR (y0, y1) && NEAR (y2, y3) && - x0 < x1 && y1 < y2; -} - -static gboolean -is_line (double x0, double y0, - double x1, double y1, - double x2, double y2, - double x3, double y3) -{ - if (NEAR (y0, y3)) - return x0 <= x1 && x1 <= x2 && x2 <= x3 && - NEAR (y0, y1) && NEAR (y0, y2) && NEAR (y0, y3); - else - return y0 <= y1 && y1 <= y2 && y2 <= y3 && - NEAR (x0, x1) && NEAR (x0, x2) && NEAR (x0, x3); -} - -static gboolean -parse_rectangle (const char **p, - double *x, - double *y, - double *w, - double *h) -{ - const char *o = *p; - double w2; - - if (parse_coordinate_pair (p, x, y) && - parse_string (p, "h") && - parse_coordinate (p, w) && - parse_string (p, "v") && - parse_coordinate (p, h) && - parse_string (p, "h") && - parse_coordinate (p, &w2) && - parse_string (p, "z") && - w2 == -*w && *w >= 0 && *h >= 0) - { - skip_whitespace (p); - - return TRUE; - } - - *p = o; - return FALSE; -} - -static gboolean -parse_circle (const char **p, - double *cx, - double *cy, - double *r) -{ - const char *o = *p; - double x0, y0, x1, y1, x2, y2, x3, y3; - double x4, y4, x5, y5, x6, y6, x7, y7; - double x8, y8, w0, w1, w2, w3; - double rr; - - if (parse_coordinate_pair (p, &x0, &y0) && - parse_string (p, "o") && - parse_coordinate_pair (p, &x1, &y1) && - parse_coordinate_pair (p, &x2, &y2) && - parse_nonnegative_number (p, &w0) && - parse_string (p, "o") && - parse_coordinate_pair (p, &x3, &y3) && - parse_coordinate_pair (p, &x4, &y4) && - parse_nonnegative_number (p, &w1) && - parse_string (p, "o") && - parse_coordinate_pair (p, &x5, &y5) && - parse_coordinate_pair (p, &x6, &y6) && - parse_nonnegative_number (p, &w2) && - parse_string (p, "o") && - parse_coordinate_pair (p, &x7, &y7) && - parse_coordinate_pair (p, &x8, &y8) && - parse_nonnegative_number (p, &w3) && - parse_string (p, "z")) - { - rr = y1; - - if (x1 == 0 && y1 == rr && - x2 == -rr && y2 == rr && - x3 == -rr && y3 == 0 && - x4 == -rr && y4 == -rr && - x5 == 0 && y5 == -rr && - x6 == rr && y6 == -rr && - x7 == rr && y7 == 0 && - x8 == rr && y8 == rr && - NEAR (w0, M_SQRT1_2) && NEAR (w1, M_SQRT1_2) && - NEAR (w2, M_SQRT1_2) && NEAR (w3, M_SQRT1_2)) - { - *cx = x0 - rr; - *cy = y0; - *r = rr; - - skip_whitespace (p); - - return TRUE; - } - } - - *p = o; - return FALSE; -} - -static gboolean -parse_rounded_rect (const char **p, - GskRoundedRect *rr) -{ - const char *o = *p; - double x0, y0, x1, y1, x2, y2, x3, y3; - double x4, y4, x5, y5, x6, y6, x7, y7; - double x8, y8, x9, y9, x10, y10, x11, y11; - double x12, y12, w0, w1, w2, w3; - - if (parse_coordinate_pair (p, &x0, &y0) && - parse_string (p, "L") && - parse_coordinate_pair (p, &x1, &y1) && - parse_string (p, "O") && - parse_coordinate_pair (p, &x2, &y2) && - parse_coordinate_pair (p, &x3, &y3) && - parse_nonnegative_number (p, &w0) && - parse_string (p, "L") && - parse_coordinate_pair (p, &x4, &y4) && - parse_string (p, "O") && - parse_coordinate_pair (p, &x5, &y5) && - parse_coordinate_pair (p, &x6, &y6) && - parse_nonnegative_number (p, &w1) && - parse_string (p, "L") && - parse_coordinate_pair (p, &x7, &y7) && - parse_string (p, "O") && - parse_coordinate_pair (p, &x8, &y8) && - parse_coordinate_pair (p, &x9, &y9) && - parse_nonnegative_number (p, &w2) && - parse_string (p, "L") && - parse_coordinate_pair (p, &x10, &y10) && - parse_string (p, "O") && - parse_coordinate_pair (p, &x11, &y11) && - parse_coordinate_pair (p, &x12, &y12) && - parse_nonnegative_number (p, &w3) && - parse_string (p, "Z")) - { - if (NEAR (x0, x12) && NEAR (y0, y12) && - is_rect (x11, y11, x2, y2, x5, y5, x8, y8) && - is_line (x11, y11, x0, y0, x1, y1, x2, y2) && - is_line (x2, y2, x3, y3, x4, y4, x5, y5) && - is_line (x8, y8, x7, y7, x6, y6, x5, y5) && - is_line (x11, y11, x10, y10, x9, y9, x8, y8) && - NEAR (w0, M_SQRT1_2) && NEAR (w1, M_SQRT1_2) && - NEAR (w2, M_SQRT1_2) && NEAR (w3, M_SQRT1_2)) - { - rr->bounds = GRAPHENE_RECT_INIT (x11, y11, x5 - x11, y5 - y11); - rr->corner[GSK_CORNER_TOP_LEFT] = GRAPHENE_SIZE_INIT (x12 - x11, y10 - y11); - rr->corner[GSK_CORNER_TOP_RIGHT] = GRAPHENE_SIZE_INIT (x2 - x1, y3 - y2); - rr->corner[GSK_CORNER_BOTTOM_RIGHT] = GRAPHENE_SIZE_INIT (x5 - x6, y5 - y4); - rr->corner[GSK_CORNER_BOTTOM_LEFT] = GRAPHENE_SIZE_INIT (x7 - x8, y8 - y7); - - skip_whitespace (p); - - return TRUE; - } - } - - *p = o; - return FALSE; -} - -#undef NEAR - -/** - * gsk_path_parse: - * @string: a string - * - * This is a convenience function that constructs a `GskPath` - * from a serialized form. - * - * The string is expected to be in (a superset of) - * [SVG path syntax](https://www.w3.org/TR/SVG11/paths.html#PathData), - * as e.g. produced by [method@Gsk.Path.to_string]. - * - * A high-level summary of the syntax: - * - * - `M x y` Move to `(x, y)` - * - `L x y` Add a line from the current point to `(x, y)` - * - `Q x1 y1 x2 y2` Add a quadratic Bézier from the current point to `(x2, y2)`, with control point `(x1, y1)` - * - `C x1 y1 x2 y2 x3 y3` Add a cubic Bézier from the current point to `(x3, y3)`, with control points `(x1, y1)` and `(x2, y2)` - * - `Z` Close the contour by drawing a line back to the start point - * - `H x` Add a horizontal line from the current point to the given x value - * - `V y` Add a vertical line from the current point to the given y value - * - `T x2 y2` Add a quadratic Bézier, using the reflection of the previous segments' control point as control point - * - `S x2 y2 x3 y3` Add a cubic Bézier, using the reflection of the previous segments' second control point as first control point - * - `A rx ry r l s x y` Add an elliptical arc from the current point to `(x, y)` with radii rx and ry. See the SVG documentation for how the other parameters influence the arc. - * - `O x1 y1 x2 y2 w` Add a rational quadratic Bézier from the current point to `(x2, y2)` with control point `(x1, y1)` and weight `w`. - * - * All the commands have lowercase variants that interpret coordinates - * relative to the current point. - * - * The `O` command is an extension that is not supported in SVG. - * - * Returns: (nullable): a new `GskPath`, or `NULL` if @string could not be parsed - * - * Since: 4.14 - */ -GskPath * -gsk_path_parse (const char *string) -{ - GskPathBuilder *builder; - double x, y; - double prev_x1, prev_y1; - double path_x, path_y; - const char *p; - char cmd; - char prev_cmd; - gboolean after_comma; - gboolean repeat; - - builder = gsk_path_builder_new (); - - cmd = 'X'; - path_x = path_y = 0; - x = y = 0; - prev_x1 = prev_y1 = 0; - after_comma = FALSE; - - p = string; - while (*p) - { - prev_cmd = cmd; - repeat = !parse_command (&p, &cmd); - - if (after_comma && !repeat) - goto error; - - switch (cmd) - { - case 'X': - goto error; - - case 'Z': - case 'z': - if (repeat) - goto error; - else - { - gsk_path_builder_close (builder); - x = path_x; - y = path_y; - } - break; - - case 'M': - case 'm': - { - double x1, y1, w, h, r; - GskRoundedRect rr; - - /* Look for special contours */ - if (parse_rectangle (&p, &x1, &y1, &w, &h)) - { - gsk_path_builder_add_rect (builder, &GRAPHENE_RECT_INIT (x1, y1, w, h)); - if (_strchr ("zZX", prev_cmd)) - { - path_x = x1; - path_y = y1; - } - - x = x1; - y = y1; - } - else if (parse_circle (&p, &x1, &y1, &r)) - { - gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (x1, y1), r); - - if (_strchr ("zZX", prev_cmd)) - { - path_x = x1 + r; - path_y = y1; - } - - x = x1 + r; - y = y1; - } - else if (parse_rounded_rect (&p, &rr)) - { - gsk_path_builder_add_rounded_rect (builder, &rr); - - if (_strchr ("zZX", prev_cmd)) - { - path_x = rr.bounds.origin.x + rr.corner[GSK_CORNER_TOP_LEFT].width; - path_y = rr.bounds.origin.y; - } - - x = rr.bounds.origin.x + rr.corner[GSK_CORNER_TOP_LEFT].width; - y = rr.bounds.origin.y; - } - else if (parse_coordinate_pair (&p, &x1, &y1)) - { - if (cmd == 'm') - { - x1 += x; - y1 += y; - } - - if (repeat) - gsk_path_builder_line_to (builder, x1, y1); - else - { - gsk_path_builder_move_to (builder, x1, y1); - if (_strchr ("zZX", prev_cmd)) - { - path_x = x1; - path_y = y1; - } - } - - x = x1; - y = y1; - } - else - goto error; - } - break; - - case 'L': - case 'l': - { - double x1, y1; - - if (parse_coordinate_pair (&p, &x1, &y1)) - { - if (cmd == 'l') - { - x1 += x; - y1 += y; - } - - if (_strchr ("zZ", prev_cmd)) - { - gsk_path_builder_move_to (builder, x, y); - path_x = x; - path_y = y; - } - gsk_path_builder_line_to (builder, x1, y1); - x = x1; - y = y1; - } - else - goto error; - } - break; - - case 'H': - case 'h': - { - double x1; - - if (parse_coordinate (&p, &x1)) - { - if (cmd == 'h') - x1 += x; - if (_strchr ("zZ", prev_cmd)) - { - gsk_path_builder_move_to (builder, x, y); - path_x = x; - path_y = y; - } - gsk_path_builder_line_to (builder, x1, y); - x = x1; - } - else - goto error; - } - break; - - case 'V': - case 'v': - { - double y1; - - if (parse_coordinate (&p, &y1)) - { - if (cmd == 'v') - y1 += y; - if (_strchr ("zZ", prev_cmd)) - { - gsk_path_builder_move_to (builder, x, y); - path_x = x; - path_y = y; - } - gsk_path_builder_line_to (builder, x, y1); - y = y1; - } - else - goto error; - } - break; - - case 'C': - case 'c': - { - double x0, y0, x1, y1, x2, y2; - - if (parse_coordinate_pair (&p, &x0, &y0) && - parse_coordinate_pair (&p, &x1, &y1) && - parse_coordinate_pair (&p, &x2, &y2)) - { - if (cmd == 'c') - { - x0 += x; - y0 += y; - x1 += x; - y1 += y; - x2 += x; - y2 += y; - } - if (_strchr ("zZ", prev_cmd)) - { - gsk_path_builder_move_to (builder, x, y); - path_x = x; - path_y = y; - } - gsk_path_builder_cubic_to (builder, x0, y0, x1, y1, x2, y2); - prev_x1 = x1; - prev_y1 = y1; - x = x2; - y = y2; - } - else - goto error; - } - break; - - case 'S': - case 's': - { - double x0, y0, x1, y1, x2, y2; - - if (parse_coordinate_pair (&p, &x1, &y1) && - parse_coordinate_pair (&p, &x2, &y2)) - { - if (cmd == 's') - { - x1 += x; - y1 += y; - x2 += x; - y2 += y; - } - if (_strchr ("CcSs", prev_cmd)) - { - x0 = 2 * x - prev_x1; - y0 = 2 * y - prev_y1; - } - else - { - x0 = x; - y0 = y; - } - if (_strchr ("zZ", prev_cmd)) - { - gsk_path_builder_move_to (builder, x, y); - path_x = x; - path_y = y; - } - gsk_path_builder_cubic_to (builder, x0, y0, x1, y1, x2, y2); - prev_x1 = x1; - prev_y1 = y1; - x = x2; - y = y2; - } - else - goto error; - } - break; - - case 'Q': - case 'q': - { - double x1, y1, x2, y2; - - if (parse_coordinate_pair (&p, &x1, &y1) && - parse_coordinate_pair (&p, &x2, &y2)) - { - if (cmd == 'q') - { - x1 += x; - y1 += y; - x2 += x; - y2 += y; - } - if (_strchr ("zZ", prev_cmd)) - { - gsk_path_builder_move_to (builder, x, y); - path_x = x; - path_y = y; - } - gsk_path_builder_quad_to (builder, x1, y1, x2, y2); - prev_x1 = x1; - prev_y1 = y1; - x = x2; - y = y2; - } - else - goto error; - } - break; - - case 'T': - case 't': - { - double x1, y1, x2, y2; - - if (parse_coordinate_pair (&p, &x2, &y2)) - { - if (cmd == 't') - { - x2 += x; - y2 += y; - } - if (_strchr ("QqTt", prev_cmd)) - { - x1 = 2 * x - prev_x1; - y1 = 2 * y - prev_y1; - } - else - { - x1 = x; - y1 = y; - } - if (_strchr ("zZ", prev_cmd)) - { - gsk_path_builder_move_to (builder, x, y); - path_x = x; - path_y = y; - } - gsk_path_builder_quad_to (builder, x1, y1, x2, y2); - prev_x1 = x1; - prev_y1 = y1; - x = x2; - y = y2; - } - else - goto error; - } - break; - - case 'O': - case 'o': - { - double x1, y1, x2, y2, weight; - - if (parse_coordinate_pair (&p, &x1, &y1) && - parse_coordinate_pair (&p, &x2, &y2) && - parse_nonnegative_number (&p, &weight)) - { - if (cmd == 'o') - { - x1 += x; - y1 += y; - x2 += x; - y2 += y; - } - if (_strchr ("zZ", prev_cmd)) - { - gsk_path_builder_move_to (builder, x, y); - path_x = x; - path_y = y; - } - gsk_path_builder_conic_to (builder, x1, y1, x2, y2, weight); - x = x2; - y = y2; - } - else - goto error; - } - break; - - case 'A': - case 'a': - { - double rx, ry; - double x_axis_rotation; - int large_arc, sweep; - double x1, y1; - - if (parse_nonnegative_number (&p, &rx) && - parse_nonnegative_number (&p, &ry) && - parse_number (&p, &x_axis_rotation) && - parse_flag (&p, &large_arc) && - parse_flag (&p, &sweep) && - parse_coordinate_pair (&p, &x1, &y1)) - { - if (cmd == 'a') - { - x1 += x; - y1 += y; - } - - if (_strchr ("zZ", prev_cmd)) - { - gsk_path_builder_move_to (builder, x, y); - path_x = x; - path_y = y; - } - gsk_path_builder_svg_arc_to (builder, - rx, ry, x_axis_rotation, - large_arc, sweep, - x1, y1); - x = x1; - y = y1; - } - else - goto error; - } - break; - - default: - goto error; - } - - after_comma = (p > string) && p[-1] == ','; - } - - if (after_comma) - goto error; - - return gsk_path_builder_free_to_path (builder); - -error: - //g_warning ("Can't parse string '%s' as GskPath, error at %ld", string, p - string); - gsk_path_builder_unref (builder); - - return NULL; -} - /* }}} */ /* vim:set foldmethod=marker expandtab: */ diff --git a/gsk/gskpathparser.c b/gsk/gskpathparser.c new file mode 100644 index 0000000000..93cf865a22 --- /dev/null +++ b/gsk/gskpathparser.c @@ -0,0 +1,823 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "gskpathprivate.h" + +#include "gskcurveprivate.h" +#include "gskpathbuilder.h" +#include "gskpathpoint.h" +#include "gskcontourprivate.h" + +static void +skip_whitespace (const char **p) +{ + while (g_ascii_isspace (**p)) + (*p)++; +} + +static void +skip_optional_comma (const char **p) +{ + skip_whitespace (p); + if (**p == ',') + (*p)++; +} + +static gboolean +parse_number (const char **p, + double *c) +{ + char *e; + *c = g_ascii_strtod (*p, &e); + if (e == *p) + return FALSE; + *p = e; + skip_optional_comma (p); + return TRUE; +} + +static gboolean +parse_coordinate (const char **p, + double *c) +{ + return parse_number (p, c); +} + +static gboolean +parse_coordinate_pair (const char **p, + double *x, + double *y) +{ + double xx, yy; + const char *o = *p; + + if (!parse_coordinate (p, &xx)) + { + *p = o; + return FALSE; + } + if (!parse_coordinate (p, &yy)) + { + *p = o; + return FALSE; + } + + *x = xx; + *y = yy; + + return TRUE; +} + +static gboolean +parse_nonnegative_number (const char **p, + double *x) +{ + const char *o = *p; + double n; + + if (!parse_number (p, &n)) + return FALSE; + + if (n < 0) + { + *p = o; + return FALSE; + } + + *x = n; + + return TRUE; +} + +/* This fixes a flaw in our use of strchr() below: + * + * If p already points at the end of the string, + * we misinterpret strchr ("xyz", *p) returning + * non-NULL to mean that we can increment p. + * + * But strchr() will return a pointer to the + * final NUL byte in this case, and we walk off + * the end of the string. Oops + */ +static inline char * +_strchr (const char *str, + int c) +{ + if (c == 0) + return NULL; + else + return strchr (str, c); +} + +static gboolean +parse_flag (const char **p, + gboolean *f) +{ + skip_whitespace (p); + if (_strchr ("01", **p)) + { + *f = **p == '1'; + (*p)++; + skip_optional_comma (p); + return TRUE; + } + + return FALSE; +} + +static gboolean +parse_command (const char **p, + char *cmd) +{ + char *s; + const char *allowed; + + if (*cmd == 'X') + allowed = "mM"; + else + allowed = "mMhHvVzZlLcCsStTqQaAoO"; + + skip_whitespace (p); + s = _strchr (allowed, **p); + if (s) + { + *cmd = *s; + (*p)++; + return TRUE; + } + return FALSE; +} + +static gboolean +parse_string (const char **p, + const char *s) +{ + int len = strlen (s); + if (strncmp (*p, s, len) != 0) + return FALSE; + (*p) += len; + return TRUE; +} + +#define NEAR(x, y) (fabs ((x) - (y)) < 0.001) + +static gboolean +is_rect (double x0, double y0, + double x1, double y1, + double x2, double y2, + double x3, double y3) +{ + return NEAR (x0, x3) && NEAR (x1, x2) && + NEAR (y0, y1) && NEAR (y2, y3) && + x0 < x1 && y1 < y2; +} + +static gboolean +is_line (double x0, double y0, + double x1, double y1, + double x2, double y2, + double x3, double y3) +{ + if (NEAR (y0, y3)) + return x0 <= x1 && x1 <= x2 && x2 <= x3 && + NEAR (y0, y1) && NEAR (y0, y2) && NEAR (y0, y3); + else + return y0 <= y1 && y1 <= y2 && y2 <= y3 && + NEAR (x0, x1) && NEAR (x0, x2) && NEAR (x0, x3); +} + +static gboolean +parse_rectangle (const char **p, + double *x, + double *y, + double *w, + double *h) +{ + const char *o = *p; + double w2; + + if (parse_coordinate_pair (p, x, y) && + parse_string (p, "h") && + parse_coordinate (p, w) && + parse_string (p, "v") && + parse_coordinate (p, h) && + parse_string (p, "h") && + parse_coordinate (p, &w2) && + parse_string (p, "z") && + w2 == -*w && *w >= 0 && *h >= 0) + { + skip_whitespace (p); + + return TRUE; + } + + *p = o; + return FALSE; +} + +static gboolean +parse_circle (const char **p, + double *cx, + double *cy, + double *r) +{ + const char *o = *p; + double x0, y0, x1, y1, x2, y2, x3, y3; + double x4, y4, x5, y5, x6, y6, x7, y7; + double x8, y8, w0, w1, w2, w3; + double rr; + + if (parse_coordinate_pair (p, &x0, &y0) && + parse_string (p, "o") && + parse_coordinate_pair (p, &x1, &y1) && + parse_coordinate_pair (p, &x2, &y2) && + parse_nonnegative_number (p, &w0) && + parse_string (p, "o") && + parse_coordinate_pair (p, &x3, &y3) && + parse_coordinate_pair (p, &x4, &y4) && + parse_nonnegative_number (p, &w1) && + parse_string (p, "o") && + parse_coordinate_pair (p, &x5, &y5) && + parse_coordinate_pair (p, &x6, &y6) && + parse_nonnegative_number (p, &w2) && + parse_string (p, "o") && + parse_coordinate_pair (p, &x7, &y7) && + parse_coordinate_pair (p, &x8, &y8) && + parse_nonnegative_number (p, &w3) && + parse_string (p, "z")) + { + rr = y1; + + if (x1 == 0 && y1 == rr && + x2 == -rr && y2 == rr && + x3 == -rr && y3 == 0 && + x4 == -rr && y4 == -rr && + x5 == 0 && y5 == -rr && + x6 == rr && y6 == -rr && + x7 == rr && y7 == 0 && + x8 == rr && y8 == rr && + NEAR (w0, M_SQRT1_2) && NEAR (w1, M_SQRT1_2) && + NEAR (w2, M_SQRT1_2) && NEAR (w3, M_SQRT1_2)) + { + *cx = x0 - rr; + *cy = y0; + *r = rr; + + skip_whitespace (p); + + return TRUE; + } + } + + *p = o; + return FALSE; +} + +static gboolean +parse_rounded_rect (const char **p, + GskRoundedRect *rr) +{ + const char *o = *p; + double x0, y0, x1, y1, x2, y2, x3, y3; + double x4, y4, x5, y5, x6, y6, x7, y7; + double x8, y8, x9, y9, x10, y10, x11, y11; + double x12, y12, w0, w1, w2, w3; + + if (parse_coordinate_pair (p, &x0, &y0) && + parse_string (p, "L") && + parse_coordinate_pair (p, &x1, &y1) && + parse_string (p, "O") && + parse_coordinate_pair (p, &x2, &y2) && + parse_coordinate_pair (p, &x3, &y3) && + parse_nonnegative_number (p, &w0) && + parse_string (p, "L") && + parse_coordinate_pair (p, &x4, &y4) && + parse_string (p, "O") && + parse_coordinate_pair (p, &x5, &y5) && + parse_coordinate_pair (p, &x6, &y6) && + parse_nonnegative_number (p, &w1) && + parse_string (p, "L") && + parse_coordinate_pair (p, &x7, &y7) && + parse_string (p, "O") && + parse_coordinate_pair (p, &x8, &y8) && + parse_coordinate_pair (p, &x9, &y9) && + parse_nonnegative_number (p, &w2) && + parse_string (p, "L") && + parse_coordinate_pair (p, &x10, &y10) && + parse_string (p, "O") && + parse_coordinate_pair (p, &x11, &y11) && + parse_coordinate_pair (p, &x12, &y12) && + parse_nonnegative_number (p, &w3) && + parse_string (p, "Z")) + { + if (NEAR (x0, x12) && NEAR (y0, y12) && + is_rect (x11, y11, x2, y2, x5, y5, x8, y8) && + is_line (x11, y11, x0, y0, x1, y1, x2, y2) && + is_line (x2, y2, x3, y3, x4, y4, x5, y5) && + is_line (x8, y8, x7, y7, x6, y6, x5, y5) && + is_line (x11, y11, x10, y10, x9, y9, x8, y8) && + NEAR (w0, M_SQRT1_2) && NEAR (w1, M_SQRT1_2) && + NEAR (w2, M_SQRT1_2) && NEAR (w3, M_SQRT1_2)) + { + rr->bounds = GRAPHENE_RECT_INIT (x11, y11, x5 - x11, y5 - y11); + rr->corner[GSK_CORNER_TOP_LEFT] = GRAPHENE_SIZE_INIT (x12 - x11, y10 - y11); + rr->corner[GSK_CORNER_TOP_RIGHT] = GRAPHENE_SIZE_INIT (x2 - x1, y3 - y2); + rr->corner[GSK_CORNER_BOTTOM_RIGHT] = GRAPHENE_SIZE_INIT (x5 - x6, y5 - y4); + rr->corner[GSK_CORNER_BOTTOM_LEFT] = GRAPHENE_SIZE_INIT (x7 - x8, y8 - y7); + + skip_whitespace (p); + + return TRUE; + } + } + + *p = o; + return FALSE; +} + +#undef NEAR + +/** + * gsk_path_parse: + * @string: a string + * + * This is a convenience function that constructs a `GskPath` + * from a serialized form. + * + * The string is expected to be in (a superset of) + * [SVG path syntax](https://www.w3.org/TR/SVG11/paths.html#PathData), + * as e.g. produced by [method@Gsk.Path.to_string]. + * + * A high-level summary of the syntax: + * + * - `M x y` Move to `(x, y)` + * - `L x y` Add a line from the current point to `(x, y)` + * - `Q x1 y1 x2 y2` Add a quadratic Bézier from the current point to `(x2, y2)`, with control point `(x1, y1)` + * - `C x1 y1 x2 y2 x3 y3` Add a cubic Bézier from the current point to `(x3, y3)`, with control points `(x1, y1)` and `(x2, y2)` + * - `Z` Close the contour by drawing a line back to the start point + * - `H x` Add a horizontal line from the current point to the given x value + * - `V y` Add a vertical line from the current point to the given y value + * - `T x2 y2` Add a quadratic Bézier, using the reflection of the previous segments' control point as control point + * - `S x2 y2 x3 y3` Add a cubic Bézier, using the reflection of the previous segments' second control point as first control point + * - `A rx ry r l s x y` Add an elliptical arc from the current point to `(x, y)` with radii rx and ry. See the SVG documentation for how the other parameters influence the arc. + * - `O x1 y1 x2 y2 w` Add a rational quadratic Bézier from the current point to `(x2, y2)` with control point `(x1, y1)` and weight `w`. + * + * All the commands have lowercase variants that interpret coordinates + * relative to the current point. + * + * The `O` command is an extension that is not supported in SVG. + * + * Returns: (nullable): a new `GskPath`, or `NULL` if @string could not be parsed + * + * Since: 4.14 + */ +GskPath * +gsk_path_parse (const char *string) +{ + GskPathBuilder *builder; + double x, y; + double prev_x1, prev_y1; + double path_x, path_y; + const char *p; + char cmd; + char prev_cmd; + gboolean after_comma; + gboolean repeat; + + builder = gsk_path_builder_new (); + + cmd = 'X'; + path_x = path_y = 0; + x = y = 0; + prev_x1 = prev_y1 = 0; + after_comma = FALSE; + + p = string; + while (*p) + { + prev_cmd = cmd; + repeat = !parse_command (&p, &cmd); + + if (after_comma && !repeat) + goto error; + + switch (cmd) + { + case 'X': + goto error; + + case 'Z': + case 'z': + if (repeat) + goto error; + else + { + gsk_path_builder_close (builder); + x = path_x; + y = path_y; + } + break; + + case 'M': + case 'm': + { + double x1, y1, w, h, r; + GskRoundedRect rr; + + /* Look for special contours */ + if (parse_rectangle (&p, &x1, &y1, &w, &h)) + { + gsk_path_builder_add_rect (builder, &GRAPHENE_RECT_INIT (x1, y1, w, h)); + if (_strchr ("zZX", prev_cmd)) + { + path_x = x1; + path_y = y1; + } + + x = x1; + y = y1; + } + else if (parse_circle (&p, &x1, &y1, &r)) + { + gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (x1, y1), r); + + if (_strchr ("zZX", prev_cmd)) + { + path_x = x1 + r; + path_y = y1; + } + + x = x1 + r; + y = y1; + } + else if (parse_rounded_rect (&p, &rr)) + { + gsk_path_builder_add_rounded_rect (builder, &rr); + + if (_strchr ("zZX", prev_cmd)) + { + path_x = rr.bounds.origin.x + rr.corner[GSK_CORNER_TOP_LEFT].width; + path_y = rr.bounds.origin.y; + } + + x = rr.bounds.origin.x + rr.corner[GSK_CORNER_TOP_LEFT].width; + y = rr.bounds.origin.y; + } + else if (parse_coordinate_pair (&p, &x1, &y1)) + { + if (cmd == 'm') + { + x1 += x; + y1 += y; + } + + if (repeat) + gsk_path_builder_line_to (builder, x1, y1); + else + { + gsk_path_builder_move_to (builder, x1, y1); + if (_strchr ("zZX", prev_cmd)) + { + path_x = x1; + path_y = y1; + } + } + + x = x1; + y = y1; + } + else + goto error; + } + break; + + case 'L': + case 'l': + { + double x1, y1; + + if (parse_coordinate_pair (&p, &x1, &y1)) + { + if (cmd == 'l') + { + x1 += x; + y1 += y; + } + + if (_strchr ("zZ", prev_cmd)) + { + gsk_path_builder_move_to (builder, x, y); + path_x = x; + path_y = y; + } + gsk_path_builder_line_to (builder, x1, y1); + x = x1; + y = y1; + } + else + goto error; + } + break; + + case 'H': + case 'h': + { + double x1; + + if (parse_coordinate (&p, &x1)) + { + if (cmd == 'h') + x1 += x; + if (_strchr ("zZ", prev_cmd)) + { + gsk_path_builder_move_to (builder, x, y); + path_x = x; + path_y = y; + } + gsk_path_builder_line_to (builder, x1, y); + x = x1; + } + else + goto error; + } + break; + + case 'V': + case 'v': + { + double y1; + + if (parse_coordinate (&p, &y1)) + { + if (cmd == 'v') + y1 += y; + if (_strchr ("zZ", prev_cmd)) + { + gsk_path_builder_move_to (builder, x, y); + path_x = x; + path_y = y; + } + gsk_path_builder_line_to (builder, x, y1); + y = y1; + } + else + goto error; + } + break; + + case 'C': + case 'c': + { + double x0, y0, x1, y1, x2, y2; + + if (parse_coordinate_pair (&p, &x0, &y0) && + parse_coordinate_pair (&p, &x1, &y1) && + parse_coordinate_pair (&p, &x2, &y2)) + { + if (cmd == 'c') + { + x0 += x; + y0 += y; + x1 += x; + y1 += y; + x2 += x; + y2 += y; + } + if (_strchr ("zZ", prev_cmd)) + { + gsk_path_builder_move_to (builder, x, y); + path_x = x; + path_y = y; + } + gsk_path_builder_cubic_to (builder, x0, y0, x1, y1, x2, y2); + prev_x1 = x1; + prev_y1 = y1; + x = x2; + y = y2; + } + else + goto error; + } + break; + + case 'S': + case 's': + { + double x0, y0, x1, y1, x2, y2; + + if (parse_coordinate_pair (&p, &x1, &y1) && + parse_coordinate_pair (&p, &x2, &y2)) + { + if (cmd == 's') + { + x1 += x; + y1 += y; + x2 += x; + y2 += y; + } + if (_strchr ("CcSs", prev_cmd)) + { + x0 = 2 * x - prev_x1; + y0 = 2 * y - prev_y1; + } + else + { + x0 = x; + y0 = y; + } + if (_strchr ("zZ", prev_cmd)) + { + gsk_path_builder_move_to (builder, x, y); + path_x = x; + path_y = y; + } + gsk_path_builder_cubic_to (builder, x0, y0, x1, y1, x2, y2); + prev_x1 = x1; + prev_y1 = y1; + x = x2; + y = y2; + } + else + goto error; + } + break; + + case 'Q': + case 'q': + { + double x1, y1, x2, y2; + + if (parse_coordinate_pair (&p, &x1, &y1) && + parse_coordinate_pair (&p, &x2, &y2)) + { + if (cmd == 'q') + { + x1 += x; + y1 += y; + x2 += x; + y2 += y; + } + if (_strchr ("zZ", prev_cmd)) + { + gsk_path_builder_move_to (builder, x, y); + path_x = x; + path_y = y; + } + gsk_path_builder_quad_to (builder, x1, y1, x2, y2); + prev_x1 = x1; + prev_y1 = y1; + x = x2; + y = y2; + } + else + goto error; + } + break; + + case 'T': + case 't': + { + double x1, y1, x2, y2; + + if (parse_coordinate_pair (&p, &x2, &y2)) + { + if (cmd == 't') + { + x2 += x; + y2 += y; + } + if (_strchr ("QqTt", prev_cmd)) + { + x1 = 2 * x - prev_x1; + y1 = 2 * y - prev_y1; + } + else + { + x1 = x; + y1 = y; + } + if (_strchr ("zZ", prev_cmd)) + { + gsk_path_builder_move_to (builder, x, y); + path_x = x; + path_y = y; + } + gsk_path_builder_quad_to (builder, x1, y1, x2, y2); + prev_x1 = x1; + prev_y1 = y1; + x = x2; + y = y2; + } + else + goto error; + } + break; + + case 'O': + case 'o': + { + double x1, y1, x2, y2, weight; + + if (parse_coordinate_pair (&p, &x1, &y1) && + parse_coordinate_pair (&p, &x2, &y2) && + parse_nonnegative_number (&p, &weight)) + { + if (cmd == 'o') + { + x1 += x; + y1 += y; + x2 += x; + y2 += y; + } + if (_strchr ("zZ", prev_cmd)) + { + gsk_path_builder_move_to (builder, x, y); + path_x = x; + path_y = y; + } + gsk_path_builder_conic_to (builder, x1, y1, x2, y2, weight); + x = x2; + y = y2; + } + else + goto error; + } + break; + + case 'A': + case 'a': + { + double rx, ry; + double x_axis_rotation; + int large_arc, sweep; + double x1, y1; + + if (parse_nonnegative_number (&p, &rx) && + parse_nonnegative_number (&p, &ry) && + parse_number (&p, &x_axis_rotation) && + parse_flag (&p, &large_arc) && + parse_flag (&p, &sweep) && + parse_coordinate_pair (&p, &x1, &y1)) + { + if (cmd == 'a') + { + x1 += x; + y1 += y; + } + + if (_strchr ("zZ", prev_cmd)) + { + gsk_path_builder_move_to (builder, x, y); + path_x = x; + path_y = y; + } + gsk_path_builder_svg_arc_to (builder, + rx, ry, x_axis_rotation, + large_arc, sweep, + x1, y1); + x = x1; + y = y1; + } + else + goto error; + } + break; + + default: + goto error; + } + + after_comma = (p > string) && p[-1] == ','; + } + + if (after_comma) + goto error; + + return gsk_path_builder_free_to_path (builder); + +error: + //g_warning ("Can't parse string '%s' as GskPath, error at %ld", string, p - string); + gsk_path_builder_unref (builder); + + return NULL; +} + +/* vim:set foldmethod=marker expandtab: */ diff --git a/gsk/meson.build b/gsk/meson.build index 8cb796fd2b..d47621c7c5 100644 --- a/gsk/meson.build +++ b/gsk/meson.build @@ -29,6 +29,7 @@ gsk_public_sources = files([ 'gskpath.c', 'gskpathbuilder.c', 'gskpathmeasure.c', + 'gskpathparser.c', 'gskpathpoint.c', 'gskrenderer.c', 'gskrendernode.c',