/* 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
 *
 * Wilber Cairo rendering
 * Copyright (C) 2008  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 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 <gtk/gtk.h>

#include "libpikamath/pikamath.h"

#include "widgets-types.h"

#include "pikacairo-mascot.h"


static void   pika_cairo_mascot_internal (GtkWidget *widget,
                                          cairo_t   *cr,
                                          gdouble    x,
                                          gdouble    y,
                                          gdouble    factor,
                                          gdouble    max_eye_angle);
static void   pika_cairo_eyes            (GtkWidget *widget,
                                          cairo_t   *cr,
                                          gdouble    x,
                                          gdouble    y,
                                          gdouble    factor,
                                          gdouble    max_eye_angle);


static gboolean  pointer_eyes         = FALSE;
static GSList   *cairo_mascot_widgets = NULL;


void
pika_cairo_mascot_toggle_pointer_eyes (void)
{
  GSList *iter;

  pointer_eyes = ! pointer_eyes;

  for (iter = cairo_mascot_widgets; iter; iter = g_slist_next (iter))
    {
      if (pointer_eyes)
        g_object_set_data (G_OBJECT (iter->data), "mascot-eyes-state", NULL);

      gtk_widget_queue_draw (GTK_WIDGET (iter->data));
    }
}

void
pika_cairo_draw_toolbox_mascot (GtkWidget *widget,
                                cairo_t   *cr)
{
  GtkStyleContext *context;
  GtkAllocation    allocation;
  GdkRGBA          color;
  gdouble          mascot_width;
  gdouble          mascot_height;
  gdouble          factor;

  g_return_if_fail (GTK_IS_WIDGET (widget));
  g_return_if_fail (cr != NULL);

  context = gtk_widget_get_style_context (widget);

  gtk_widget_get_allocation (widget, &allocation);

  pika_cairo_mascot_get_size (cr, &mascot_width, &mascot_height);

  factor = allocation.width / mascot_width * 0.9;

  if (! gtk_widget_get_has_window (widget))
    cairo_translate (cr, allocation.x, allocation.y);

  cairo_scale (cr, factor, factor);

  pika_cairo_mascot_internal (widget, cr,
                              (allocation.width  / factor - mascot_width)  / 2.0,
                              (allocation.height / factor - mascot_height) / 2.0,
                              factor, 30.0 * G_PI / 180.0);

  gtk_style_context_get_color (context, gtk_widget_get_state_flags (widget),
                               &color);
  color.alpha = 0.1;
  gdk_cairo_set_source_rgba (cr, &color);

  cairo_fill (cr);
}

void
pika_cairo_draw_drop_mascot (GtkWidget *widget,
                             cairo_t   *cr,
                             gboolean   blink)
{
  GtkStyleContext *context;
  GtkAllocation    allocation;
  GdkRGBA          color;
  gdouble          mascot_width;
  gdouble          mascot_height;
  gdouble          width;
  gdouble          height;
  gdouble          side;
  gdouble          factor;

  g_return_if_fail (GTK_IS_WIDGET (widget));
  g_return_if_fail (cr != NULL);

  context = gtk_widget_get_style_context (widget);

  gtk_widget_get_allocation (widget, &allocation);

  pika_cairo_mascot_get_size (cr, &mascot_width, &mascot_height);

  mascot_width  /= 2;
  mascot_height /= 2;

  side = MIN (MIN (allocation.width, allocation.height),
              MAX (allocation.width, allocation.height) / 2);

  width  = MAX (mascot_width,  side);
  height = MAX (mascot_height, side);

  factor = MIN (width / mascot_width, height / mascot_height);

  if (! gtk_widget_get_has_window (widget))
    cairo_translate (cr, allocation.x, allocation.y);

  cairo_scale (cr, factor, factor);

  /*  magic factors depend on the image used, everything else is generic
   */
  pika_cairo_mascot_internal (widget, cr,
                              - mascot_width * 0.6,
                              allocation.height / factor - mascot_height * 1.1,
                              factor, 50.0 * G_PI / 180.0);

  gtk_style_context_get_color (context, gtk_widget_get_state_flags (widget),
                               &color);
  color.alpha = 0.15;

  gdk_cairo_set_source_rgba (cr, &color);

  cairo_fill (cr);

  if (blink)
    {
      pika_cairo_eyes (widget, cr,
                       - mascot_width * 0.6,
                       allocation.height / factor - mascot_height * 1.1,
                       factor, 50.0 * G_PI / 180.0);

      cairo_set_source_rgba (cr,
                             color.red,
                             0,
                             0,
                             1.0);
      cairo_fill (cr);
    }
}


/* This string is a path description as found in SVG files.  You can
 * use Inkscape to create the SVG file, then copy the path from it.
 * It works best if you combine all paths into one. Inkscape has a
 * function to do that.
 */
static const gchar mascot_path[] = "m -207.49076,-47.11613 c -0.83216,0.01379 -1.75994,0.132794 -2.79626,0.374018 -3.0213,0.703292 -6.37381,5.151603 -6.05031,14.544176 -3.3946,0.694591 -9.06197,7.571488 -14.96578,13.773355 -5.10251,5.360107 -8.01715,9.8272102 -11.49916,16.1833234 -2.25177,2.9309451 -4.75772,3.6115694 -4.4792,9.7814676 0.1825,4.042811 2.18347,6.848398 4.34722,9.025833 3.50452,3.526666 6.90015,5.679308 10.28542,7.064055 4.20093,0.376871 7.16515,-0.637152 8.91693,-2.241495 1.79411,-1.643108 2.47524,-3.929753 1.86602,-6.6366 -0.17079,-0.760554 0.30681,-1.515692 1.06717,-1.687331 0.76085,-0.171332 1.51658,0.306392 1.6882,1.067174 0.79537,3.533949 -0.17735,7.017482 -2.71362,9.340293 -0.99661,0.91273 -2.21366,1.638713 -3.6202,2.15653 2.61496,0.458532 5.26434,0.718712 7.99253,1.048777 15.53163,1.879066 28.88302,-5.611083 38.57054,-21.5296844 16.58511,-10.995142 20.90751,-22.4499396 17.3264,-28.5055596 -6.37546,-10.780846 -20.0562,-5.820933 -29.08892,-3.471589 -1.94526,-2.902299 -4.67843,-4.841714 -7.86641,-6.2255 -1.82789,-4.48163 -0.9364,-14.194573 -8.98057,-14.061243 z m 29.60192,29.535646 c 1.73264,0.02249 3.3123,0.638684 4.04503,1.930544 3.44158,6.0677904 -2.18521,8.8680325 -5.76388,9.9108149 -1.6963,0.4942794 -0.35659,-2.7174403 -1.50163,-4.7086889 -1.16512,-2.026143 -3.8958,-2.136121 -3.19217,-3.684144 1.02102,-2.246332 3.88034,-3.481403 6.41265,-3.448526 z m -35.13515,7.116319 c 1.43053,0.0011 2.46779,0.5782959 3.18604,1.2376872 0.95765,0.8791847 1.21517,1.1382517 1.34834,1.9045536 0.1303,0.7497755 -0.0888,0.9983955 -0.61345,2.1991595 -0.52462,1.2007584 -1.485,2.6039192 -3.22895,3.4134837 -1.91429,0.88863155 -3.56872,0.6194623 -4.7446,0.1281774 -1.17588,-0.4912848 -1.54865,-0.5370616 -1.87331,-1.2046908 -0.47615,-0.97914 -0.15911,-1.5456442 0.30657,-3.2520216 0.46568,-1.7063774 1.55605,-3.5736123 4.05495,-4.217003 0.56701,-0.145986 1.08757,-0.209702 1.56441,-0.209346 z m -32.03934,15.4650644 c 1.1541,-0.06197 3.06966,1.7813368 4.21963,2.5720075 1.47196,1.012063 4.80239,2.0588852 4.0678,4.0181711 -0.64273,1.714304 -4.04364,2.034381 -6.83193,-0.256067 -1.9952,-1.638955 -2.61532,-4.8667661 -2.2123,-5.7688505 0.17023,-0.381035 0.43365,-0.5479098 0.7568,-0.5652611 z ";


static const gchar eyes_path[] = "M z"; // #heckimp skip


static cairo_path_t *mascot_cairo_path = NULL;
static gdouble       mascot_x1, mascot_y1;
static gdouble       mascot_x2, mascot_y2;

static cairo_path_t *eyes_cairo_path = NULL;
static gdouble       eyes_x1, eyes_y1;
static gdouble       eyes_x2, eyes_y2;


static void  parse_path_data    (cairo_t     *cr,
                                 const gchar *data);
static void  mascot_get_extents (cairo_t     *cr);
static void  eyes_get_extents   (cairo_t     *cr);


/**
 * pika_cairo_mascot:
 * @cr: Cairo context
 * @x: x position
 * @y: y position
 *
 * Draw a Wilber path at position @x, @y.
 */
void
pika_cairo_mascot (cairo_t *cr,
                   gdouble  x,
                   gdouble  y)
{
  pika_cairo_mascot_internal (NULL, cr, x, y, 1.0, 0.0);
}

static void
pika_cairo_mascot_weak_notify (gpointer  data,
                               GObject  *widget)
{
  cairo_mascot_widgets = g_slist_remove (cairo_mascot_widgets, widget);
}

static void
pika_cairo_mascot_internal (GtkWidget *widget,
                            cairo_t   *cr,
                            gdouble    x,
                            gdouble    y,
                            gdouble    factor,
                            gdouble    max_eye_angle)
{
  mascot_get_extents (cr);

  cairo_save (cr);

  cairo_translate (cr, x - mascot_x1, y - mascot_y1);
  cairo_append_path (cr, mascot_cairo_path);

  cairo_restore (cr);

  pika_cairo_eyes (widget, cr, x, y, factor, max_eye_angle);

  if (widget && ! g_slist_find (cairo_mascot_widgets, widget))
    {
      cairo_mascot_widgets = g_slist_prepend (cairo_mascot_widgets, widget);

      g_object_weak_ref (G_OBJECT (widget),
                         pika_cairo_mascot_weak_notify, NULL);
    }
}

typedef struct
{
  gdouble x;
  gdouble y;
  gdouble radius;

  gdouble a;
  gdouble b;
  gdouble r;
} Eye;

static const Eye eyes[2] =
{
  { .x      = (344.151 + 382.496) / 2.0,
    .y      = (501.128 + 539.473) / 2.0,
    .radius = (382.496 - 344.151) / 2.0,

    .a      = 25.0 * G_PI / 180.0,
    .b      = 24.0 * G_PI / 180.0,
    .r      = 0.475
  },

  { .x      = (385.287 + 442.878) / 2.0,
    .y      = (491.431 + 549.022) / 2.0,
    .radius = (442.878 - 385.287) / 2.0,

    .a      = 34.0 * G_PI / 180.0,
    .b      = 19.0 * G_PI / 180.0,
    .r      = 0.5
  }
};

typedef struct
{
  gdouble a;
  gdouble b;
} EyeState;

typedef struct
{
  EyeState eyes[2];
  gdouble  x;
  gdouble  y;
  gdouble  factor;
  gdouble  max_eye_angle;
  gdouble  t;
  gint     timeout_id;
} EyesState;

static EyesState *
eyes_state_new (void)
{
  EyesState *state = g_slice_new0 (EyesState);
  gint       i;

  for (i = 0; i < 2; i++)
    {
      state->eyes[i].a = eyes[i].a;
      state->eyes[i].b = eyes[i].b;
    }

  state->t = (gdouble) g_get_monotonic_time () / G_TIME_SPAN_SECOND;

  return state;
}

static void
eyes_state_free (EyesState *state)
{
  if (state->timeout_id)
    g_source_remove (state->timeout_id);

  g_slice_free (EyesState, state);
}

static gboolean
pika_cairo_pointer_eyes_timeout (GtkWidget *widget)
{
  GdkSeat       *seat;
  EyesState     *state;
  gdouble        t;
  gint           pointer_x;
  gint           pointer_y;
  GtkAllocation  allocation;
  GdkWindow     *window;
  gint           window_x;
  gint           window_y;
  gint           redraw = 2;
  gint           i;

  state = g_object_get_data (G_OBJECT (widget), "mascot-eyes-state");

  t = (gdouble) g_get_monotonic_time () / G_TIME_SPAN_SECOND;

  seat = gdk_display_get_default_seat (gdk_display_get_default ());

  gdk_device_get_position (gdk_seat_get_pointer (seat),
                           NULL, &pointer_x, &pointer_y);

  gtk_widget_get_allocation (widget, &allocation);

  window = gtk_widget_get_window (widget);

  if (window)
    gdk_window_get_origin (window, &window_x, &window_y);

  for (i = 0; i < 2; i++)
    {
      const Eye   *eye = &eyes[i];
      gdouble      a;
      gdouble      b;
      gdouble      c;
      PikaVector3  u;
      PikaVector3  v;
      PikaVector3  w;

      if (pointer_eyes)
        {
          gdouble screen_x;
          gdouble screen_y;
          gdouble z = 220.0 * state->factor;
          gdouble d;

          screen_x = (eye->x + state->x - mascot_x1) * state->factor;
          screen_y = (eye->y + state->y - mascot_y1) * state->factor;

          if (! gtk_widget_get_has_window (widget))
            {
              screen_x += allocation.x;
              screen_y += allocation.y;
            }

          if (window)
            {
              screen_x += window_x;
              screen_y += window_y;
            }

          d = sqrt (SQR (pointer_x - screen_x) + SQR (pointer_y - screen_y));
          a = atan2 (pointer_y - screen_y, pointer_x - screen_x);
          b = atan (d / z);
          b = MIN (b, state->max_eye_angle);
        }
      else
        {
          a = eyes[i].a;
          b = eyes[i].b;
        }

      if (a == state->eyes[i].a && b == state->eyes[i].b)
        {
          redraw--;

          continue;
        }

      u.x = sin (state->eyes[i].b) * cos (state->eyes[i].a);
      u.y = sin (state->eyes[i].b) * sin (state->eyes[i].a);
      u.z = cos (state->eyes[i].b);

      v.x = sin (b) * cos (a);
      v.y = sin (b) * sin (a);
      v.z = cos (b);

      c = acos (pika_vector3_inner_product (&u, &v));

      if (c < 1e-2)
        {
          state->eyes[i].a = a;
          state->eyes[i].b = b;

          continue;
        }

      c *= 1.0 - exp (-(t - state->t) * 15.0);

      w = pika_vector3_cross_product (&u, &v);
      w = pika_vector3_cross_product (&w, &u);
      pika_vector3_normalize (&w);

      v.x = u.x * cos (c) + w.x * sin (c);
      v.y = u.y * cos (c) + w.y * sin (c);
      v.z = u.z * cos (c) + w.z * sin (c);

      a = atan2 (v.y, v.x);
      b = acos (v.z);

      state->eyes[i].a = a;
      state->eyes[i].b = b;
    }

  state->t = t;

  if (redraw)
    {
      state->timeout_id = 0;

      gtk_widget_queue_draw (widget);

      return G_SOURCE_REMOVE;
    }
  else if (! pointer_eyes)
    {
      state->timeout_id = 0;

      g_object_set_data (G_OBJECT (widget), "mascot-eyes-state", NULL);
      gtk_widget_queue_draw (widget);

      return G_SOURCE_REMOVE;
    }

  return G_SOURCE_CONTINUE;
}

static void
pika_cairo_pointer_eyes (GtkWidget *widget,
                         cairo_t   *cr,
                         gdouble    x,
                         gdouble    y,
                         gdouble    factor,
                         gdouble    max_eye_angle)
{
  EyesState *state;
  gint       i;

  state = g_object_get_data (G_OBJECT (widget), "mascot-eyes-state");

  if (! state)
    {
      state = eyes_state_new ();

      g_object_set_data_full (G_OBJECT (widget), "mascot-eyes-state", state,
                              (GDestroyNotify) eyes_state_free);
    }

  for (i = 0; i < 2; i++)
    {
      const Eye *eye = &eyes[i];
      gdouble    R = eye->radius;
      gdouble    r = eye->r * eye->radius;
      gint       j;

      cairo_save (cr);

      cairo_translate (cr, eye->x, eye->y);
      cairo_rotate (cr, state->eyes[i].a);

      for (j = 0; j < 32; j++)
        {
          gdouble a = -2.0 * G_PI * j / 32.0;
          gdouble u = r * cos (a);
          gdouble v = r * sin (a);
          gdouble w = sqrt (SQR (R) - SQR (v));
          gdouble b = asin (u / w);

          b = CLAMP (b + state->eyes[i].b, -G_PI / 2.0, +G_PI / 2.0);
          u = w * sin (b);

          if (j == 0)
            cairo_move_to (cr, u, v);
          else
            cairo_line_to (cr, u, v);
        }

      cairo_close_path (cr);

      cairo_restore (cr);
    }

  state->x             = x;
  state->y             = y;
  state->factor        = factor;
  state->max_eye_angle = max_eye_angle;

  if (! state->timeout_id)
    {
      state->timeout_id =
        g_timeout_add (17,
                       (GSourceFunc) pika_cairo_pointer_eyes_timeout,
                       widget);
    }
}

static void
pika_cairo_eyes (GtkWidget *widget,
                 cairo_t   *cr,
                 gdouble    x,
                 gdouble    y,
                 gdouble    factor,
                 gdouble    max_eye_angle)
{
  mascot_get_extents (cr);
  eyes_get_extents (cr);

  cairo_save (cr);

  cairo_translate (cr, x - mascot_x1, y - mascot_y1);
  if (widget        &&
      (pointer_eyes ||
       g_object_get_data (G_OBJECT (widget), "mascot-eyes-state")))
    {
      pika_cairo_pointer_eyes (widget, cr, x, y, factor, max_eye_angle);
    }
  else
    {
      cairo_append_path (cr, eyes_cairo_path);
    }

  cairo_restore (cr);
}

void
pika_cairo_mascot_get_size (cairo_t *cr,
                            gdouble *width,
                            gdouble *height)
{
  mascot_get_extents (cr);

  *width  = mascot_x2 - mascot_x1;
  *height = mascot_y2 - mascot_y1;
}


static void
mascot_get_extents (cairo_t *unused)
{
  if (! mascot_cairo_path)
    {
      cairo_surface_t *s  = cairo_image_surface_create (CAIRO_FORMAT_A8, 1, 1);
      cairo_t         *cr = cairo_create (s);

      parse_path_data (cr, mascot_path);
      cairo_fill_extents (cr, &mascot_x1, &mascot_y1, &mascot_x2, &mascot_y2);

      mascot_cairo_path = cairo_copy_path (cr);

      cairo_destroy (cr);
      cairo_surface_destroy (s);
    }
}

static void
eyes_get_extents (cairo_t *unused)
{
  if (! eyes_cairo_path)
    {
      cairo_surface_t *s  = cairo_image_surface_create (CAIRO_FORMAT_A8, 1, 1);
      cairo_t         *cr = cairo_create (s);

      parse_path_data (cr, eyes_path);
      cairo_fill_extents (cr, &eyes_x1, &eyes_y1, &eyes_x2, &eyes_y2);

      eyes_cairo_path = cairo_copy_path (cr);

      cairo_destroy (cr);
      cairo_surface_destroy (s);
    }
}

/**********************************************************/
/*  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
{
  cairo_t     *cr;
  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 void
parse_path_data (cairo_t     *cr,
                 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));

  ctx.cr = cr;

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

          cairo_close_path (ctx.cr);
        }
      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 , */
    }
}

/* 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)
{
  switch (ctx->cmd)
    {
    case 'm':
      /* moveto */
      if (ctx->param == 2 || final)
        {
          parse_path_default_xy (ctx, 2);

          ctx->cpx = ctx->rpx = ctx->params[0];
          ctx->cpy = ctx->rpy = ctx->params[1];

          cairo_move_to (ctx->cr, ctx->cpx, ctx->cpy);

          ctx->param = 0;
        }
      break;

    case 'l':
      /* lineto */
      if (ctx->param == 2 || final)
        {
          parse_path_default_xy (ctx, 2);

          ctx->cpx = ctx->rpx = ctx->params[0];
          ctx->cpy = ctx->rpy = ctx->params[1];

          cairo_line_to (ctx->cr, ctx->cpx, ctx->cpy);

          ctx->param = 0;
        }
      break;

    case 'c':
      /* curveto */
      if (ctx->param == 6 || final)
        {
          gdouble x, y;

          parse_path_default_xy (ctx, 6);

          x        = ctx->params[0];
          y        = ctx->params[1];
          ctx->rpx = ctx->params[2];
          ctx->rpy = ctx->params[3];
          ctx->cpx = ctx->params[4];
          ctx->cpy = ctx->params[5];

          cairo_curve_to (ctx->cr,
                          x, y, ctx->rpx, ctx->rpy, ctx->cpx, ctx->cpy);

          ctx->param = 0;
        }
      break;

    case 's':
      /* smooth curveto */
      if (ctx->param == 4 || final)
        {
          gdouble x, y;

          parse_path_default_xy (ctx, 4);

          x = 2 * ctx->cpx - ctx->rpx;
          y = 2 * ctx->cpy - ctx->rpy;
          ctx->rpx = ctx->params[0];
          ctx->rpy = ctx->params[1];
          ctx->cpx = ctx->params[2];
          ctx->cpy = ctx->params[3];

          cairo_curve_to (ctx->cr,
                          x, y, ctx->rpx, ctx->rpy, ctx->cpx, ctx->cpy);

          ctx->param = 0;
        }
      break;

    case 'h':
      /* horizontal lineto */
      if (ctx->param == 1)
        {
          ctx->cpx = ctx->rpx = ctx->params[0];

          cairo_line_to (ctx->cr, ctx->cpx, ctx->cpy);

          ctx->param = 0;
        }
      break;

    case 'v':
      /* vertical lineto */
      if (ctx->param == 1)
        {
          ctx->cpy = ctx->rpy = ctx->params[0];

          cairo_line_to (ctx->cr, ctx->cpx, ctx->cpy);

          ctx->param = 0;
        }
      break;

    case 'q':
      /* quadratic bezier curveto */
      if (ctx->param == 4 || final)
        {
          parse_path_default_xy (ctx, 4);

          ctx->rpx = ctx->params[0];
          ctx->rpy = ctx->params[1];
          ctx->cpx = ctx->params[2];
          ctx->cpy = ctx->params[3];

          g_warning ("quadratic bezier curveto not implemented");

          ctx->param = 0;
        }
      break;

    case 't':
      /* truetype quadratic bezier curveto */
      if (ctx->param == 2 || final)
        {
          parse_path_default_xy (ctx, 2);

          ctx->rpx = 2 * ctx->cpx - ctx->rpx;
          ctx->rpy = 2 * ctx->cpy - ctx->rpy;
          ctx->cpx = ctx->params[0];
          ctx->cpy = ctx->params[1];

          g_warning ("truetype quadratic bezier curveto not implemented");

          ctx->param = 0;
        }
      else if (final)
        {
          if (ctx->param > 2)
            {
              parse_path_default_xy (ctx, 4);

              ctx->rpx = ctx->params[0];
              ctx->rpy = ctx->params[1];
              ctx->cpx = ctx->params[2];
              ctx->cpy = ctx->params[3];

              g_warning ("conicto not implemented");
            }
          else
            {
              parse_path_default_xy (ctx, 2);

              ctx->cpx = ctx->rpx = ctx->params[0];
              ctx->cpy = ctx->rpy = ctx->params[1];

              cairo_line_to (ctx->cr, ctx->cpx, ctx->cpy);
            }

          ctx->param = 0;
        }
      break;

    case 'a':
      if (ctx->param == 7 || final)
        {
          ctx->cpx = ctx->rpx = ctx->params[5];
          ctx->cpy = ctx->rpy = ctx->params[6];

          g_warning ("arcto not implemented");

          ctx->param = 0;
        }
      break;

    default:
      ctx->param = 0;
      break;
    }
}
