Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Gimp/app/path/   (Gimp Bildeditor Version 3.2.0©)  Datei vom 14.2.2026 mit Größe 53 kB image not shown  

Quelle  gimppath-import.c   Sprache: C

 

/* GIMP - The GNU Image Manipulation Program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * GimpPath 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 "path-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 "gimppath.h"
#include "gimppath-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  \
  }

#define GOT_SIGN          0x1
#define GOT_EXPONENT_SIGN 0x2

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;


typedef enum
{
  IN_PREINTEGER,
  IN_INTEGER,
  IN_FRACTION,
  IN_PREEXPONENT,
  IN_EXPONENT
} SvgNumberInput;


static gboolean  gimp_path_import  (GimpImage            *image,
                                    GFile                *file,
                                    const gchar          *str,
                                    gsize                 len,
                                    gboolean              merge,
                                    gboolean              scale,
                                    GimpPath             *parent,
                                    gint                  position,
                                    GList               **ret_paths,
                                    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_text          (GMarkupParseContext  *context,
                                       const gchar          *text,
                                       gsize                 text_len,
                                       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,
  svg_parser_text,
  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                },
  { "title",    NULL,                      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_path_import_file:
 * @image:    the #GimpImage to add the paths to
 * @file:     a SVG file
 * @merge:    should multiple paths be merged into a single #GimpPath object
 * @scale:    should the SVG be scaled to fit the image dimensions
 * @position: position in the image's path stack where to add the paths
 * @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
gimp_path_import_file (GimpImage    *image,
                       GFile        *file,
                       gboolean      merge,
                       gboolean      scale,
                       GimpPath     *parent,
                       gint          position,
                       GList       **ret_paths,
                       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_PATH (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_paths == NULL || *ret_paths == NULL, FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  return gimp_path_import (image, file, NULL, 0, merge, scale,
                           parent, position,
                           ret_paths, error);
}

/**
 * gimp_path_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 #GimpPath 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
gimp_path_import_buffer (GimpImage    *image,
                         const gchar  *buffer,
                         gsize         len,
                         gboolean      merge,
                         gboolean      scale,
                         GimpPath     *parent,
                         gint          position,
                         GList       **ret_paths,
                         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_PATH (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_paths == NULL || *ret_paths == NULL, FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  return gimp_path_import (image, NULL, buffer, len, merge, scale,
                           parent, position,
                           ret_paths, error);
}

static gboolean
gimp_path_import (GimpImage    *image,
                  GFile        *file,
                  const gchar  *str,
                  gsize         len,
                  gboolean      merge,
                  gboolean      scale,
                  GimpPath     *parent,
                  gint          position,
                  GList       **ret_paths,
                  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)
        {
          GimpPath *path = NULL;

          base->paths = g_list_reverse (base->paths);

          merge = merge && base->paths->next;

          gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_PATHS_IMPORT,
                                       _("Import Paths"));

          for (paths = base->paths; paths; paths = paths->next)
            {
              SvgPath *svg_path = paths->data;
              GList   *list;

              if (! merge || ! path)
                {
                  path = gimp_path_new (image,
                                        ((merge || ! svg_path->id) ?
                                         _("Imported Path") : svg_path->id));
                  gimp_image_add_path (image, path,
                                       parent, position, TRUE);
                  gimp_path_freeze (path);

                  if (ret_paths)
                    *ret_paths = g_list_prepend (*ret_paths, path);

                  if (position != -1)
                    position++;
                }

              for (list = svg_path->strokes; list; list = list->next)
                gimp_path_stroke_add (path, GIMP_STROKE (list->data));

              if (! merge)
                gimp_path_thaw (path);

              g_list_free_full (svg_path->strokes, g_object_unref);
              svg_path->strokes = NULL;
            }

          if (merge)
            gimp_path_thaw (path);

          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 *svg_path = paths->data;
          GList   *list;

          g_free (svg_path->id);

          for (list = svg_path->strokes; list; list = list->next)
            g_object_unref (list->data);

          g_list_free (svg_path->strokes);

          g_slice_free (SvgPath, svg_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_text (GMarkupParseContext *context,
                  const gchar         *text,
                  gsize                text_len,
                  gpointer             user_data,
                  GError             **error)
{
  SvgParser  *parser = user_data;
  SvgHandler *handler;

  handler = g_queue_pop_head (parser->stack);

  if (handler       &&
      handler->name &&
      strcmp (handler->name, "title") == 0)
    {
      gchar      *title = g_uri_unescape_string (text, NULL);
      SvgHandler *prior_handler;
      GList      *paths;

      prior_handler = g_queue_pop_head (parser->stack);

      /* Replace path ID with title name if imported */
      if (prior_handler && prior_handler->paths)
        {
          for (paths = prior_handler->paths; paths; paths = paths->next)
            {
              SvgPath *svg_path = paths->data;

              if (svg_path->id)
                g_free (svg_path->id);

              svg_path->id = g_strdup (title);
            }
          g_list_free (paths);
        }

      g_queue_push_head (parser->stack, prior_handler);
      g_free (title);
    }
  g_queue_push_head (parser->stack, handler);
}


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 *svg_path = paths->data;
              GList   *list;

              for (list = svg_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_PATH_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 (gimp_unit_get_id (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_PATH_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;
  gchar *safe_value;

  safe_value = g_strstrip (g_strdup (value));

  gimp_matrix3_identity (matrix);

  for (i = 0; safe_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 (safe_value[i]))
        i++;

      /* parse keyword */
      for (key_len = 0; key_len < sizeof (keyword); key_len++)
        {
          gchar c = safe_value[i];

          if (g_ascii_isalpha (c) || c == '-')
            keyword[key_len] = safe_value[i++];
          else
            break;
        }

      if (key_len >= sizeof (keyword))
        goto out;

      keyword[key_len] = '\0';

      /* skip whitespace */
      while (g_ascii_isspace (safe_value[i]))
        i++;

      if (safe_value[i] != '(')
        goto out;
      i++;

      for (n_args = 0; ; n_args++)
        {
          gchar  c;
          gchar *end_ptr;

          /* skip whitespace */
          while (g_ascii_isspace (safe_value[i]))
            i++;

          c = safe_value[i];
          if (g_ascii_isdigit (c) || c == '+' || c == '-' || c == '.')
            {
              if (n_args == G_N_ELEMENTS (args))
                goto out; /* too many args */

              args[n_args] = g_ascii_strtod (safe_value + i, &end_ptr);
              i            = end_ptr - safe_value;

              while (g_ascii_isspace (safe_value[i]))
                i++;

              /* skip optional comma */
              if (safe_value[i] == ',')
                i++;
            }
          else if (c == ')')
            {
              break;
            }
          else
            {
              goto out;
            }
        }

      /* OK, have parsed keyword and args, now calculate the transform matrix */

      if (strcmp (keyword, "matrix") == 0)
        {
          if (n_args != 6)
            goto out;

          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)
            goto out;

          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)
            goto out;

          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
            goto out;
        }
      else if (strcmp (keyword, "skewX") == 0)
        {
          if (n_args != 1)
            goto out;

          gimp_matrix3_xshear (&trafo, tan (gimp_deg_to_rad (args[0])));
        }
      else if (strcmp (keyword, "skewY") == 0)
        {
          if (n_args != 1)
            goto out;

          gimp_matrix3_yshear (&trafo, tan (gimp_deg_to_rad (args[0])));
        }
      else
        {
          goto out; /* unknown keyword */
        }

      gimp_matrix3_invert (&trafo);
      gimp_matrix3_mult (&trafo, matrix);
    }

  gimp_matrix3_invert (matrix);

  return TRUE;

out:
  g_free (safe_value);
  return FALSE;
}


/**********************************************************/
/*  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 gint  parse_number          (ParsePathContext *ctx,
                                    const char       *data);
static void  path_end_of_number    (ParsePathContext *ctx,
                                    gdouble           val,
                                    gint              sign,
                                    gint              exp_sign,
                                    gint              exp);


static GList *
parse_path_data (const gchar *data)
{
  ParsePathContext ctx;

  gchar c = 0;
  gint  i;

  memset (&ctx, 0, sizeof (ParsePathContext));

  for (i = 0; data[i] != '\0'; i++)
    {
      c = data[i];

      if ((c >= '0' && c <= '9') || c == '+' || c == '-' || c == '.')
        {
          /* digit */
          i += parse_number (&ctx, data + i) - 1;
        }
      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;
    }
}

static gint
parse_number (ParsePathContext *ctx,
              const char       *data)
{
  gint     length        = 0;
  /* Current location within the number */
  gint     in            = IN_PREINTEGER;
  /* [bitfield] Having 2 of each of these is an error */
  gint     got           = 0x0;
  /* Set to true if the number should end after a char */
  gboolean end           = FALSE;
  /* Set to true if the number ended due to an error */
  gboolean error         = FALSE;
  gdouble  value         = 0.0;
  gdouble  fraction      = 1.0;
  /* Presume the INTEGER is positive if it has no sign */
  gint     sign          = +1;
  gint     exponent      = 0;
  /* Presume the EXPONENT is positive if it has no sign */
  gint     exponent_sign = +1;

  while (data[length] != '\0' && ! end && ! error)
    {
      gchar c = data[length];

      switch (in)
        {
        case IN_PREINTEGER: /* No numbers yet, we're just starting out */
          /* LEGAL: + - .->FRACTION DIGIT->INTEGER */
          if (c == '+' || c == '-')
            {
              if (got & GOT_SIGN)
                {
                  error = TRUE; /* Two signs: not allowed */
                }
              else
                {
                  sign = c == '+' ? +1 : -1;
                  got |= GOT_SIGN;
                }
            }
          else if (c == '.')
            {
              in = IN_FRACTION;
            }
          else if (c >= '0' && c <= '9')
            {
              value = c - '0';
              in    = IN_INTEGER;
            }
          break;

        case IN_INTEGER: /* Previous character(s) was/were digit(s) */
          /* LEGAL: DIGIT .->FRACTION E->PREEXPONENT */
          if (c >= '0' && c <= '9')
            value = value * 10 + (c - '0');
          else if (c == '.')
            in = IN_FRACTION;
          else if (c == 'e' || c == 'E')
            in = IN_PREEXPONENT;
          else
            end = TRUE;
          break;

        case IN_FRACTION: /* Previously, digit(s) in the fractional part */
          /* LEGAL: DIGIT E->PREEXPONENT */
          if (c >= '0' && c <= '9')
            {
              fraction *= 0.1;
              value    += fraction * (c - '0');
            }
          else if (c == 'e' || c == 'E')
            {
              in = IN_PREEXPONENT;
            }
          else
            {
              end = TRUE;
            }
          break;

        case IN_PREEXPONENT: /* Right after E */
          /* LEGAL: + - DIGIT->EXPONENT */
          if (c == '+' || c == '-')
            {
              if (got & GOT_EXPONENT_SIGN)
                {
                  error = TRUE; /* Two signs: not allowed */
                }
              else
                {
                  exponent_sign = c == '+' ? +1 : -1;
                  got |= GOT_EXPONENT_SIGN;
                }
            }
          else if (c >= '0' && c <= '9')
            {
              exponent = c - '0';
              in       = IN_EXPONENT;
            }
          break;

        case IN_EXPONENT: /* After E and the sign, if applicable */
          /* LEGAL: DIGIT */
          if (c >= '0' && c <= '9')
            exponent = exponent * 10 + (c - '0');
          else
            end = TRUE;
          break;
        }
      length++;
    }

  path_end_of_number (ctx, value, sign, exponent_sign, exponent);

  return end ? length - 1 : length;
}

static void
path_end_of_number (ParsePathContext *ctx,
                    gdouble           val,
                    gint              sign,
                    gint              exp_sign,
                    gint              exp)
{
  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);
}

Messung
CHZG
839610093

¤ Dauer der Verarbeitung: 0.19 Sekunden  (vorverarbeitet am  2026-11-04) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover


Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.