Implement gsk_contour_default_add_stroke, which takes a contour and stroke parameters, and adds contours to a path builder for the outline that woul be produced by stroking the path with these parameters. The current implementation does not try to handle short segments in the vicinity of sharp joins in any special way, so there can be some artifacts in that situation.
1267 lines
31 KiB
C
1267 lines
31 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 "gskstrokeprivate.h"
|
|
|
|
#include "gdk/gdk-private.h"
|
|
|
|
/**
|
|
* SECTION:gskpath
|
|
* @Title: Path
|
|
* @Short_description: Lines and Curves
|
|
* @See_also: #GskRenderNode, #GskPathBuilder
|
|
*
|
|
* This section describes the #GskPath structure that is used to
|
|
* describe lines and curves that are more complex than simple rectangles.
|
|
*
|
|
* #GskPath is an immutable struct. After creation, you cannot change
|
|
* the types it represents. Instead, new #GskPath have to be created.
|
|
* The #GskPathBuilder structure is meant to help in this endeavor.
|
|
*/
|
|
|
|
struct _GskPath
|
|
{
|
|
/*< private >*/
|
|
guint ref_count;
|
|
|
|
GskPathFlags flags;
|
|
|
|
gsize n_contours;
|
|
GskContour *contours[];
|
|
/* followed by the contours data */
|
|
};
|
|
|
|
/**
|
|
* GskPath:
|
|
*
|
|
* A #GskPath struct is a reference counted struct
|
|
* and should be treated as opaque.
|
|
*/
|
|
|
|
G_DEFINE_BOXED_TYPE (GskPath, gsk_path,
|
|
gsk_path_ref,
|
|
gsk_path_unref)
|
|
|
|
GskPath *
|
|
gsk_path_new_from_contours (const GSList *contours)
|
|
{
|
|
GskPath *path;
|
|
const GSList *l;
|
|
gsize size;
|
|
gsize n_contours;
|
|
guint8 *contour_data;
|
|
GskPathFlags flags;
|
|
|
|
flags = GSK_PATH_CLOSED | GSK_PATH_FLAT;
|
|
size = 0;
|
|
n_contours = 0;
|
|
for (l = contours; l; l = l->next)
|
|
{
|
|
GskContour *contour = l->data;
|
|
|
|
n_contours++;
|
|
size += sizeof (GskContour *);
|
|
size += gsk_contour_get_size (contour);
|
|
flags &= gsk_contour_get_flags (contour);
|
|
}
|
|
|
|
path = g_malloc0 (sizeof (GskPath) + size);
|
|
path->ref_count = 1;
|
|
path->flags = flags;
|
|
path->n_contours = n_contours;
|
|
contour_data = (guint8 *) &path->contours[n_contours];
|
|
n_contours = 0;
|
|
|
|
for (l = contours; l; l = l->next)
|
|
{
|
|
GskContour *contour = l->data;
|
|
|
|
path->contours[n_contours] = (GskContour *) contour_data;
|
|
gsk_contour_copy ((GskContour *) contour_data, contour);
|
|
size = gsk_contour_get_size (contour);
|
|
contour_data += size;
|
|
n_contours++;
|
|
}
|
|
|
|
return path;
|
|
}
|
|
|
|
/**
|
|
* gsk_path_new_from_cairo:
|
|
* @path: a Cairo path
|
|
*
|
|
* This is a convenience function that constructs a #GskPath from a Cairo path.
|
|
*
|
|
* You can use cairo_copy_path() to access the path from a Cairo context.
|
|
*
|
|
* Returns: a new #GskPath
|
|
**/
|
|
GskPath *
|
|
gsk_path_new_from_cairo (const cairo_path_t *path)
|
|
{
|
|
GskPathBuilder *builder;
|
|
gsize i;
|
|
|
|
g_return_val_if_fail (path != NULL, NULL);
|
|
|
|
builder = gsk_path_builder_new ();
|
|
|
|
for (i = 0; i < path->num_data; i += path->data[i].header.length)
|
|
{
|
|
const cairo_path_data_t *data = &path->data[i];
|
|
|
|
switch (data->header.type)
|
|
{
|
|
case CAIRO_PATH_MOVE_TO:
|
|
gsk_path_builder_move_to (builder, data[1].point.x, data[1].point.y);
|
|
break;
|
|
|
|
case CAIRO_PATH_LINE_TO:
|
|
gsk_path_builder_line_to (builder, data[1].point.x, data[1].point.y);
|
|
break;
|
|
|
|
case CAIRO_PATH_CURVE_TO:
|
|
gsk_path_builder_curve_to (builder,
|
|
data[1].point.x, data[1].point.y,
|
|
data[2].point.x, data[2].point.y,
|
|
data[3].point.x, data[3].point.y);
|
|
break;
|
|
|
|
case CAIRO_PATH_CLOSE_PATH:
|
|
gsk_path_builder_close (builder);
|
|
break;
|
|
|
|
default:
|
|
g_assert_not_reached ();
|
|
break;
|
|
}
|
|
}
|
|
|
|
return gsk_path_builder_free_to_path (builder);
|
|
}
|
|
|
|
/**
|
|
* gsk_path_ref:
|
|
* @self: a #GskPath
|
|
*
|
|
* Increases the reference count of a #GskPath by one.
|
|
*
|
|
* Returns: the passed in #GskPath.
|
|
**/
|
|
GskPath *
|
|
gsk_path_ref (GskPath *self)
|
|
{
|
|
g_return_val_if_fail (self != NULL, NULL);
|
|
|
|
self->ref_count++;
|
|
|
|
return self;
|
|
}
|
|
|
|
/**
|
|
* gsk_path_unref:
|
|
* @self: a #GskPath
|
|
*
|
|
* Decreases the reference count of a #GskPath by one.
|
|
* If the resulting reference count is zero, frees the path.
|
|
**/
|
|
void
|
|
gsk_path_unref (GskPath *self)
|
|
{
|
|
g_return_if_fail (self != NULL);
|
|
g_return_if_fail (self->ref_count > 0);
|
|
|
|
self->ref_count--;
|
|
if (self->ref_count > 0)
|
|
return;
|
|
|
|
g_free (self);
|
|
}
|
|
|
|
const GskContour *
|
|
gsk_path_get_contour (GskPath *path,
|
|
gsize i)
|
|
{
|
|
return path->contours[i];
|
|
}
|
|
|
|
/**
|
|
* gsk_path_print:
|
|
* @self: a #GskPath
|
|
* @string: The string to print into
|
|
*
|
|
* Converts @self into a human-readable string representation suitable
|
|
* for printing.
|
|
*
|
|
* The string is compatible with
|
|
* [SVG path syntax](https://www.w3.org/TR/SVG11/paths.html#PathData),
|
|
* with the exception that conic curves will generate a string of the
|
|
* form "O x1 y1, x2 y2, w" where x1, y1 is the control point, x2, y2
|
|
* is the end point, and w is the weight.
|
|
**/
|
|
void
|
|
gsk_path_print (GskPath *self,
|
|
GString *string)
|
|
{
|
|
gsize i;
|
|
|
|
g_return_if_fail (self != NULL);
|
|
g_return_if_fail (string != NULL);
|
|
|
|
for (i = 0; i < self->n_contours; i++)
|
|
{
|
|
if (i > 0)
|
|
g_string_append_c (string, ' ');
|
|
gsk_contour_print (self->contours[i], string);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* gsk_path_to_string:
|
|
* @self: a #GskPath
|
|
*
|
|
* Converts the path into a string that is suitable for
|
|
* printing. You can use this function in a debugger to get a quick overview
|
|
* of the path.
|
|
*
|
|
* This is a wrapper around gsk_path_print(), see that function
|
|
* for details.
|
|
*
|
|
* Returns: A new string for @self
|
|
**/
|
|
char *
|
|
gsk_path_to_string (GskPath *self)
|
|
{
|
|
GString *string;
|
|
|
|
g_return_val_if_fail (self != NULL, NULL);
|
|
|
|
string = g_string_new ("");
|
|
|
|
gsk_path_print (self, string);
|
|
|
|
return g_string_free (string, FALSE);
|
|
}
|
|
|
|
static gboolean
|
|
gsk_path_to_cairo_add_op (GskPathOperation op,
|
|
const graphene_point_t *pts,
|
|
gsize n_pts,
|
|
float weight,
|
|
gpointer cr)
|
|
{
|
|
switch (op)
|
|
{
|
|
case GSK_PATH_MOVE:
|
|
cairo_move_to (cr, pts[0].x, pts[0].y);
|
|
break;
|
|
|
|
case GSK_PATH_CLOSE:
|
|
cairo_close_path (cr);
|
|
break;
|
|
|
|
case GSK_PATH_LINE:
|
|
cairo_line_to (cr, pts[1].x, pts[1].y);
|
|
break;
|
|
|
|
case GSK_PATH_CURVE:
|
|
cairo_curve_to (cr, pts[1].x, pts[1].y, pts[2].x, pts[2].y, pts[3].x, pts[3].y);
|
|
break;
|
|
|
|
case GSK_PATH_CONIC:
|
|
default:
|
|
g_assert_not_reached ();
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* gsk_path_to_cairo:
|
|
* @self: a #GskPath
|
|
* @cr: a cairo context
|
|
*
|
|
* Appends the given @path to the given cairo context for drawing
|
|
* with Cairo.
|
|
*
|
|
* This may cause some suboptimal conversions to be performed as Cairo
|
|
* may not support all features of #GskPath.
|
|
*
|
|
* This function does not clear the existing Cairo path. Call
|
|
* cairo_new_path() if you want this.
|
|
**/
|
|
void
|
|
gsk_path_to_cairo (GskPath *self,
|
|
cairo_t *cr)
|
|
{
|
|
g_return_if_fail (self != NULL);
|
|
g_return_if_fail (cr != NULL);
|
|
|
|
gsk_path_foreach_with_tolerance (self,
|
|
GSK_PATH_FOREACH_ALLOW_CURVE,
|
|
cairo_get_tolerance (cr),
|
|
gsk_path_to_cairo_add_op,
|
|
cr);
|
|
}
|
|
|
|
/*
|
|
* gsk_path_get_n_contours:
|
|
* @path: a #GskPath
|
|
*
|
|
* Gets the number of contours @path is composed out of.
|
|
*
|
|
* Returns: the number of contours in @path
|
|
**/
|
|
gsize
|
|
gsk_path_get_n_contours (GskPath *path)
|
|
{
|
|
return path->n_contours;
|
|
}
|
|
|
|
/**
|
|
* gsk_path_is_empty:
|
|
* @path: a #GskPath
|
|
*
|
|
* Checks if the path is empty, i.e. contains no lines or curves.
|
|
*
|
|
* Returns: %TRUE if the path is empty
|
|
**/
|
|
gboolean
|
|
gsk_path_is_empty (GskPath *path)
|
|
{
|
|
g_return_val_if_fail (path != NULL, FALSE);
|
|
|
|
return path->n_contours == 0;
|
|
}
|
|
|
|
/**
|
|
* gsk_path_get_bounds:
|
|
* @self: a #GskPath
|
|
* @bounds: (out) (caller-allocates): the bounds of the given path
|
|
*
|
|
* Computes the bounds of the given path.
|
|
*
|
|
* The returned bounds may be larger than necessary, because this
|
|
* function aims to be fast, not accurate. The bounds are guaranteed
|
|
* to contain the path.
|
|
*
|
|
* It is possible that the returned rectangle has 0 width and/or height.
|
|
* This can happen when the path only describes a point or an
|
|
* axis-aligned line.
|
|
*
|
|
* If the path is empty, %FALSE is returned and @bounds are set to
|
|
* graphene_rect_zero(). This is different from the case where the path
|
|
* is a single point at the origin, where the @bounds will also be set to
|
|
* the zero rectangle but 0 will be returned.
|
|
*
|
|
* Returns: %TRUE if the path has bounds, %FALSE if the path is known
|
|
* to be empty and have no bounds.
|
|
**/
|
|
gboolean
|
|
gsk_path_get_bounds (GskPath *self,
|
|
graphene_rect_t *bounds)
|
|
{
|
|
gsize i;
|
|
|
|
g_return_val_if_fail (self != NULL, FALSE);
|
|
g_return_val_if_fail (bounds != NULL, FALSE);
|
|
|
|
for (i = 0; i < self->n_contours; i++)
|
|
{
|
|
if (gsk_contour_get_bounds (self->contours[i], bounds))
|
|
break;
|
|
}
|
|
|
|
if (i >= self->n_contours)
|
|
{
|
|
graphene_rect_init_from_rect (bounds, graphene_rect_zero ());
|
|
return FALSE;
|
|
}
|
|
|
|
for (i++; i < self->n_contours; i++)
|
|
{
|
|
graphene_rect_t tmp;
|
|
|
|
if (gsk_contour_get_bounds (self->contours[i], &tmp))
|
|
graphene_rect_union (bounds, &tmp, bounds);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* gsk_path_foreach:
|
|
* @self: a #GskPath
|
|
* @flags: flags to pass to the foreach function. See #GskPathForeachFlags for
|
|
* details about flags.
|
|
* @func: (scope call) (closure user_data): the function to call for operations
|
|
* @user_data: (nullable): user data passed to @func
|
|
*
|
|
* Calls @func for every operation of the path. Note that this only approximates
|
|
* @self, because paths can contain optimizations for various specialized contours.
|
|
*
|
|
* Returns: %FALSE if @func returned %FALSE, %TRUE otherwise.
|
|
**/
|
|
gboolean
|
|
gsk_path_foreach (GskPath *self,
|
|
GskPathForeachFlags flags,
|
|
GskPathForeachFunc func,
|
|
gpointer user_data)
|
|
{
|
|
g_return_val_if_fail (self != NULL, FALSE);
|
|
g_return_val_if_fail (func, FALSE);
|
|
|
|
return gsk_path_foreach_with_tolerance (self,
|
|
flags,
|
|
GSK_PATH_TOLERANCE_DEFAULT,
|
|
func,
|
|
user_data);
|
|
}
|
|
|
|
typedef struct _GskPathForeachTrampoline GskPathForeachTrampoline;
|
|
struct _GskPathForeachTrampoline
|
|
{
|
|
GskPathForeachFlags flags;
|
|
double tolerance;
|
|
GskPathForeachFunc func;
|
|
gpointer user_data;
|
|
};
|
|
|
|
static gboolean
|
|
gsk_path_foreach_trampoline_add_line (const graphene_point_t *from,
|
|
const graphene_point_t *to,
|
|
float from_progress,
|
|
float to_progress,
|
|
gpointer data)
|
|
{
|
|
GskPathForeachTrampoline *trampoline = data;
|
|
|
|
return trampoline->func (GSK_PATH_LINE,
|
|
(graphene_point_t[2]) { *from, *to },
|
|
2,
|
|
0.0f,
|
|
trampoline->user_data);
|
|
}
|
|
|
|
static gboolean
|
|
gsk_path_foreach_trampoline_add_curve (const graphene_point_t points[4],
|
|
gpointer data)
|
|
{
|
|
GskPathForeachTrampoline *trampoline = data;
|
|
|
|
return trampoline->func (GSK_PATH_CURVE, points, 4, 0.0f, trampoline->user_data);
|
|
}
|
|
|
|
static gboolean
|
|
gsk_path_foreach_trampoline (GskPathOperation op,
|
|
const graphene_point_t *pts,
|
|
gsize n_pts,
|
|
float weight,
|
|
gpointer data)
|
|
{
|
|
GskPathForeachTrampoline *trampoline = data;
|
|
|
|
switch (op)
|
|
{
|
|
case GSK_PATH_MOVE:
|
|
case GSK_PATH_CLOSE:
|
|
case GSK_PATH_LINE:
|
|
return trampoline->func (op, pts, n_pts, weight, trampoline->user_data);
|
|
|
|
case GSK_PATH_CURVE:
|
|
{
|
|
GskCurve curve;
|
|
|
|
if (trampoline->flags & GSK_PATH_FOREACH_ALLOW_CURVE)
|
|
return trampoline->func (op, pts, n_pts, weight, trampoline->user_data);
|
|
|
|
gsk_curve_init (&curve, gsk_pathop_encode (GSK_PATH_CURVE, pts));
|
|
return gsk_curve_decompose (&curve,
|
|
trampoline->tolerance,
|
|
gsk_path_foreach_trampoline_add_line,
|
|
trampoline);
|
|
}
|
|
|
|
case GSK_PATH_CONIC:
|
|
{
|
|
GskCurve curve;
|
|
|
|
if (trampoline->flags & GSK_PATH_FOREACH_ALLOW_CONIC)
|
|
return trampoline->func (op, pts, n_pts, weight, trampoline->user_data);
|
|
else
|
|
{
|
|
gsk_curve_init (&curve, gsk_pathop_encode (GSK_PATH_CONIC, (graphene_point_t[4]) { pts[0], pts[1], { weight, }, pts[2] } ));
|
|
|
|
if (trampoline->flags & GSK_PATH_FOREACH_ALLOW_CURVE)
|
|
return gsk_curve_decompose_curve (&curve,
|
|
trampoline->tolerance,
|
|
gsk_path_foreach_trampoline_add_curve,
|
|
trampoline);
|
|
else
|
|
return gsk_curve_decompose (&curve,
|
|
trampoline->tolerance,
|
|
gsk_path_foreach_trampoline_add_line,
|
|
trampoline);
|
|
}
|
|
}
|
|
|
|
|
|
default:
|
|
g_assert_not_reached ();
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
gboolean
|
|
gsk_path_foreach_with_tolerance (GskPath *self,
|
|
GskPathForeachFlags flags,
|
|
double tolerance,
|
|
GskPathForeachFunc func,
|
|
gpointer user_data)
|
|
{
|
|
GskPathForeachTrampoline trampoline;
|
|
gsize i;
|
|
|
|
/* If we need to massage the data, set up a trampoline here */
|
|
if (flags != (GSK_PATH_FOREACH_ALLOW_CURVE | GSK_PATH_FOREACH_ALLOW_CONIC))
|
|
{
|
|
trampoline = (GskPathForeachTrampoline) { flags, tolerance, func, user_data };
|
|
func = gsk_path_foreach_trampoline;
|
|
user_data = &trampoline;
|
|
}
|
|
|
|
for (i = 0; i < self->n_contours; i++)
|
|
{
|
|
if (!gsk_contour_foreach (self->contours[i], tolerance, func, user_data))
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* path 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;
|
|
}
|
|
|
|
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 = "mMhHvVzZlLcCsStTqQoOaA";
|
|
|
|
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;
|
|
}
|
|
|
|
static gboolean
|
|
parse_rectangle (const char **p,
|
|
double *x,
|
|
double *y,
|
|
double *w,
|
|
double *h)
|
|
{
|
|
const char *o = *p;
|
|
double w2;
|
|
|
|
/* Check for M%g,%gh%gv%gh%gz without any intervening whitespace */
|
|
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)
|
|
{
|
|
skip_whitespace (p);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
*p = o;
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
parse_circle (const char **p,
|
|
double *sx,
|
|
double *sy,
|
|
double *r)
|
|
{
|
|
const char *o = *p;
|
|
double r1, r2, r3, mx, my, ex, ey;
|
|
|
|
/* Check for M%g,%gA%g,%g,0,1,0,%g,%gA%g,%g,0,1,0,%g,%g
|
|
* without any intervening whitespace
|
|
*/
|
|
if (parse_coordinate_pair (p, sx, sy) &&
|
|
parse_string (p, "A") &&
|
|
parse_coordinate_pair (p, r, &r1) &&
|
|
parse_string (p, "0 0 0") &&
|
|
parse_coordinate_pair (p, &mx, &my) &&
|
|
parse_string (p, "A") &&
|
|
parse_coordinate_pair (p, &r2, &r3) &&
|
|
parse_string (p, "0 0 0") &&
|
|
parse_coordinate_pair (p, &ex, &ey) &&
|
|
parse_string (p, "z") &&
|
|
*r == r1 && r1 == r2 && r2 == r3 &&
|
|
*sx == ex && *sy == ey)
|
|
{
|
|
skip_whitespace (p);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
*p = o;
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
* [SVG path syntax](https://www.w3.org/TR/SVG11/paths.html#PathData),
|
|
* as e.g. produced by gsk_path_to_string().
|
|
*
|
|
* Returns: (nullable): a new #GskPath, or %NULL if @string could not be parsed
|
|
**/
|
|
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;
|
|
|
|
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 - r, y1), r);
|
|
if (strchr ("zZX", prev_cmd))
|
|
{
|
|
path_x = x1;
|
|
path_y = y1;
|
|
}
|
|
x = x1;
|
|
y = y1;
|
|
}
|
|
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_curve_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_curve_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, xx1, yy1, xx2, yy2;
|
|
|
|
if (parse_coordinate_pair (&p, &x1, &y1) &&
|
|
parse_coordinate_pair (&p, &x2, &y2))
|
|
{
|
|
if (cmd == 'q')
|
|
{
|
|
x1 += x;
|
|
y1 += y;
|
|
x2 += x;
|
|
y2 += y;
|
|
}
|
|
xx1 = (x + 2.0 * x1) / 3.0;
|
|
yy1 = (y + 2.0 * y1) / 3.0;
|
|
xx2 = (x2 + 2.0 * x1) / 3.0;
|
|
yy2 = (y2 + 2.0 * y1) / 3.0;
|
|
if (strchr ("zZ", prev_cmd))
|
|
{
|
|
gsk_path_builder_move_to (builder, x, y);
|
|
path_x = x;
|
|
path_y = y;
|
|
}
|
|
gsk_path_builder_curve_to (builder, xx1, yy1, xx2, yy2, 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, xx1, yy1, xx2, yy2;
|
|
|
|
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;
|
|
}
|
|
xx1 = (x + 2.0 * x1) / 3.0;
|
|
yy1 = (y + 2.0 * y1) / 3.0;
|
|
xx2 = (x2 + 2.0 * x1) / 3.0;
|
|
yy2 = (y2 + 2.0 * y1) / 3.0;
|
|
if (strchr ("zZ", prev_cmd))
|
|
{
|
|
gsk_path_builder_move_to (builder, x, y);
|
|
path_x = x;
|
|
path_y = y;
|
|
}
|
|
gsk_path_builder_curve_to (builder, xx1, yy1, xx2, yy2, 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 == 'c')
|
|
{
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* gsk_path_get_stroke_bounds:
|
|
* @self: a #GtkPath
|
|
* @stroke: stroke parameters
|
|
* @bounds: (out) (caller-allocates): the bounds to fill in
|
|
*
|
|
* Computes the bounds for stroking the given path with the
|
|
* parameters in @stroke.
|
|
*
|
|
* The returned bounds may be larger than necessary, because this
|
|
* function aims to be fast, not accurate. The bounds are guaranteed
|
|
* to contain the area affected by the stroke, including protrusions
|
|
* like miters.
|
|
*
|
|
* Returns: %TRUE if the path has bounds, %FALSE if the path is known
|
|
* to be empty and have no bounds.
|
|
*/
|
|
gboolean
|
|
gsk_path_get_stroke_bounds (GskPath *self,
|
|
const GskStroke *stroke,
|
|
graphene_rect_t *bounds)
|
|
{
|
|
gsize i;
|
|
|
|
g_return_val_if_fail (self != NULL, FALSE);
|
|
g_return_val_if_fail (bounds != NULL, FALSE);
|
|
|
|
for (i = 0; i < self->n_contours; i++)
|
|
{
|
|
if (gsk_contour_get_stroke_bounds (self->contours[i], stroke, bounds))
|
|
break;
|
|
}
|
|
|
|
if (i >= self->n_contours)
|
|
{
|
|
graphene_rect_init_from_rect (bounds, graphene_rect_zero ());
|
|
return FALSE;
|
|
}
|
|
|
|
for (i++; i < self->n_contours; i++)
|
|
{
|
|
graphene_rect_t tmp;
|
|
|
|
if (gsk_contour_get_stroke_bounds (self->contours[i], stroke, &tmp))
|
|
graphene_rect_union (bounds, &tmp, bounds);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* gsk_path_stroke:
|
|
* @self: a #GskPath
|
|
* @stroke: stroke parameters
|
|
*
|
|
* Create a new path that follows the outline of the area
|
|
* that would be affected by stroking along @self with
|
|
* the given stroke parameters.
|
|
*
|
|
* Returns: a new #GskPath
|
|
*/
|
|
GskPath *
|
|
gsk_path_stroke (GskPath *self,
|
|
GskStroke *stroke)
|
|
{
|
|
GskPathBuilder *builder;
|
|
|
|
builder = gsk_path_builder_new ();
|
|
|
|
for (int i = 0; i < self->n_contours; i++)
|
|
gsk_contour_add_stroke (gsk_path_get_contour (self, i), builder, stroke);
|
|
|
|
return gsk_path_builder_free_to_path (builder);
|
|
}
|