summaryrefslogtreecommitdiffstats
path: root/app/vectors/gimpvectors-import.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--app/vectors/gimpvectors-import.c1784
1 files changed, 1784 insertions, 0 deletions
diff --git a/app/vectors/gimpvectors-import.c b/app/vectors/gimpvectors-import.c
new file mode 100644
index 0000000..fa631ee
--- /dev/null
+++ b/app/vectors/gimpvectors-import.c
@@ -0,0 +1,1784 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * GimpVectors Import
+ * Copyright (C) 2003-2004 Sven Neumann <sven@gimp.org>
+ *
+ * Some code here is based on code from librsvg that was originally
+ * written by Raph Levien <raph@artofcode.com> for Gill.
+ *
+ * This SVG path importer implements a subset of SVG that is
+ * sufficient to parse path elements and basic shapes and to apply
+ * transformations as described by the SVG specification:
+ * http://www.w3.org/TR/SVG/. It must handle the SVG files exported
+ * by GIMP but it is also supposed to be able to extract paths and
+ * shapes from foreign SVG documents.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <errno.h>
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "vectors-types.h"
+
+#include "config/gimpxmlparser.h"
+
+#include "core/gimperror.h"
+#include "core/gimpimage.h"
+#include "core/gimpimage-undo.h"
+
+#include "gimpbezierstroke.h"
+#include "gimpstroke.h"
+#include "gimpvectors.h"
+#include "gimpvectors-import.h"
+
+#include "gimp-intl.h"
+
+
+#define COORDS_INIT \
+ { \
+ .x = 0.0, \
+ .y = 0.0, \
+ .pressure = 1.0, \
+ .xtilt = 0.0, \
+ .ytilt = 0.0, \
+ .wheel = 0.5, \
+ .velocity = 0.0, \
+ .direction = 0.0 \
+ }
+
+
+typedef struct
+{
+ GQueue *stack;
+ GimpImage *image;
+ gboolean scale;
+ gint svg_depth;
+} SvgParser;
+
+
+typedef struct _SvgHandler SvgHandler;
+
+struct _SvgHandler
+{
+ const gchar *name;
+
+ void (* start) (SvgHandler *handler,
+ const gchar **names,
+ const gchar **values,
+ SvgParser *parser);
+ void (* end) (SvgHandler *handler,
+ SvgParser *parser);
+
+ gdouble width;
+ gdouble height;
+ gchar *id;
+ GList *paths;
+ GimpMatrix3 *transform;
+};
+
+
+typedef struct
+{
+ gchar *id;
+ GList *strokes;
+} SvgPath;
+
+
+static gboolean gimp_vectors_import (GimpImage *image,
+ GFile *file,
+ const gchar *str,
+ gsize len,
+ gboolean merge,
+ gboolean scale,
+ GimpVectors *parent,
+ gint position,
+ GList **ret_vectors,
+ GError **error);
+
+static void svg_parser_start_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ gpointer user_data,
+ GError **error);
+static void svg_parser_end_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ gpointer user_data,
+ GError **error);
+
+static const GMarkupParser markup_parser =
+{
+ svg_parser_start_element,
+ svg_parser_end_element,
+ NULL, /* characters */
+ NULL, /* passthrough */
+ NULL /* error */
+};
+
+
+static void svg_handler_svg_start (SvgHandler *handler,
+ const gchar **names,
+ const gchar **values,
+ SvgParser *parser);
+static void svg_handler_svg_end (SvgHandler *handler,
+ SvgParser *parser);
+static void svg_handler_group_start (SvgHandler *handler,
+ const gchar **names,
+ const gchar **values,
+ SvgParser *parser);
+static void svg_handler_path_start (SvgHandler *handler,
+ const gchar **names,
+ const gchar **values,
+ SvgParser *parser);
+static void svg_handler_rect_start (SvgHandler *handler,
+ const gchar **names,
+ const gchar **values,
+ SvgParser *parser);
+static void svg_handler_ellipse_start (SvgHandler *handler,
+ const gchar **names,
+ const gchar **values,
+ SvgParser *parser);
+static void svg_handler_line_start (SvgHandler *handler,
+ const gchar **names,
+ const gchar **values,
+ SvgParser *parser);
+static void svg_handler_poly_start (SvgHandler *handler,
+ const gchar **names,
+ const gchar **values,
+ SvgParser *parser);
+
+static const SvgHandler svg_handlers[] =
+{
+ { "svg", svg_handler_svg_start, svg_handler_svg_end },
+ { "g", svg_handler_group_start, NULL },
+ { "path", svg_handler_path_start, NULL },
+ { "rect", svg_handler_rect_start, NULL },
+ { "circle", svg_handler_ellipse_start, NULL },
+ { "ellipse", svg_handler_ellipse_start, NULL },
+ { "line", svg_handler_line_start, NULL },
+ { "polyline", svg_handler_poly_start, NULL },
+ { "polygon", svg_handler_poly_start, NULL }
+};
+
+
+static gboolean parse_svg_length (const gchar *value,
+ gdouble reference,
+ gdouble resolution,
+ gdouble *length);
+static gboolean parse_svg_viewbox (const gchar *value,
+ gdouble *width,
+ gdouble *height,
+ GimpMatrix3 *matrix);
+static gboolean parse_svg_transform (const gchar *value,
+ GimpMatrix3 *matrix);
+static GList * parse_path_data (const gchar *data);
+
+
+/**
+ * gimp_vectors_import_file:
+ * @image: the #GimpImage to add the paths to
+ * @file: a SVG file
+ * @merge: should multiple paths be merged into a single #GimpVectors object
+ * @scale: should the SVG be scaled to fit the image dimensions
+ * @position: position in the image's vectors stack where to add the vectors
+ * @error: location to store possible errors
+ *
+ * Imports one or more paths and basic shapes from a SVG file.
+ *
+ * Return value: %TRUE on success, %FALSE if an error occurred
+ **/
+gboolean
+gimp_vectors_import_file (GimpImage *image,
+ GFile *file,
+ gboolean merge,
+ gboolean scale,
+ GimpVectors *parent,
+ gint position,
+ GList **ret_vectors,
+ GError **error)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+ g_return_val_if_fail (G_IS_FILE (file), FALSE);
+ g_return_val_if_fail (parent == NULL ||
+ parent == GIMP_IMAGE_ACTIVE_PARENT ||
+ GIMP_IS_VECTORS (parent), FALSE);
+ g_return_val_if_fail (parent == NULL ||
+ parent == GIMP_IMAGE_ACTIVE_PARENT ||
+ gimp_item_is_attached (GIMP_ITEM (parent)), FALSE);
+ g_return_val_if_fail (parent == NULL ||
+ parent == GIMP_IMAGE_ACTIVE_PARENT ||
+ gimp_item_get_image (GIMP_ITEM (parent)) == image,
+ FALSE);
+ g_return_val_if_fail (parent == NULL ||
+ parent == GIMP_IMAGE_ACTIVE_PARENT ||
+ gimp_viewable_get_children (GIMP_VIEWABLE (parent)),
+ FALSE);
+ g_return_val_if_fail (ret_vectors == NULL || *ret_vectors == NULL, FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ return gimp_vectors_import (image, file, NULL, 0, merge, scale,
+ parent, position,
+ ret_vectors, error);
+}
+
+/**
+ * gimp_vectors_import_string:
+ * @image: the #GimpImage to add the paths to
+ * @buffer: a character buffer to parse
+ * @len: number of bytes in @str or -1 if @str is %NUL-terminated
+ * @merge: should multiple paths be merged into a single #GimpVectors object
+ * @scale: should the SVG be scaled to fit the image dimensions
+ * @error: location to store possible errors
+ *
+ * Imports one or more paths and basic shapes from a SVG file.
+ *
+ * Return value: %TRUE on success, %FALSE if an error occurred
+ **/
+gboolean
+gimp_vectors_import_buffer (GimpImage *image,
+ const gchar *buffer,
+ gsize len,
+ gboolean merge,
+ gboolean scale,
+ GimpVectors *parent,
+ gint position,
+ GList **ret_vectors,
+ GError **error)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+ g_return_val_if_fail (buffer != NULL || len == 0, FALSE);
+ g_return_val_if_fail (parent == NULL ||
+ parent == GIMP_IMAGE_ACTIVE_PARENT ||
+ GIMP_IS_VECTORS (parent), FALSE);
+ g_return_val_if_fail (parent == NULL ||
+ parent == GIMP_IMAGE_ACTIVE_PARENT ||
+ gimp_item_is_attached (GIMP_ITEM (parent)), FALSE);
+ g_return_val_if_fail (parent == NULL ||
+ parent == GIMP_IMAGE_ACTIVE_PARENT ||
+ gimp_item_get_image (GIMP_ITEM (parent)) == image,
+ FALSE);
+ g_return_val_if_fail (parent == NULL ||
+ parent == GIMP_IMAGE_ACTIVE_PARENT ||
+ gimp_viewable_get_children (GIMP_VIEWABLE (parent)),
+ FALSE);
+ g_return_val_if_fail (ret_vectors == NULL || *ret_vectors == NULL, FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ return gimp_vectors_import (image, NULL, buffer, len, merge, scale,
+ parent, position,
+ ret_vectors, error);
+}
+
+static gboolean
+gimp_vectors_import (GimpImage *image,
+ GFile *file,
+ const gchar *str,
+ gsize len,
+ gboolean merge,
+ gboolean scale,
+ GimpVectors *parent,
+ gint position,
+ GList **ret_vectors,
+ GError **error)
+{
+ GimpXmlParser *xml_parser;
+ SvgParser parser;
+ GList *paths;
+ SvgHandler *base;
+ gboolean success = TRUE;
+
+ parser.stack = g_queue_new ();
+ parser.image = image;
+ parser.scale = scale;
+ parser.svg_depth = 0;
+
+ /* the base of the stack, defines the size of the view-port */
+ base = g_slice_new0 (SvgHandler);
+ base->name = "image";
+ base->width = gimp_image_get_width (image);
+ base->height = gimp_image_get_height (image);
+
+ g_queue_push_head (parser.stack, base);
+
+ xml_parser = gimp_xml_parser_new (&markup_parser, &parser);
+
+ if (file)
+ success = gimp_xml_parser_parse_gfile (xml_parser, file, error);
+ else
+ success = gimp_xml_parser_parse_buffer (xml_parser, str, len, error);
+
+ gimp_xml_parser_free (xml_parser);
+
+ if (success)
+ {
+ if (base->paths)
+ {
+ GimpVectors *vectors = NULL;
+
+ base->paths = g_list_reverse (base->paths);
+
+ merge = merge && base->paths->next;
+
+ gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_VECTORS_IMPORT,
+ _("Import Paths"));
+
+ for (paths = base->paths; paths; paths = paths->next)
+ {
+ SvgPath *path = paths->data;
+ GList *list;
+
+ if (! merge || ! vectors)
+ {
+ vectors = gimp_vectors_new (image,
+ ((merge || ! path->id) ?
+ _("Imported Path") : path->id));
+ gimp_image_add_vectors (image, vectors,
+ parent, position, TRUE);
+ gimp_vectors_freeze (vectors);
+
+ if (ret_vectors)
+ *ret_vectors = g_list_prepend (*ret_vectors, vectors);
+
+ if (position != -1)
+ position++;
+ }
+
+ for (list = path->strokes; list; list = list->next)
+ gimp_vectors_stroke_add (vectors, GIMP_STROKE (list->data));
+
+ if (! merge)
+ gimp_vectors_thaw (vectors);
+
+ g_list_free_full (path->strokes, g_object_unref);
+ path->strokes = NULL;
+ }
+
+ if (merge)
+ gimp_vectors_thaw (vectors);
+
+ gimp_image_undo_group_end (image);
+ }
+ else
+ {
+ if (file)
+ g_set_error (error, GIMP_ERROR, GIMP_FAILED,
+ _("No paths found in '%s'"),
+ gimp_file_get_utf8_name (file));
+ else
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ _("No paths found in the buffer"));
+
+ success = FALSE;
+ }
+ }
+ else if (error && *error && file) /* parser reported an error */
+ {
+ gchar *msg = (*error)->message;
+
+ (*error)->message =
+ g_strdup_printf (_("Failed to import paths from '%s': %s"),
+ gimp_file_get_utf8_name (file), msg);
+
+ g_free (msg);
+ }
+
+ while ((base = g_queue_pop_head (parser.stack)) != NULL)
+ {
+ for (paths = base->paths; paths; paths = paths->next)
+ {
+ SvgPath *path = paths->data;
+ GList *list;
+
+ g_free (path->id);
+
+ for (list = path->strokes; list; list = list->next)
+ g_object_unref (list->data);
+
+ g_list_free (path->strokes);
+
+ g_slice_free (SvgPath, path);
+ }
+
+ g_list_free (base->paths);
+
+ g_slice_free (GimpMatrix3, base->transform);
+ g_slice_free (SvgHandler, base);
+ }
+
+ g_queue_free (parser.stack);
+
+ return success;
+}
+
+static void
+svg_parser_start_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ gpointer user_data,
+ GError **error)
+{
+ SvgParser *parser = user_data;
+ SvgHandler *handler;
+ SvgHandler *base;
+ gint i = 0;
+
+ handler = g_slice_new0 (SvgHandler);
+ base = g_queue_peek_head (parser->stack);
+
+ /* if the element is not rendered, always use the generic handler */
+ if (base->width <= 0.0 || base->height <= 0.0)
+ i = G_N_ELEMENTS (svg_handlers);
+
+ for (; i < G_N_ELEMENTS (svg_handlers); i++)
+ if (strcmp (svg_handlers[i].name, element_name) == 0)
+ {
+ handler->name = svg_handlers[i].name;
+ handler->start = svg_handlers[i].start;
+ break;
+ }
+
+ handler->width = base->width;
+ handler->height = base->height;
+
+ g_queue_push_head (parser->stack, handler);
+
+ if (handler->start)
+ handler->start (handler, attribute_names, attribute_values, parser);
+}
+
+static void
+svg_parser_end_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ gpointer user_data,
+ GError **error)
+{
+ SvgParser *parser = user_data;
+ SvgHandler *handler;
+ SvgHandler *base;
+ GList *paths;
+
+ handler = g_queue_pop_head (parser->stack);
+
+ g_return_if_fail (handler != NULL &&
+ (handler->name == NULL ||
+ strcmp (handler->name, element_name) == 0));
+
+ if (handler->end)
+ handler->end (handler, parser);
+
+ if (handler->paths)
+ {
+ if (handler->transform)
+ {
+ for (paths = handler->paths; paths; paths = paths->next)
+ {
+ SvgPath *path = paths->data;
+ GList *list;
+
+ for (list = path->strokes; list; list = list->next)
+ gimp_stroke_transform (GIMP_STROKE (list->data),
+ handler->transform, NULL);
+ }
+
+ g_slice_free (GimpMatrix3, handler->transform);
+ }
+
+ base = g_queue_peek_head (parser->stack);
+ base->paths = g_list_concat (base->paths, handler->paths);
+ }
+
+ g_slice_free (SvgHandler, handler);
+}
+
+static void
+svg_handler_svg_start (SvgHandler *handler,
+ const gchar **names,
+ const gchar **values,
+ SvgParser *parser)
+{
+ GimpMatrix3 *matrix;
+ GimpMatrix3 box;
+ const gchar *viewbox = NULL;
+ gdouble x = 0;
+ gdouble y = 0;
+ gdouble w = handler->width;
+ gdouble h = handler->height;
+ gdouble xres;
+ gdouble yres;
+
+ matrix = g_slice_new (GimpMatrix3);
+ gimp_matrix3_identity (matrix);
+
+ gimp_image_get_resolution (parser->image, &xres, &yres);
+
+ while (*names)
+ {
+ switch (*names[0])
+ {
+ case 'x':
+ if (strcmp (*names, "x") == 0)
+ parse_svg_length (*values, handler->width, xres, &x);
+ break;
+
+ case 'y':
+ if (strcmp (*names, "y") == 0)
+ parse_svg_length (*values, handler->height, yres, &y);
+ break;
+
+ case 'w':
+ if (strcmp (*names, "width") == 0)
+ parse_svg_length (*values, handler->width, xres, &w);
+ break;
+
+ case 'h':
+ if (strcmp (*names, "height") == 0)
+ parse_svg_length (*values, handler->height, yres, &h);
+ break;
+
+ case 'v':
+ if (strcmp (*names, "viewBox") == 0)
+ viewbox = *values;
+ break;
+ }
+
+ names++;
+ values++;
+ }
+
+ if (x || y)
+ {
+ /* according to the spec offsets are meaningless on the outermost svg */
+ if (parser->svg_depth > 0)
+ gimp_matrix3_translate (matrix, x, y);
+ }
+
+ if (viewbox && parse_svg_viewbox (viewbox, &w, &h, &box))
+ {
+ gimp_matrix3_mult (&box, matrix);
+ }
+
+ /* optionally scale the outermost svg to image size */
+ if (parser->scale && parser->svg_depth == 0)
+ {
+ if (w > 0.0 && h > 0.0)
+ gimp_matrix3_scale (matrix,
+ gimp_image_get_width (parser->image) / w,
+ gimp_image_get_height (parser->image) / h);
+ }
+
+ handler->width = w;
+ handler->height = h;
+
+ handler->transform = matrix;
+
+ parser->svg_depth++;
+}
+
+static void
+svg_handler_svg_end (SvgHandler *handler,
+ SvgParser *parser)
+{
+ parser->svg_depth--;
+}
+
+static void
+svg_handler_group_start (SvgHandler *handler,
+ const gchar **names,
+ const gchar **values,
+ SvgParser *parser)
+{
+ while (*names)
+ {
+ if (strcmp (*names, "transform") == 0 && ! handler->transform)
+ {
+ GimpMatrix3 matrix;
+
+ if (parse_svg_transform (*values, &matrix))
+ {
+ handler->transform = g_slice_dup (GimpMatrix3, &matrix);
+
+#ifdef DEBUG_VECTORS_IMPORT
+ g_printerr ("transform %s: %g %g %g %g %g %g %g %g %g\n",
+ handler->id ? handler->id : "(null)",
+ handler->transform->coeff[0][0],
+ handler->transform->coeff[0][1],
+ handler->transform->coeff[0][2],
+ handler->transform->coeff[1][0],
+ handler->transform->coeff[1][1],
+ handler->transform->coeff[1][2],
+ handler->transform->coeff[2][0],
+ handler->transform->coeff[2][1],
+ handler->transform->coeff[2][2]);
+#endif
+ }
+ }
+
+ names++;
+ values++;
+ }
+}
+
+static void
+svg_handler_path_start (SvgHandler *handler,
+ const gchar **names,
+ const gchar **values,
+ SvgParser *parser)
+{
+ SvgPath *path = g_slice_new0 (SvgPath);
+
+ while (*names)
+ {
+ switch (*names[0])
+ {
+ case 'i':
+ if (strcmp (*names, "id") == 0 && ! path->id)
+ path->id = g_strdup (*values);
+ break;
+
+ case 'd':
+ if (strcmp (*names, "d") == 0 && ! path->strokes)
+ path->strokes = parse_path_data (*values);
+ break;
+
+ case 't':
+ if (strcmp (*names, "transform") == 0 && ! handler->transform)
+ {
+ GimpMatrix3 matrix;
+
+ if (parse_svg_transform (*values, &matrix))
+ {
+ handler->transform = g_slice_dup (GimpMatrix3, &matrix);
+ }
+ }
+ break;
+ }
+
+ names++;
+ values++;
+ }
+
+ handler->paths = g_list_prepend (handler->paths, path);
+}
+
+static void
+svg_handler_rect_start (SvgHandler *handler,
+ const gchar **names,
+ const gchar **values,
+ SvgParser *parser)
+{
+ SvgPath *path = g_slice_new0 (SvgPath);
+ gdouble x = 0.0;
+ gdouble y = 0.0;
+ gdouble width = 0.0;
+ gdouble height = 0.0;
+ gdouble rx = 0.0;
+ gdouble ry = 0.0;
+ gdouble xres;
+ gdouble yres;
+
+ gimp_image_get_resolution (parser->image, &xres, &yres);
+
+ while (*names)
+ {
+ switch (*names[0])
+ {
+ case 'i':
+ if (strcmp (*names, "id") == 0 && ! path->id)
+ path->id = g_strdup (*values);
+ break;
+
+ case 'x':
+ if (strcmp (*names, "x") == 0)
+ parse_svg_length (*values, handler->width, xres, &x);
+ break;
+
+ case 'y':
+ if (strcmp (*names, "y") == 0)
+ parse_svg_length (*values, handler->height, yres, &y);
+ break;
+
+ case 'w':
+ if (strcmp (*names, "width") == 0)
+ parse_svg_length (*values, handler->width, xres, &width);
+ break;
+
+ case 'h':
+ if (strcmp (*names, "height") == 0)
+ parse_svg_length (*values, handler->height, yres, &height);
+ break;
+
+ case 'r':
+ if (strcmp (*names, "rx") == 0)
+ parse_svg_length (*values, handler->width, xres, &rx);
+ else if (strcmp (*names, "ry") == 0)
+ parse_svg_length (*values, handler->height, yres, &ry);
+ break;
+
+ case 't':
+ if (strcmp (*names, "transform") == 0 && ! handler->transform)
+ {
+ GimpMatrix3 matrix;
+
+ if (parse_svg_transform (*values, &matrix))
+ {
+ handler->transform = g_slice_dup (GimpMatrix3, &matrix);
+ }
+ }
+ break;
+ }
+
+ names++;
+ values++;
+ }
+
+ if (width > 0.0 && height > 0.0 && rx >= 0.0 && ry >= 0.0)
+ {
+ GimpStroke *stroke;
+ GimpCoords point = COORDS_INIT;
+
+ if (rx == 0.0)
+ rx = ry;
+ if (ry == 0.0)
+ ry = rx;
+
+ rx = MIN (rx, width / 2);
+ ry = MIN (ry, height / 2);
+
+ point.x = x + width - rx;
+ point.y = y;
+ stroke = gimp_bezier_stroke_new_moveto (&point);
+
+ if (rx)
+ {
+ GimpCoords end = COORDS_INIT;
+
+ end.x = x + width;
+ end.y = y + ry;
+
+ gimp_bezier_stroke_arcto (stroke, rx, ry, 0, FALSE, TRUE, &end);
+ }
+
+ point.x = x + width;
+ point.y = y + height - ry;
+ gimp_bezier_stroke_lineto (stroke, &point);
+
+ if (rx)
+ {
+ GimpCoords end = COORDS_INIT;
+
+ end.x = x + width - rx;
+ end.y = y + height;
+
+ gimp_bezier_stroke_arcto (stroke, rx, ry, 0, FALSE, TRUE, &end);
+ }
+
+ point.x = x + rx;
+ point.y = y + height;
+ gimp_bezier_stroke_lineto (stroke, &point);
+
+ if (rx)
+ {
+ GimpCoords end = COORDS_INIT;
+
+ end.x = x;
+ end.y = y + height - ry;
+
+ gimp_bezier_stroke_arcto (stroke, rx, ry, 0, FALSE, TRUE, &end);
+ }
+
+ point.x = x;
+ point.y = y + ry;
+ gimp_bezier_stroke_lineto (stroke, &point);
+
+ if (rx)
+ {
+ GimpCoords end = COORDS_INIT;
+
+ end.x = x + rx;
+ end.y = y;
+
+ gimp_bezier_stroke_arcto (stroke, rx, ry, 0, FALSE, TRUE, &end);
+ }
+
+ /* the last line is handled by closing the stroke */
+ gimp_stroke_close (stroke);
+
+ path->strokes = g_list_prepend (path->strokes, stroke);
+ }
+
+ handler->paths = g_list_prepend (handler->paths, path);
+}
+
+static void
+svg_handler_ellipse_start (SvgHandler *handler,
+ const gchar **names,
+ const gchar **values,
+ SvgParser *parser)
+{
+ SvgPath *path = g_slice_new0 (SvgPath);
+ GimpCoords center = COORDS_INIT;
+ gdouble rx = 0.0;
+ gdouble ry = 0.0;
+ gdouble xres;
+ gdouble yres;
+
+ gimp_image_get_resolution (parser->image, &xres, &yres);
+
+ while (*names)
+ {
+ switch (*names[0])
+ {
+ case 'i':
+ if (strcmp (*names, "id") == 0 && ! path->id)
+ path->id = g_strdup (*values);
+ break;
+
+ case 'c':
+ if (strcmp (*names, "cx") == 0)
+ parse_svg_length (*values, handler->width, xres, &center.x);
+ else if (strcmp (*names, "cy") == 0)
+ parse_svg_length (*values, handler->height, yres, &center.y);
+ break;
+
+ case 'r':
+ if (strcmp (*names, "r") == 0)
+ {
+ parse_svg_length (*values, handler->width, xres, &rx);
+ parse_svg_length (*values, handler->height, yres, &ry);
+ }
+ else if (strcmp (*names, "rx") == 0)
+ {
+ parse_svg_length (*values, handler->width, xres, &rx);
+ }
+ else if (strcmp (*names, "ry") == 0)
+ {
+ parse_svg_length (*values, handler->height, yres, &ry);
+ }
+ break;
+
+ case 't':
+ if (strcmp (*names, "transform") == 0 && ! handler->transform)
+ {
+ GimpMatrix3 matrix;
+
+ if (parse_svg_transform (*values, &matrix))
+ {
+ handler->transform = g_slice_dup (GimpMatrix3, &matrix);
+ }
+ }
+ break;
+ }
+
+ names++;
+ values++;
+ }
+
+ if (rx >= 0.0 && ry >= 0.0)
+ path->strokes = g_list_prepend (path->strokes,
+ gimp_bezier_stroke_new_ellipse (&center,
+ rx, ry,
+ 0.0));
+
+ handler->paths = g_list_prepend (handler->paths, path);
+}
+
+static void
+svg_handler_line_start (SvgHandler *handler,
+ const gchar **names,
+ const gchar **values,
+ SvgParser *parser)
+{
+ SvgPath *path = g_slice_new0 (SvgPath);
+ GimpCoords start = COORDS_INIT;
+ GimpCoords end = COORDS_INIT;
+ GimpStroke *stroke;
+ gdouble xres;
+ gdouble yres;
+
+ gimp_image_get_resolution (parser->image, &xres, &yres);
+
+ while (*names)
+ {
+ switch (*names[0])
+ {
+ case 'i':
+ if (strcmp (*names, "id") == 0 && ! path->id)
+ path->id = g_strdup (*values);
+ break;
+
+ case 'x':
+ if (strcmp (*names, "x1") == 0)
+ parse_svg_length (*values, handler->width, xres, &start.x);
+ else if (strcmp (*names, "x2") == 0)
+ parse_svg_length (*values, handler->width, xres, &end.x);
+ break;
+
+ case 'y':
+ if (strcmp (*names, "y1") == 0)
+ parse_svg_length (*values, handler->height, yres, &start.y);
+ else if (strcmp (*names, "y2") == 0)
+ parse_svg_length (*values, handler->height, yres, &end.y);
+ break;
+
+ case 't':
+ if (strcmp (*names, "transform") == 0 && ! handler->transform)
+ {
+ GimpMatrix3 matrix;
+
+ if (parse_svg_transform (*values, &matrix))
+ {
+ handler->transform = g_slice_dup (GimpMatrix3, &matrix);
+ }
+ }
+ break;
+ }
+
+ names++;
+ values++;
+ }
+
+ stroke = gimp_bezier_stroke_new_moveto (&start);
+ gimp_bezier_stroke_lineto (stroke, &end);
+
+ path->strokes = g_list_prepend (path->strokes, stroke);
+
+ handler->paths = g_list_prepend (handler->paths, path);
+}
+
+static void
+svg_handler_poly_start (SvgHandler *handler,
+ const gchar **names,
+ const gchar **values,
+ SvgParser *parser)
+{
+ SvgPath *path = g_slice_new0 (SvgPath);
+ GString *points = NULL;
+
+ while (*names)
+ {
+ switch (*names[0])
+ {
+ case 'i':
+ if (strcmp (*names, "id") == 0 && ! path->id)
+ path->id = g_strdup (*values);
+ break;
+
+ case 'p':
+ if (strcmp (*names, "points") == 0 && ! points)
+ {
+ const gchar *p = *values;
+ const gchar *m = NULL;
+ const gchar *l = NULL;
+ gint n = 0;
+
+ while (*p)
+ {
+ while (g_ascii_isspace (*p) || *p == ',')
+ p++;
+
+ switch (n)
+ {
+ case 0:
+ m = p;
+ break;
+ case 2:
+ l = p;
+ break;
+ }
+
+ if (*p)
+ n++;
+
+ while (*p && ! g_ascii_isspace (*p) && *p != ',')
+ p++;
+ }
+
+ if ((n > 3) && (n % 2 == 0))
+ {
+ points = g_string_sized_new (p - *values + 8);
+
+ g_string_append_len (points, "M ", 2);
+ g_string_append_len (points, m, l - m);
+
+ g_string_append_len (points, "L ", 2);
+ g_string_append_len (points, l, p - l);
+
+ if (strcmp (handler->name, "polygon") == 0)
+ g_string_append_c (points, 'Z');
+ }
+ }
+ break;
+
+ case 't':
+ if (strcmp (*names, "transform") == 0 && ! handler->transform)
+ {
+ GimpMatrix3 matrix;
+
+ if (parse_svg_transform (*values, &matrix))
+ {
+ handler->transform = g_slice_dup (GimpMatrix3, &matrix);
+ }
+ }
+ break;
+ }
+
+ names++;
+ values++;
+ }
+
+ if (points)
+ {
+ path->strokes = parse_path_data (points->str);
+ g_string_free (points, TRUE);
+ }
+
+ handler->paths = g_list_prepend (handler->paths, path);
+}
+
+static gboolean
+parse_svg_length (const gchar *value,
+ gdouble reference,
+ gdouble resolution,
+ gdouble *length)
+{
+ GimpUnit unit = GIMP_UNIT_PIXEL;
+ gdouble len;
+ gchar *ptr;
+
+ len = g_ascii_strtod (value, &ptr);
+
+ while (g_ascii_isspace (*ptr))
+ ptr++;
+
+ switch (ptr[0])
+ {
+ case '\0':
+ break;
+
+ case 'p':
+ switch (ptr[1])
+ {
+ case 'x': break;
+ case 't': unit = GIMP_UNIT_POINT; break;
+ case 'c': unit = GIMP_UNIT_PICA; break;
+ default:
+ return FALSE;
+ }
+ ptr += 2;
+ break;
+
+ case 'c':
+ if (ptr[1] == 'm')
+ len *= 10.0, unit = GIMP_UNIT_MM;
+ else
+ return FALSE;
+ ptr += 2;
+ break;
+
+ case 'm':
+ if (ptr[1] == 'm')
+ unit = GIMP_UNIT_MM;
+ else
+ return FALSE;
+ ptr += 2;
+ break;
+
+ case 'i':
+ if (ptr[1] == 'n')
+ unit = GIMP_UNIT_INCH;
+ else
+ return FALSE;
+ ptr += 2;
+ break;
+
+ case '%':
+ unit = GIMP_UNIT_PERCENT;
+ ptr += 1;
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ while (g_ascii_isspace (*ptr))
+ ptr++;
+
+ if (*ptr)
+ return FALSE;
+
+ switch (unit)
+ {
+ case GIMP_UNIT_PERCENT:
+ *length = len * reference / 100.0;
+ break;
+
+ case GIMP_UNIT_PIXEL:
+ *length = len;
+ break;
+
+ default:
+ *length = len * resolution / gimp_unit_get_factor (unit);
+ break;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+parse_svg_viewbox (const gchar *value,
+ gdouble *width,
+ gdouble *height,
+ GimpMatrix3 *matrix)
+{
+ gdouble x, y, w, h;
+ gchar *tok;
+ gchar *str = g_strdup (value);
+ gboolean success = FALSE;
+
+ x = y = w = h = 0;
+
+ tok = strtok (str, ", \t");
+ if (tok)
+ {
+ x = g_ascii_strtod (tok, NULL);
+ tok = strtok (NULL, ", \t");
+ if (tok)
+ {
+ y = g_ascii_strtod (tok, NULL);
+ tok = strtok (NULL, ", \t");
+ if (tok != NULL)
+ {
+ w = g_ascii_strtod (tok, NULL);
+ tok = strtok (NULL, ", \t");
+ if (tok)
+ {
+ h = g_ascii_strtod (tok, NULL);
+ success = TRUE;
+ }
+ }
+ }
+ }
+
+ g_free (str);
+
+ if (success)
+ {
+ gimp_matrix3_identity (matrix);
+ gimp_matrix3_translate (matrix, -x, -y);
+
+ if (w > 0.0 && h > 0.0)
+ {
+ gimp_matrix3_scale (matrix, *width / w, *height / h);
+ }
+ else /* disable rendering of the element */
+ {
+#ifdef DEBUG_VECTORS_IMPORT
+ g_printerr ("empty viewBox");
+#endif
+ *width = *height = 0.0;
+ }
+ }
+ else
+ {
+ g_printerr ("SVG import: cannot parse viewBox attribute\n");
+ }
+
+ return success;
+}
+
+static gboolean
+parse_svg_transform (const gchar *value,
+ GimpMatrix3 *matrix)
+{
+ gint i;
+
+ gimp_matrix3_identity (matrix);
+
+ for (i = 0; value[i]; i++)
+ {
+ GimpMatrix3 trafo;
+ gchar keyword[32];
+ gdouble args[6];
+ gint n_args;
+ gint key_len;
+
+ gimp_matrix3_identity (&trafo);
+
+ /* skip initial whitespace */
+ while (g_ascii_isspace (value[i]))
+ i++;
+
+ /* parse keyword */
+ for (key_len = 0; key_len < sizeof (keyword); key_len++)
+ {
+ gchar c = value[i];
+
+ if (g_ascii_isalpha (c) || c == '-')
+ keyword[key_len] = value[i++];
+ else
+ break;
+ }
+
+ if (key_len >= sizeof (keyword))
+ return FALSE;
+
+ keyword[key_len] = '\0';
+
+ /* skip whitespace */
+ while (g_ascii_isspace (value[i]))
+ i++;
+
+ if (value[i] != '(')
+ return FALSE;
+ i++;
+
+ for (n_args = 0; ; n_args++)
+ {
+ gchar c;
+ gchar *end_ptr;
+
+ /* skip whitespace */
+ while (g_ascii_isspace (value[i]))
+ i++;
+
+ c = value[i];
+ if (g_ascii_isdigit (c) || c == '+' || c == '-' || c == '.')
+ {
+ if (n_args == G_N_ELEMENTS (args))
+ return FALSE; /* too many args */
+
+ args[n_args] = g_ascii_strtod (value + i, &end_ptr);
+ i = end_ptr - value;
+
+ while (g_ascii_isspace (value[i]))
+ i++;
+
+ /* skip optional comma */
+ if (value[i] == ',')
+ i++;
+ }
+ else if (c == ')')
+ break;
+ else
+ return FALSE;
+ }
+
+ /* OK, have parsed keyword and args, now calculate the transform matrix */
+
+ if (strcmp (keyword, "matrix") == 0)
+ {
+ if (n_args != 6)
+ return FALSE;
+
+ gimp_matrix3_affine (&trafo,
+ args[0], args[1],
+ args[2], args[3],
+ args[4], args[5]);
+ }
+ else if (strcmp (keyword, "translate") == 0)
+ {
+ if (n_args == 1)
+ args[1] = 0.0;
+ else if (n_args != 2)
+ return FALSE;
+
+ gimp_matrix3_translate (&trafo, args[0], args[1]);
+ }
+ else if (strcmp (keyword, "scale") == 0)
+ {
+ if (n_args == 1)
+ args[1] = args[0];
+ else if (n_args != 2)
+ return FALSE;
+
+ gimp_matrix3_scale (&trafo, args[0], args[1]);
+ }
+ else if (strcmp (keyword, "rotate") == 0)
+ {
+ if (n_args == 1)
+ {
+ gimp_matrix3_rotate (&trafo, gimp_deg_to_rad (args[0]));
+ }
+ else if (n_args == 3)
+ {
+ gimp_matrix3_translate (&trafo, -args[1], -args[2]);
+ gimp_matrix3_rotate (&trafo, gimp_deg_to_rad (args[0]));
+ gimp_matrix3_translate (&trafo, args[1], args[2]);
+ }
+ else
+ return FALSE;
+ }
+ else if (strcmp (keyword, "skewX") == 0)
+ {
+ if (n_args != 1)
+ return FALSE;
+
+ gimp_matrix3_xshear (&trafo, tan (gimp_deg_to_rad (args[0])));
+ }
+ else if (strcmp (keyword, "skewY") == 0)
+ {
+ if (n_args != 1)
+ return FALSE;
+
+ gimp_matrix3_yshear (&trafo, tan (gimp_deg_to_rad (args[0])));
+ }
+ else
+ {
+ return FALSE; /* unknown keyword */
+ }
+
+ gimp_matrix3_invert (&trafo);
+ gimp_matrix3_mult (&trafo, matrix);
+ }
+
+ gimp_matrix3_invert (matrix);
+
+ return TRUE;
+}
+
+
+/**********************************************************/
+/* Below is the code that parses the actual path data. */
+/* */
+/* This code is taken from librsvg and was originally */
+/* written by Raph Levien <raph@artofcode.com> for Gill. */
+/**********************************************************/
+
+typedef struct
+{
+ GList *strokes;
+ GimpStroke *stroke;
+ gdouble cpx, cpy; /* current point */
+ gdouble rpx, rpy; /* reflection point (for 's' and 't' commands) */
+ gchar cmd; /* current command (lowercase) */
+ gint param; /* number of parameters */
+ gboolean rel; /* true if relative coords */
+ gdouble params[7]; /* parameters that have been parsed */
+} ParsePathContext;
+
+
+static void parse_path_default_xy (ParsePathContext *ctx,
+ gint n_params);
+static void parse_path_do_cmd (ParsePathContext *ctx,
+ gboolean final);
+
+
+static GList *
+parse_path_data (const gchar *data)
+{
+ ParsePathContext ctx;
+
+ gboolean in_num = FALSE;
+ gboolean in_frac = FALSE;
+ gboolean in_exp = FALSE;
+ gboolean exp_wait_sign = FALSE;
+ gdouble val = 0.0;
+ gchar c = 0;
+ gint sign = 0;
+ gint exp = 0;
+ gint exp_sign = 0;
+ gdouble frac = 0.0;
+ gint i;
+
+ memset (&ctx, 0, sizeof (ParsePathContext));
+
+ for (i = 0; ; i++)
+ {
+ c = data[i];
+
+ if (c >= '0' && c <= '9')
+ {
+ /* digit */
+ if (in_num)
+ {
+ if (in_exp)
+ {
+ exp = (exp * 10) + c - '0';
+ exp_wait_sign = FALSE;
+ }
+ else if (in_frac)
+ val += (frac *= 0.1) * (c - '0');
+ else
+ val = (val * 10) + c - '0';
+ }
+ else
+ {
+ in_num = TRUE;
+ in_frac = FALSE;
+ in_exp = FALSE;
+ exp = 0;
+ exp_sign = 1;
+ exp_wait_sign = FALSE;
+ val = c - '0';
+ sign = 1;
+ }
+ }
+ else if (c == '.')
+ {
+ if (! in_num)
+ {
+ in_num = TRUE;
+ val = 0;
+ }
+
+ in_frac = TRUE;
+ frac = 1;
+ }
+ else if ((c == 'E' || c == 'e') && in_num)
+ {
+ in_exp = TRUE;
+ exp_wait_sign = TRUE;
+ exp = 0;
+ exp_sign = 1;
+ }
+ else if ((c == '+' || c == '-') && in_exp)
+ {
+ exp_sign = c == '+' ? 1 : -1;
+ }
+ else if (in_num)
+ {
+ /* end of number */
+
+ val *= sign * pow (10, exp_sign * exp);
+
+ if (ctx.rel)
+ {
+ /* Handle relative coordinates. This switch statement attempts
+ to determine _what_ the coords are relative to. This is
+ underspecified in the 12 Apr working draft. */
+ switch (ctx.cmd)
+ {
+ case 'l':
+ case 'm':
+ case 'c':
+ case 's':
+ case 'q':
+ case 't':
+ /* rule: even-numbered params are x-relative, odd-numbered
+ are y-relative */
+ if ((ctx.param & 1) == 0)
+ val += ctx.cpx;
+ else if ((ctx.param & 1) == 1)
+ val += ctx.cpy;
+ break;
+
+ case 'a':
+ /* rule: sixth and seventh are x and y, rest are not
+ relative */
+ if (ctx.param == 5)
+ val += ctx.cpx;
+ else if (ctx.param == 6)
+ val += ctx.cpy;
+ break;
+
+ case 'h':
+ /* rule: x-relative */
+ val += ctx.cpx;
+ break;
+
+ case 'v':
+ /* rule: y-relative */
+ val += ctx.cpy;
+ break;
+ }
+ }
+
+ ctx.params[ctx.param++] = val;
+ parse_path_do_cmd (&ctx, FALSE);
+ in_num = FALSE;
+ }
+
+ if (c == '\0')
+ {
+ break;
+ }
+ else if ((c == '+' || c == '-') && ! exp_wait_sign)
+ {
+ sign = c == '+' ? 1 : -1;
+ val = 0;
+ in_num = TRUE;
+ in_frac = FALSE;
+ in_exp = FALSE;
+ exp = 0;
+ exp_sign = 1;
+ exp_wait_sign = FALSE;
+ }
+ else if (c == 'z' || c == 'Z')
+ {
+ if (ctx.param)
+ parse_path_do_cmd (&ctx, TRUE);
+
+ if (ctx.stroke)
+ gimp_stroke_close (ctx.stroke);
+ }
+ else if (c >= 'A' && c <= 'Z' && c != 'E')
+ {
+ if (ctx.param)
+ parse_path_do_cmd (&ctx, TRUE);
+
+ ctx.cmd = c + 'a' - 'A';
+ ctx.rel = FALSE;
+ }
+ else if (c >= 'a' && c <= 'z' && c != 'e')
+ {
+ if (ctx.param)
+ parse_path_do_cmd (&ctx, TRUE);
+
+ ctx.cmd = c;
+ ctx.rel = TRUE;
+ }
+ /* else c _should_ be whitespace or , */
+ }
+
+ return g_list_reverse (ctx.strokes);
+}
+
+/* supply defaults for missing parameters, assuming relative coordinates
+ are to be interpreted as x,y */
+static void
+parse_path_default_xy (ParsePathContext *ctx,
+ gint n_params)
+{
+ gint i;
+
+ if (ctx->rel)
+ {
+ for (i = ctx->param; i < n_params; i++)
+ {
+ if (i > 2)
+ ctx->params[i] = ctx->params[i - 2];
+ else if (i == 1)
+ ctx->params[i] = ctx->cpy;
+ else if (i == 0)
+ /* we shouldn't get here (ctx->param > 0 as precondition) */
+ ctx->params[i] = ctx->cpx;
+ }
+ }
+ else
+ {
+ for (i = ctx->param; i < n_params; i++)
+ ctx->params[i] = 0.0;
+ }
+}
+
+static void
+parse_path_do_cmd (ParsePathContext *ctx,
+ gboolean final)
+{
+ GimpCoords coords = COORDS_INIT;
+
+ switch (ctx->cmd)
+ {
+ case 'm':
+ /* moveto */
+ if (ctx->param == 2 || final)
+ {
+ parse_path_default_xy (ctx, 2);
+
+ coords.x = ctx->cpx = ctx->rpx = ctx->params[0];
+ coords.y = ctx->cpy = ctx->rpy = ctx->params[1];
+
+ ctx->stroke = gimp_bezier_stroke_new_moveto (&coords);
+ ctx->strokes = g_list_prepend (ctx->strokes, ctx->stroke);
+
+ ctx->param = 0;
+
+ /* If a moveto is followed by multiple pairs of coordinates,
+ * the subsequent pairs are treated as implicit lineto commands.
+ */
+ ctx->cmd = 'l';
+ }
+ break;
+
+ case 'l':
+ /* lineto */
+ if (ctx->param == 2 || final)
+ {
+ parse_path_default_xy (ctx, 2);
+
+ coords.x = ctx->cpx = ctx->rpx = ctx->params[0];
+ coords.y = ctx->cpy = ctx->rpy = ctx->params[1];
+
+ gimp_bezier_stroke_lineto (ctx->stroke, &coords);
+
+ ctx->param = 0;
+ }
+ break;
+
+ case 'c':
+ /* curveto */
+ if (ctx->param == 6 || final)
+ {
+ GimpCoords ctrl1 = COORDS_INIT;
+ GimpCoords ctrl2 = COORDS_INIT;
+
+ parse_path_default_xy (ctx, 6);
+
+ ctrl1.x = ctx->params[0];
+ ctrl1.y = ctx->params[1];
+ ctrl2.x = ctx->rpx = ctx->params[2];
+ ctrl2.y = ctx->rpy = ctx->params[3];
+ coords.x = ctx->cpx = ctx->params[4];
+ coords.y = ctx->cpy = ctx->params[5];
+
+ gimp_bezier_stroke_cubicto (ctx->stroke, &ctrl1, &ctrl2, &coords);
+
+ ctx->param = 0;
+ }
+ break;
+
+ case 's':
+ /* smooth curveto */
+ if (ctx->param == 4 || final)
+ {
+ GimpCoords ctrl1 = COORDS_INIT;
+ GimpCoords ctrl2 = COORDS_INIT;
+
+ parse_path_default_xy (ctx, 4);
+
+ ctrl1.x = 2 * ctx->cpx - ctx->rpx;
+ ctrl1.y = 2 * ctx->cpy - ctx->rpy;
+ ctrl2.x = ctx->rpx = ctx->params[0];
+ ctrl2.y = ctx->rpy = ctx->params[1];
+ coords.x = ctx->cpx = ctx->params[2];
+ coords.y = ctx->cpy = ctx->params[3];
+
+ gimp_bezier_stroke_cubicto (ctx->stroke, &ctrl1, &ctrl2, &coords);
+
+ ctx->param = 0;
+ }
+ break;
+
+ case 'h':
+ /* horizontal lineto */
+ if (ctx->param == 1)
+ {
+ coords.x = ctx->cpx = ctx->rpx = ctx->params[0];
+ coords.y = ctx->cpy;
+
+ gimp_bezier_stroke_lineto (ctx->stroke, &coords);
+
+ ctx->param = 0;
+ }
+ break;
+
+ case 'v':
+ /* vertical lineto */
+ if (ctx->param == 1)
+ {
+ coords.x = ctx->cpx;
+ coords.y = ctx->cpy = ctx->rpy = ctx->params[0];
+
+ gimp_bezier_stroke_lineto (ctx->stroke, &coords);
+
+ ctx->param = 0;
+ }
+ break;
+
+ case 'q':
+ /* quadratic bezier curveto */
+ if (ctx->param == 4 || final)
+ {
+ GimpCoords ctrl = COORDS_INIT;
+
+ parse_path_default_xy (ctx, 4);
+
+ ctrl.x = ctx->rpx = ctx->params[0];
+ ctrl.y = ctx->rpy = ctx->params[1];
+ coords.x = ctx->cpx = ctx->params[2];
+ coords.y = ctx->cpy = ctx->params[3];
+
+ gimp_bezier_stroke_conicto (ctx->stroke, &ctrl, &coords);
+
+ ctx->param = 0;
+ }
+ break;
+
+ case 't':
+ /* truetype quadratic bezier curveto */
+ if (ctx->param == 2 || final)
+ {
+ GimpCoords ctrl = COORDS_INIT;
+
+ parse_path_default_xy (ctx, 2);
+
+ ctrl.x = ctx->rpx = 2 * ctx->cpx - ctx->rpx;
+ ctrl.y = ctx->rpy = 2 * ctx->cpy - ctx->rpy;
+ coords.x = ctx->cpx = ctx->params[0];
+ coords.y = ctx->cpy = ctx->params[1];
+
+ gimp_bezier_stroke_conicto (ctx->stroke, &ctrl, &coords);
+
+ ctx->param = 0;
+ }
+ else if (final)
+ {
+ if (ctx->param > 2)
+ {
+ GimpCoords ctrl = COORDS_INIT;
+
+ parse_path_default_xy (ctx, 4);
+
+ ctrl.x = ctx->rpx = ctx->params[0];
+ ctrl.y = ctx->rpy = ctx->params[1];
+ coords.x = ctx->cpx = ctx->params[2];
+ coords.y = ctx->cpy = ctx->params[3];
+
+ gimp_bezier_stroke_conicto (ctx->stroke, &ctrl, &coords);
+ }
+ else
+ {
+ parse_path_default_xy (ctx, 2);
+
+ coords.x = ctx->cpx = ctx->rpx = ctx->params[0];
+ coords.y = ctx->cpy = ctx->rpy = ctx->params[1];
+
+ gimp_bezier_stroke_lineto (ctx->stroke, &coords);
+ }
+
+ ctx->param = 0;
+ }
+ break;
+
+ case 'a':
+ if (ctx->param == 7 || final)
+ {
+ coords.x = ctx->cpx = ctx->rpx = ctx->params[5];
+ coords.y = ctx->cpy = ctx->rpy = ctx->params[6];
+
+ gimp_bezier_stroke_arcto (ctx->stroke,
+ ctx->params[0], ctx->params[1],
+ gimp_deg_to_rad (ctx->params[2]),
+ ctx->params[3], ctx->params[4],
+ &coords);
+ ctx->param = 0;
+ }
+ break;
+
+ default:
+ ctx->param = 0;
+ break;
+ }
+}