mirror of
https://gitlab.gnome.org/GNOME/gtk.git
synced 2024-12-25 21:21:21 +00:00
f67c57b1f8
We were messing up the bottom left corner. Test included.
824 lines
21 KiB
C
824 lines
21 KiB
C
/*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*
|
|
* Authors: Benjamin Otte <otte@gnome.org>
|
|
*/
|
|
|
|
#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 - y9);
|
|
|
|
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: */
|