diff options
Diffstat (limited to 'app/vectors/gimpvectors-import.c')
-rw-r--r-- | app/vectors/gimpvectors-import.c | 1784 |
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, ¢er.x); + else if (strcmp (*names, "cy") == 0) + parse_svg_length (*values, handler->height, yres, ¢er.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 (¢er, + 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; + } +} |