1789 lines
50 KiB
C
1789 lines
50 KiB
C
/* PIKA - Photo and Image Kooker Application
|
|
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
|
|
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
|
|
*
|
|
* Original copyright, applying to most contents (license remains unchanged):
|
|
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
|
*
|
|
* PikaVectors 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 PIKA 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 "libpikabase/pikabase.h"
|
|
#include "libpikamath/pikamath.h"
|
|
|
|
#include "vectors-types.h"
|
|
|
|
#include "config/pikaxmlparser.h"
|
|
|
|
#include "core/pikaerror.h"
|
|
#include "core/pikaimage.h"
|
|
#include "core/pikaimage-undo.h"
|
|
|
|
#include "pikabezierstroke.h"
|
|
#include "pikastroke.h"
|
|
#include "pikavectors.h"
|
|
#include "pikavectors-import.h"
|
|
|
|
#include "pika-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;
|
|
PikaImage *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;
|
|
PikaMatrix3 *transform;
|
|
};
|
|
|
|
|
|
typedef struct
|
|
{
|
|
gchar *id;
|
|
GList *strokes;
|
|
} SvgPath;
|
|
|
|
|
|
static gboolean pika_vectors_import (PikaImage *image,
|
|
GFile *file,
|
|
const gchar *str,
|
|
gsize len,
|
|
gboolean merge,
|
|
gboolean scale,
|
|
PikaVectors *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,
|
|
PikaMatrix3 *matrix);
|
|
static gboolean parse_svg_transform (const gchar *value,
|
|
PikaMatrix3 *matrix);
|
|
static GList * parse_path_data (const gchar *data);
|
|
|
|
|
|
/**
|
|
* pika_vectors_import_file:
|
|
* @image: the #PikaImage to add the paths to
|
|
* @file: a SVG file
|
|
* @merge: should multiple paths be merged into a single #PikaVectors 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.
|
|
*
|
|
* Returns: %TRUE on success, %FALSE if an error occurred
|
|
**/
|
|
gboolean
|
|
pika_vectors_import_file (PikaImage *image,
|
|
GFile *file,
|
|
gboolean merge,
|
|
gboolean scale,
|
|
PikaVectors *parent,
|
|
gint position,
|
|
GList **ret_vectors,
|
|
GError **error)
|
|
{
|
|
g_return_val_if_fail (PIKA_IS_IMAGE (image), FALSE);
|
|
g_return_val_if_fail (G_IS_FILE (file), FALSE);
|
|
g_return_val_if_fail (parent == NULL ||
|
|
parent == PIKA_IMAGE_ACTIVE_PARENT ||
|
|
PIKA_IS_VECTORS (parent), FALSE);
|
|
g_return_val_if_fail (parent == NULL ||
|
|
parent == PIKA_IMAGE_ACTIVE_PARENT ||
|
|
pika_item_is_attached (PIKA_ITEM (parent)), FALSE);
|
|
g_return_val_if_fail (parent == NULL ||
|
|
parent == PIKA_IMAGE_ACTIVE_PARENT ||
|
|
pika_item_get_image (PIKA_ITEM (parent)) == image,
|
|
FALSE);
|
|
g_return_val_if_fail (parent == NULL ||
|
|
parent == PIKA_IMAGE_ACTIVE_PARENT ||
|
|
pika_viewable_get_children (PIKA_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 pika_vectors_import (image, file, NULL, 0, merge, scale,
|
|
parent, position,
|
|
ret_vectors, error);
|
|
}
|
|
|
|
/**
|
|
* pika_vectors_import_string:
|
|
* @image: the #PikaImage 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 #PikaVectors 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.
|
|
*
|
|
* Returns: %TRUE on success, %FALSE if an error occurred
|
|
**/
|
|
gboolean
|
|
pika_vectors_import_buffer (PikaImage *image,
|
|
const gchar *buffer,
|
|
gsize len,
|
|
gboolean merge,
|
|
gboolean scale,
|
|
PikaVectors *parent,
|
|
gint position,
|
|
GList **ret_vectors,
|
|
GError **error)
|
|
{
|
|
g_return_val_if_fail (PIKA_IS_IMAGE (image), FALSE);
|
|
g_return_val_if_fail (buffer != NULL || len == 0, FALSE);
|
|
g_return_val_if_fail (parent == NULL ||
|
|
parent == PIKA_IMAGE_ACTIVE_PARENT ||
|
|
PIKA_IS_VECTORS (parent), FALSE);
|
|
g_return_val_if_fail (parent == NULL ||
|
|
parent == PIKA_IMAGE_ACTIVE_PARENT ||
|
|
pika_item_is_attached (PIKA_ITEM (parent)), FALSE);
|
|
g_return_val_if_fail (parent == NULL ||
|
|
parent == PIKA_IMAGE_ACTIVE_PARENT ||
|
|
pika_item_get_image (PIKA_ITEM (parent)) == image,
|
|
FALSE);
|
|
g_return_val_if_fail (parent == NULL ||
|
|
parent == PIKA_IMAGE_ACTIVE_PARENT ||
|
|
pika_viewable_get_children (PIKA_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 pika_vectors_import (image, NULL, buffer, len, merge, scale,
|
|
parent, position,
|
|
ret_vectors, error);
|
|
}
|
|
|
|
static gboolean
|
|
pika_vectors_import (PikaImage *image,
|
|
GFile *file,
|
|
const gchar *str,
|
|
gsize len,
|
|
gboolean merge,
|
|
gboolean scale,
|
|
PikaVectors *parent,
|
|
gint position,
|
|
GList **ret_vectors,
|
|
GError **error)
|
|
{
|
|
PikaXmlParser *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 = pika_image_get_width (image);
|
|
base->height = pika_image_get_height (image);
|
|
|
|
g_queue_push_head (parser.stack, base);
|
|
|
|
xml_parser = pika_xml_parser_new (&markup_parser, &parser);
|
|
|
|
if (file)
|
|
success = pika_xml_parser_parse_gfile (xml_parser, file, error);
|
|
else
|
|
success = pika_xml_parser_parse_buffer (xml_parser, str, len, error);
|
|
|
|
pika_xml_parser_free (xml_parser);
|
|
|
|
if (success)
|
|
{
|
|
if (base->paths)
|
|
{
|
|
PikaVectors *vectors = NULL;
|
|
|
|
base->paths = g_list_reverse (base->paths);
|
|
|
|
merge = merge && base->paths->next;
|
|
|
|
pika_image_undo_group_start (image, PIKA_UNDO_GROUP_VECTORS_IMPORT,
|
|
_("Import Paths"));
|
|
|
|
for (paths = base->paths; paths; paths = paths->next)
|
|
{
|
|
SvgPath *path = paths->data;
|
|
GList *list;
|
|
|
|
if (! merge || ! vectors)
|
|
{
|
|
vectors = pika_vectors_new (image,
|
|
((merge || ! path->id) ?
|
|
_("Imported Path") : path->id));
|
|
pika_image_add_vectors (image, vectors,
|
|
parent, position, TRUE);
|
|
pika_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)
|
|
pika_vectors_stroke_add (vectors, PIKA_STROKE (list->data));
|
|
|
|
if (! merge)
|
|
pika_vectors_thaw (vectors);
|
|
|
|
g_list_free_full (path->strokes, g_object_unref);
|
|
path->strokes = NULL;
|
|
}
|
|
|
|
if (merge)
|
|
pika_vectors_thaw (vectors);
|
|
|
|
pika_image_undo_group_end (image);
|
|
}
|
|
else
|
|
{
|
|
if (file)
|
|
g_set_error (error, PIKA_ERROR, PIKA_FAILED,
|
|
_("No paths found in '%s'"),
|
|
pika_file_get_utf8_name (file));
|
|
else
|
|
g_set_error_literal (error, PIKA_ERROR, PIKA_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"),
|
|
pika_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 (PikaMatrix3, 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)
|
|
pika_stroke_transform (PIKA_STROKE (list->data),
|
|
handler->transform, NULL);
|
|
}
|
|
|
|
g_slice_free (PikaMatrix3, 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)
|
|
{
|
|
PikaMatrix3 *matrix;
|
|
PikaMatrix3 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 (PikaMatrix3);
|
|
pika_matrix3_identity (matrix);
|
|
|
|
pika_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)
|
|
pika_matrix3_translate (matrix, x, y);
|
|
}
|
|
|
|
if (viewbox && parse_svg_viewbox (viewbox, &w, &h, &box))
|
|
{
|
|
pika_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)
|
|
pika_matrix3_scale (matrix,
|
|
pika_image_get_width (parser->image) / w,
|
|
pika_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)
|
|
{
|
|
PikaMatrix3 matrix;
|
|
|
|
if (parse_svg_transform (*values, &matrix))
|
|
{
|
|
handler->transform = g_slice_dup (PikaMatrix3, &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)
|
|
{
|
|
PikaMatrix3 matrix;
|
|
|
|
if (parse_svg_transform (*values, &matrix))
|
|
{
|
|
handler->transform = g_slice_dup (PikaMatrix3, &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;
|
|
|
|
pika_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)
|
|
{
|
|
PikaMatrix3 matrix;
|
|
|
|
if (parse_svg_transform (*values, &matrix))
|
|
{
|
|
handler->transform = g_slice_dup (PikaMatrix3, &matrix);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
names++;
|
|
values++;
|
|
}
|
|
|
|
if (width > 0.0 && height > 0.0 && rx >= 0.0 && ry >= 0.0)
|
|
{
|
|
PikaStroke *stroke;
|
|
PikaCoords 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 = pika_bezier_stroke_new_moveto (&point);
|
|
|
|
if (rx)
|
|
{
|
|
PikaCoords end = COORDS_INIT;
|
|
|
|
end.x = x + width;
|
|
end.y = y + ry;
|
|
|
|
pika_bezier_stroke_arcto (stroke, rx, ry, 0, FALSE, TRUE, &end);
|
|
}
|
|
|
|
point.x = x + width;
|
|
point.y = y + height - ry;
|
|
pika_bezier_stroke_lineto (stroke, &point);
|
|
|
|
if (rx)
|
|
{
|
|
PikaCoords end = COORDS_INIT;
|
|
|
|
end.x = x + width - rx;
|
|
end.y = y + height;
|
|
|
|
pika_bezier_stroke_arcto (stroke, rx, ry, 0, FALSE, TRUE, &end);
|
|
}
|
|
|
|
point.x = x + rx;
|
|
point.y = y + height;
|
|
pika_bezier_stroke_lineto (stroke, &point);
|
|
|
|
if (rx)
|
|
{
|
|
PikaCoords end = COORDS_INIT;
|
|
|
|
end.x = x;
|
|
end.y = y + height - ry;
|
|
|
|
pika_bezier_stroke_arcto (stroke, rx, ry, 0, FALSE, TRUE, &end);
|
|
}
|
|
|
|
point.x = x;
|
|
point.y = y + ry;
|
|
pika_bezier_stroke_lineto (stroke, &point);
|
|
|
|
if (rx)
|
|
{
|
|
PikaCoords end = COORDS_INIT;
|
|
|
|
end.x = x + rx;
|
|
end.y = y;
|
|
|
|
pika_bezier_stroke_arcto (stroke, rx, ry, 0, FALSE, TRUE, &end);
|
|
}
|
|
|
|
/* the last line is handled by closing the stroke */
|
|
pika_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);
|
|
PikaCoords center = COORDS_INIT;
|
|
gdouble rx = 0.0;
|
|
gdouble ry = 0.0;
|
|
gdouble xres;
|
|
gdouble yres;
|
|
|
|
pika_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)
|
|
{
|
|
PikaMatrix3 matrix;
|
|
|
|
if (parse_svg_transform (*values, &matrix))
|
|
{
|
|
handler->transform = g_slice_dup (PikaMatrix3, &matrix);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
names++;
|
|
values++;
|
|
}
|
|
|
|
if (rx >= 0.0 && ry >= 0.0)
|
|
path->strokes = g_list_prepend (path->strokes,
|
|
pika_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);
|
|
PikaCoords start = COORDS_INIT;
|
|
PikaCoords end = COORDS_INIT;
|
|
PikaStroke *stroke;
|
|
gdouble xres;
|
|
gdouble yres;
|
|
|
|
pika_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)
|
|
{
|
|
PikaMatrix3 matrix;
|
|
|
|
if (parse_svg_transform (*values, &matrix))
|
|
{
|
|
handler->transform = g_slice_dup (PikaMatrix3, &matrix);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
names++;
|
|
values++;
|
|
}
|
|
|
|
stroke = pika_bezier_stroke_new_moveto (&start);
|
|
pika_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)
|
|
{
|
|
PikaMatrix3 matrix;
|
|
|
|
if (parse_svg_transform (*values, &matrix))
|
|
{
|
|
handler->transform = g_slice_dup (PikaMatrix3, &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)
|
|
{
|
|
PikaUnit unit = PIKA_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 = PIKA_UNIT_POINT; break;
|
|
case 'c': unit = PIKA_UNIT_PICA; break;
|
|
default:
|
|
return FALSE;
|
|
}
|
|
ptr += 2;
|
|
break;
|
|
|
|
case 'c':
|
|
if (ptr[1] == 'm')
|
|
len *= 10.0, unit = PIKA_UNIT_MM;
|
|
else
|
|
return FALSE;
|
|
ptr += 2;
|
|
break;
|
|
|
|
case 'm':
|
|
if (ptr[1] == 'm')
|
|
unit = PIKA_UNIT_MM;
|
|
else
|
|
return FALSE;
|
|
ptr += 2;
|
|
break;
|
|
|
|
case 'i':
|
|
if (ptr[1] == 'n')
|
|
unit = PIKA_UNIT_INCH;
|
|
else
|
|
return FALSE;
|
|
ptr += 2;
|
|
break;
|
|
|
|
case '%':
|
|
unit = PIKA_UNIT_PERCENT;
|
|
ptr += 1;
|
|
break;
|
|
|
|
default:
|
|
return FALSE;
|
|
}
|
|
|
|
while (g_ascii_isspace (*ptr))
|
|
ptr++;
|
|
|
|
if (*ptr)
|
|
return FALSE;
|
|
|
|
switch (unit)
|
|
{
|
|
case PIKA_UNIT_PERCENT:
|
|
*length = len * reference / 100.0;
|
|
break;
|
|
|
|
case PIKA_UNIT_PIXEL:
|
|
*length = len;
|
|
break;
|
|
|
|
default:
|
|
*length = len * resolution / pika_unit_get_factor (unit);
|
|
break;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
parse_svg_viewbox (const gchar *value,
|
|
gdouble *width,
|
|
gdouble *height,
|
|
PikaMatrix3 *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)
|
|
{
|
|
pika_matrix3_identity (matrix);
|
|
pika_matrix3_translate (matrix, -x, -y);
|
|
|
|
if (w > 0.0 && h > 0.0)
|
|
{
|
|
pika_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,
|
|
PikaMatrix3 *matrix)
|
|
{
|
|
gint i;
|
|
|
|
pika_matrix3_identity (matrix);
|
|
|
|
for (i = 0; value[i]; i++)
|
|
{
|
|
PikaMatrix3 trafo;
|
|
gchar keyword[32];
|
|
gdouble args[6];
|
|
gint n_args;
|
|
gint key_len;
|
|
|
|
pika_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;
|
|
|
|
pika_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;
|
|
|
|
pika_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;
|
|
|
|
pika_matrix3_scale (&trafo, args[0], args[1]);
|
|
}
|
|
else if (strcmp (keyword, "rotate") == 0)
|
|
{
|
|
if (n_args == 1)
|
|
{
|
|
pika_matrix3_rotate (&trafo, pika_deg_to_rad (args[0]));
|
|
}
|
|
else if (n_args == 3)
|
|
{
|
|
pika_matrix3_translate (&trafo, -args[1], -args[2]);
|
|
pika_matrix3_rotate (&trafo, pika_deg_to_rad (args[0]));
|
|
pika_matrix3_translate (&trafo, args[1], args[2]);
|
|
}
|
|
else
|
|
return FALSE;
|
|
}
|
|
else if (strcmp (keyword, "skewX") == 0)
|
|
{
|
|
if (n_args != 1)
|
|
return FALSE;
|
|
|
|
pika_matrix3_xshear (&trafo, tan (pika_deg_to_rad (args[0])));
|
|
}
|
|
else if (strcmp (keyword, "skewY") == 0)
|
|
{
|
|
if (n_args != 1)
|
|
return FALSE;
|
|
|
|
pika_matrix3_yshear (&trafo, tan (pika_deg_to_rad (args[0])));
|
|
}
|
|
else
|
|
{
|
|
return FALSE; /* unknown keyword */
|
|
}
|
|
|
|
pika_matrix3_invert (&trafo);
|
|
pika_matrix3_mult (&trafo, matrix);
|
|
}
|
|
|
|
pika_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;
|
|
PikaStroke *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)
|
|
pika_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)
|
|
{
|
|
PikaCoords 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 = pika_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];
|
|
|
|
pika_bezier_stroke_lineto (ctx->stroke, &coords);
|
|
|
|
ctx->param = 0;
|
|
}
|
|
break;
|
|
|
|
case 'c':
|
|
/* curveto */
|
|
if (ctx->param == 6 || final)
|
|
{
|
|
PikaCoords ctrl1 = COORDS_INIT;
|
|
PikaCoords 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];
|
|
|
|
pika_bezier_stroke_cubicto (ctx->stroke, &ctrl1, &ctrl2, &coords);
|
|
|
|
ctx->param = 0;
|
|
}
|
|
break;
|
|
|
|
case 's':
|
|
/* smooth curveto */
|
|
if (ctx->param == 4 || final)
|
|
{
|
|
PikaCoords ctrl1 = COORDS_INIT;
|
|
PikaCoords 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];
|
|
|
|
pika_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;
|
|
|
|
pika_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];
|
|
|
|
pika_bezier_stroke_lineto (ctx->stroke, &coords);
|
|
|
|
ctx->param = 0;
|
|
}
|
|
break;
|
|
|
|
case 'q':
|
|
/* quadratic bezier curveto */
|
|
if (ctx->param == 4 || final)
|
|
{
|
|
PikaCoords 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];
|
|
|
|
pika_bezier_stroke_conicto (ctx->stroke, &ctrl, &coords);
|
|
|
|
ctx->param = 0;
|
|
}
|
|
break;
|
|
|
|
case 't':
|
|
/* truetype quadratic bezier curveto */
|
|
if (ctx->param == 2 || final)
|
|
{
|
|
PikaCoords 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];
|
|
|
|
pika_bezier_stroke_conicto (ctx->stroke, &ctrl, &coords);
|
|
|
|
ctx->param = 0;
|
|
}
|
|
else if (final)
|
|
{
|
|
if (ctx->param > 2)
|
|
{
|
|
PikaCoords 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];
|
|
|
|
pika_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];
|
|
|
|
pika_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];
|
|
|
|
pika_bezier_stroke_arcto (ctx->stroke,
|
|
ctx->params[0], ctx->params[1],
|
|
pika_deg_to_rad (ctx->params[2]),
|
|
ctx->params[3], ctx->params[4],
|
|
&coords);
|
|
ctx->param = 0;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
ctx->param = 0;
|
|
break;
|
|
}
|
|
}
|