1 /* GIMP - The GNU Image Manipulation Program
2  * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3  *
4  * Wilber Cairo rendering
5  * Copyright (C) 2008  Sven Neumann <sven@gimp.org>
6  *
7  * Some code here is based on code from librsvg that was originally
8  * written by Raph Levien <raph@artofcode.com> for Gill.
9  *
10  * This program is free software: you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 3 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
22  */
23 
24 #include "config.h"
25 
26 #include <string.h>
27 
28 #include <gtk/gtk.h>
29 
30 #include "libgimpmath/gimpmath.h"
31 
32 #include "widgets-types.h"
33 
34 #include "gimpcairo-wilber.h"
35 
36 
37 static void   gimp_cairo_wilber_internal (GtkWidget *widget,
38                                           cairo_t   *cr,
39                                           gdouble    x,
40                                           gdouble    y,
41                                           gdouble    factor,
42                                           gdouble    max_eye_angle);
43 static void   gimp_cairo_eyes            (GtkWidget *widget,
44                                           cairo_t   *cr,
45                                           gdouble    x,
46                                           gdouble    y,
47                                           gdouble    factor,
48                                           gdouble    max_eye_angle);
49 
50 
51 static gboolean  pointer_eyes         = FALSE;
52 static GSList   *cairo_wilber_widgets = NULL;
53 
54 
55 void
gimp_cairo_wilber_toggle_pointer_eyes(void)56 gimp_cairo_wilber_toggle_pointer_eyes (void)
57 {
58   GSList *iter;
59 
60   pointer_eyes = ! pointer_eyes;
61 
62   for (iter = cairo_wilber_widgets; iter; iter = g_slist_next (iter))
63     {
64       if (pointer_eyes)
65         g_object_set_data (G_OBJECT (iter->data), "wilber-eyes-state", NULL);
66 
67       gtk_widget_queue_draw (GTK_WIDGET (iter->data));
68     }
69 }
70 
71 void
gimp_cairo_draw_toolbox_wilber(GtkWidget * widget,cairo_t * cr)72 gimp_cairo_draw_toolbox_wilber (GtkWidget *widget,
73                                 cairo_t   *cr)
74 {
75   GtkStyle     *style;
76   GtkStateType  state;
77   GtkAllocation allocation;
78   gdouble       wilber_width;
79   gdouble       wilber_height;
80   gdouble       factor;
81 
82   g_return_if_fail (GTK_IS_WIDGET (widget));
83   g_return_if_fail (cr != NULL);
84 
85   style = gtk_widget_get_style (widget);
86   state = gtk_widget_get_state (widget);
87 
88   gtk_widget_get_allocation (widget, &allocation);
89 
90   gimp_cairo_wilber_get_size (cr, &wilber_width, &wilber_height);
91 
92   factor = allocation.width / wilber_width * 0.9;
93 
94   if (! gtk_widget_get_has_window (widget))
95     cairo_translate (cr, allocation.x, allocation.y);
96 
97   cairo_scale (cr, factor, factor);
98 
99   gimp_cairo_wilber_internal (widget, cr,
100                               (allocation.width  / factor - wilber_width)  / 2.0,
101                               (allocation.height / factor - wilber_height) / 2.0,
102                               factor, 30.0 * G_PI / 180.0);
103 
104   cairo_set_source_rgba (cr,
105                          style->fg[state].red   / 65535.0,
106                          style->fg[state].green / 65535.0,
107                          style->fg[state].blue  / 65535.0,
108                          0.10);
109   cairo_fill (cr);
110 }
111 
112 void
gimp_cairo_draw_drop_wilber(GtkWidget * widget,cairo_t * cr,gboolean blink)113 gimp_cairo_draw_drop_wilber (GtkWidget *widget,
114                              cairo_t   *cr,
115                              gboolean   blink)
116 {
117   GtkStyle     *style;
118   GtkStateType  state;
119   GtkAllocation allocation;
120   gdouble       wilber_width;
121   gdouble       wilber_height;
122   gdouble       width;
123   gdouble       height;
124   gdouble       side;
125   gdouble       factor;
126 
127   g_return_if_fail (GTK_IS_WIDGET (widget));
128   g_return_if_fail (cr != NULL);
129 
130   style  = gtk_widget_get_style (widget);
131   state  = gtk_widget_get_state (widget);
132 
133   gtk_widget_get_allocation (widget, &allocation);
134 
135   gimp_cairo_wilber_get_size (cr, &wilber_width, &wilber_height);
136 
137   wilber_width  /= 2;
138   wilber_height /= 2;
139 
140   side = MIN (MIN (allocation.width, allocation.height),
141               MAX (allocation.width, allocation.height) / 2);
142 
143   width  = MAX (wilber_width,  side);
144   height = MAX (wilber_height, side);
145 
146   factor = MIN (width / wilber_width, height / wilber_height);
147 
148   if (! gtk_widget_get_has_window (widget))
149     cairo_translate (cr, allocation.x, allocation.y);
150 
151   cairo_scale (cr, factor, factor);
152 
153   /*  magic factors depend on the image used, everything else is generic
154    */
155   gimp_cairo_wilber_internal (widget, cr,
156                               - wilber_width * 0.6,
157                               allocation.height / factor - wilber_height * 1.1,
158                               factor, 50.0 * G_PI / 180.0);
159 
160   cairo_set_source_rgba (cr,
161                          style->fg[state].red   / 65535.0,
162                          style->fg[state].green / 65535.0,
163                          style->fg[state].blue  / 65535.0,
164                          0.15);
165   cairo_fill (cr);
166 
167   if (blink)
168     {
169       gimp_cairo_eyes (widget, cr,
170                        - wilber_width * 0.6,
171                        allocation.height / factor - wilber_height * 1.1,
172                        factor, 50.0 * G_PI / 180.0);
173 
174       cairo_set_source_rgba (cr,
175                              style->fg[state].red   / 65535.0,
176                              0,
177                              0,
178                              1.0);
179       cairo_fill (cr);
180     }
181 }
182 
183 
184 /* This string is a path description as found in SVG files.  You can
185  * use Inkscape to create the SVG file, then copy the path from it.
186  * It works best if you combine all paths into one. Inkscape has a
187  * function to do that.
188  */
189 static const gchar wilber_path[] =
190   "M 509.72445,438.68864 C 501.47706,469.77945 464.95038,491.54566 431.85915,497.74874 C 438.5216,503.01688 442.87782,511.227 442.87782,520.37375 C 442.87783,536.24746 429.95607,549.0223 414.08235,549.0223 C 398.20863,549.0223 385.28688,536.24746 385.28688,520.37375 C 385.28688,511.52403 389.27666,503.61286 395.57098,498.3364 C 359.36952,495.90384 343.70976,463.95812 343.70975,463.95814 L 342.68134,509.64891 C 342.68134,514.35021 342.08391,519.96098 340.18378,528.3072 C 339.84664,527.80364 339.51399,527.33515 339.15537,526.83804 C 330.25511,514.5011 317.25269,507.81431 306.39317,508.76741 C 302.77334,509.08511 299.47017,510.33348 296.54982,512.4403 C 284.86847,520.86757 284.97665,540.94721 296.84366,557.3965 C 306.96274,571.42287 322.32232,578.25612 333.8664,574.73254 C 391.94635,615.17624 532.16931,642.41915 509.72445,438.68864 z M 363.24953,501.1278 C 373.83202,501.12778 382.49549,509.79127 382.49549,520.37375 C 382.49549,530.95624 373.83201,539.47279 363.24953,539.47279 C 352.66706,539.47279 344.1505,530.95624 344.1505,520.37375 C 344.15049,509.79129 352.66706,501.1278 363.24953,501.1278 z M 305.80551,516.1132 C 311.68466,516.11318 316.38344,521.83985 316.38344,528.89486 C 316.38345,535.94982 311.68467,541.67652 305.80551,541.67652 C 299.92636,541.67652 295.08067,535.94987 295.08067,528.89486 C 295.08065,521.83985 299.92636,516.1132 305.80551,516.1132 z M 440.821,552.54828 C 440.821,552.54828 448.7504,554.02388 453.8965,559.45332 C 457.41881,563.16951 457.75208,569.15506 456.98172,577.37703 C 456.21143,573.8833 454.89571,571.76659 453.8965,569.29666 C 443.01388,582.47662 413.42981,583.08929 376.0312,569.88433 C 416.63248,578.00493 437.38806,570.56014 449.48903,561.2163 C 446.29383,557.08917 440.821,552.54828 440.821,552.54828 z ";
191 
192 static const gchar eyes_path[] =
193   "M 434.64723,524.59684 C 434.64723,532.23974 428.44429,538.44268 420.80139,538.44268 C 413.15849,538.44268 406.95555,532.23974 406.95555,524.59684 C 406.95555,516.95394 413.15849,510.751 420.80139,510.751 C 428.44429,510.751 434.64723,516.95394 434.64723,524.59684 z M 378.00043,522.99931 C 378.00043,527.70264 374.18324,531.51984 369.47991,531.51984 C 364.77658,531.51984 360.95939,527.70264 360.95939,522.99931 C 360.95939,518.29599 364.77658,514.47879 369.47991,514.47879 C 374.18324,514.47879 378.00043,518.29599 378.00043,522.99931 z ";
194 
195 static cairo_path_t *wilber_cairo_path = NULL;
196 static gdouble       wilber_x1, wilber_y1;
197 static gdouble       wilber_x2, wilber_y2;
198 
199 static cairo_path_t *eyes_cairo_path = NULL;
200 static gdouble       eyes_x1, eyes_y1;
201 static gdouble       eyes_x2, eyes_y2;
202 
203 
204 static void  parse_path_data    (cairo_t     *cr,
205                                  const gchar *data);
206 static void  wilber_get_extents (cairo_t     *cr);
207 static void  eyes_get_extents   (cairo_t     *cr);
208 
209 
210 /**
211  * gimp_cairo_wilber:
212  * @cr: Cairo context
213  * @x: x position
214  * @y: y position
215  *
216  * Draw a Wilber path at position @x, @y.
217  */
218 void
gimp_cairo_wilber(cairo_t * cr,gdouble x,gdouble y)219 gimp_cairo_wilber (cairo_t *cr,
220                    gdouble  x,
221                    gdouble  y)
222 {
223   gimp_cairo_wilber_internal (NULL, cr, x, y, 1.0, 0.0);
224 }
225 
226 static void
gimp_cairo_wilber_weak_notify(gpointer data,GObject * widget)227 gimp_cairo_wilber_weak_notify (gpointer  data,
228                                GObject  *widget)
229 {
230   cairo_wilber_widgets = g_slist_remove (cairo_wilber_widgets, widget);
231 }
232 
233 static void
gimp_cairo_wilber_internal(GtkWidget * widget,cairo_t * cr,gdouble x,gdouble y,gdouble factor,gdouble max_eye_angle)234 gimp_cairo_wilber_internal (GtkWidget *widget,
235                             cairo_t   *cr,
236                             gdouble    x,
237                             gdouble    y,
238                             gdouble    factor,
239                             gdouble    max_eye_angle)
240 {
241   wilber_get_extents (cr);
242 
243   cairo_save (cr);
244 
245   cairo_translate (cr, x - wilber_x1, y - wilber_y1);
246   cairo_append_path (cr, wilber_cairo_path);
247 
248   cairo_restore (cr);
249 
250   gimp_cairo_eyes (widget, cr, x, y, factor, max_eye_angle);
251 
252   if (widget && ! g_slist_find (cairo_wilber_widgets, widget))
253     {
254       cairo_wilber_widgets = g_slist_prepend (cairo_wilber_widgets, widget);
255 
256       g_object_weak_ref (G_OBJECT (widget),
257                          gimp_cairo_wilber_weak_notify, NULL);
258     }
259 }
260 
261 typedef struct
262 {
263   gdouble x;
264   gdouble y;
265   gdouble radius;
266 
267   gdouble a;
268   gdouble b;
269   gdouble r;
270 } Eye;
271 
272 static const Eye eyes[2] =
273 {
274   { .x      = (344.151 + 382.496) / 2.0,
275     .y      = (501.128 + 539.473) / 2.0,
276     .radius = (382.496 - 344.151) / 2.0,
277 
278     .a      = 25.0 * G_PI / 180.0,
279     .b      = 24.0 * G_PI / 180.0,
280     .r      = 0.475
281   },
282 
283   { .x      = (385.287 + 442.878) / 2.0,
284     .y      = (491.431 + 549.022) / 2.0,
285     .radius = (442.878 - 385.287) / 2.0,
286 
287     .a      = 34.0 * G_PI / 180.0,
288     .b      = 19.0 * G_PI / 180.0,
289     .r      = 0.5
290   }
291 };
292 
293 typedef struct
294 {
295   gdouble a;
296   gdouble b;
297 } EyeState;
298 
299 typedef struct
300 {
301   EyeState eyes[2];
302   gdouble  x;
303   gdouble  y;
304   gdouble  factor;
305   gdouble  max_eye_angle;
306   gdouble  t;
307   gint     timeout_id;
308 } EyesState;
309 
310 static EyesState *
eyes_state_new(void)311 eyes_state_new (void)
312 {
313   EyesState *state = g_slice_new0 (EyesState);
314   gint       i;
315 
316   for (i = 0; i < 2; i++)
317     {
318       state->eyes[i].a = eyes[i].a;
319       state->eyes[i].b = eyes[i].b;
320     }
321 
322   state->t = (gdouble) g_get_monotonic_time () / G_TIME_SPAN_SECOND;
323 
324   return state;
325 }
326 
327 static void
eyes_state_free(EyesState * state)328 eyes_state_free (EyesState *state)
329 {
330   if (state->timeout_id)
331     g_source_remove (state->timeout_id);
332 
333   g_slice_free (EyesState, state);
334 }
335 
336 static gboolean
gimp_cairo_pointer_eyes_timeout(GtkWidget * widget)337 gimp_cairo_pointer_eyes_timeout (GtkWidget *widget)
338 {
339   EyesState     *state;
340   gdouble        t;
341   gint           pointer_x;
342   gint           pointer_y;
343   GtkAllocation  allocation;
344   GdkWindow     *window;
345   gint           window_x;
346   gint           window_y;
347   gint           redraw = 2;
348   gint           i;
349 
350   state = g_object_get_data (G_OBJECT (widget), "wilber-eyes-state");
351 
352   t = (gdouble) g_get_monotonic_time () / G_TIME_SPAN_SECOND;
353 
354   gdk_display_get_pointer (gtk_widget_get_display (widget),
355                            NULL, &pointer_x, &pointer_y, NULL);
356 
357   gtk_widget_get_allocation (widget, &allocation);
358 
359   window = gtk_widget_get_window (widget);
360 
361   if (window)
362     gdk_window_get_origin (window, &window_x, &window_y);
363 
364   for (i = 0; i < 2; i++)
365     {
366       const Eye   *eye = &eyes[i];
367       gdouble      a;
368       gdouble      b;
369       gdouble      c;
370       GimpVector3  u;
371       GimpVector3  v;
372       GimpVector3  w;
373 
374       if (pointer_eyes)
375         {
376           gdouble screen_x;
377           gdouble screen_y;
378           gdouble z = 220.0 * state->factor;
379           gdouble d;
380 
381           screen_x = (eye->x + state->x - wilber_x1) * state->factor;
382           screen_y = (eye->y + state->y - wilber_y1) * state->factor;
383 
384           if (! gtk_widget_get_has_window (widget))
385             {
386               screen_x += allocation.x;
387               screen_y += allocation.y;
388             }
389 
390           if (window)
391             {
392               screen_x += window_x;
393               screen_y += window_y;
394             }
395 
396           d = sqrt (SQR (pointer_x - screen_x) + SQR (pointer_y - screen_y));
397           a = atan2 (pointer_y - screen_y, pointer_x - screen_x);
398           b = atan (d / z);
399           b = MIN (b, state->max_eye_angle);
400         }
401       else
402         {
403           a = eyes[i].a;
404           b = eyes[i].b;
405         }
406 
407       if (a == state->eyes[i].a && b == state->eyes[i].b)
408         {
409           redraw--;
410 
411           continue;
412         }
413 
414       u.x = sin (state->eyes[i].b) * cos (state->eyes[i].a);
415       u.y = sin (state->eyes[i].b) * sin (state->eyes[i].a);
416       u.z = cos (state->eyes[i].b);
417 
418       v.x = sin (b) * cos (a);
419       v.y = sin (b) * sin (a);
420       v.z = cos (b);
421 
422       c = acos (gimp_vector3_inner_product (&u, &v));
423 
424       if (c < 1e-2)
425         {
426           state->eyes[i].a = a;
427           state->eyes[i].b = b;
428 
429           continue;
430         }
431 
432       c *= 1.0 - exp (-(t - state->t) * 15.0);
433 
434       w = gimp_vector3_cross_product (&u, &v);
435       w = gimp_vector3_cross_product (&w, &u);
436       gimp_vector3_normalize (&w);
437 
438       v.x = u.x * cos (c) + w.x * sin (c);
439       v.y = u.y * cos (c) + w.y * sin (c);
440       v.z = u.z * cos (c) + w.z * sin (c);
441 
442       a = atan2 (v.y, v.x);
443       b = acos (v.z);
444 
445       state->eyes[i].a = a;
446       state->eyes[i].b = b;
447     }
448 
449   state->t = t;
450 
451   if (redraw)
452     {
453       state->timeout_id = 0;
454 
455       gtk_widget_queue_draw (widget);
456 
457       return G_SOURCE_REMOVE;
458     }
459   else if (! pointer_eyes)
460     {
461       state->timeout_id = 0;
462 
463       g_object_set_data (G_OBJECT (widget), "wilber-eyes-state", NULL);
464       gtk_widget_queue_draw (widget);
465 
466       return G_SOURCE_REMOVE;
467     }
468 
469   return G_SOURCE_CONTINUE;
470 }
471 
472 static void
gimp_cairo_pointer_eyes(GtkWidget * widget,cairo_t * cr,gdouble x,gdouble y,gdouble factor,gdouble max_eye_angle)473 gimp_cairo_pointer_eyes (GtkWidget *widget,
474                          cairo_t   *cr,
475                          gdouble    x,
476                          gdouble    y,
477                          gdouble    factor,
478                          gdouble    max_eye_angle)
479 {
480   EyesState *state;
481   gint       i;
482 
483   state = g_object_get_data (G_OBJECT (widget), "wilber-eyes-state");
484 
485   if (! state)
486     {
487       state = eyes_state_new ();
488 
489       g_object_set_data_full (G_OBJECT (widget), "wilber-eyes-state", state,
490                               (GDestroyNotify) eyes_state_free);
491     }
492 
493   for (i = 0; i < 2; i++)
494     {
495       const Eye *eye = &eyes[i];
496       gdouble    R = eye->radius;
497       gdouble    r = eye->r * eye->radius;
498       gint       j;
499 
500       cairo_save (cr);
501 
502       cairo_translate (cr, eye->x, eye->y);
503       cairo_rotate (cr, state->eyes[i].a);
504 
505       for (j = 0; j < 32; j++)
506         {
507           gdouble a = -2.0 * G_PI * j / 32.0;
508           gdouble u = r * cos (a);
509           gdouble v = r * sin (a);
510           gdouble w = sqrt (SQR (R) - SQR (v));
511           gdouble b = asin (u / w);
512 
513           b = CLAMP (b + state->eyes[i].b, -G_PI / 2.0, +G_PI / 2.0);
514           u = w * sin (b);
515 
516           if (j == 0)
517             cairo_move_to (cr, u, v);
518           else
519             cairo_line_to (cr, u, v);
520         }
521 
522       cairo_close_path (cr);
523 
524       cairo_restore (cr);
525     }
526 
527   state->x             = x;
528   state->y             = y;
529   state->factor        = factor;
530   state->max_eye_angle = max_eye_angle;
531 
532   if (! state->timeout_id)
533     {
534       state->timeout_id =
535         g_timeout_add (17,
536                        (GSourceFunc) gimp_cairo_pointer_eyes_timeout,
537                        widget);
538     }
539 }
540 
541 static void
gimp_cairo_eyes(GtkWidget * widget,cairo_t * cr,gdouble x,gdouble y,gdouble factor,gdouble max_eye_angle)542 gimp_cairo_eyes (GtkWidget *widget,
543                  cairo_t   *cr,
544                  gdouble    x,
545                  gdouble    y,
546                  gdouble    factor,
547                  gdouble    max_eye_angle)
548 {
549   wilber_get_extents (cr);
550   eyes_get_extents (cr);
551 
552   cairo_save (cr);
553 
554   cairo_translate (cr, x - wilber_x1, y - wilber_y1);
555   if (widget        &&
556       (pointer_eyes ||
557        g_object_get_data (G_OBJECT (widget), "wilber-eyes-state")))
558     {
559       gimp_cairo_pointer_eyes (widget, cr, x, y, factor, max_eye_angle);
560     }
561   else
562     {
563       cairo_append_path (cr, eyes_cairo_path);
564     }
565 
566   cairo_restore (cr);
567 }
568 
569 void
gimp_cairo_wilber_get_size(cairo_t * cr,gdouble * width,gdouble * height)570 gimp_cairo_wilber_get_size (cairo_t *cr,
571                             gdouble *width,
572                             gdouble *height)
573 {
574   wilber_get_extents (cr);
575 
576   *width  = wilber_x2 - wilber_x1;
577   *height = wilber_y2 - wilber_y1;
578 }
579 
580 
581 static void
wilber_get_extents(cairo_t * unused)582 wilber_get_extents (cairo_t *unused)
583 {
584   if (! wilber_cairo_path)
585     {
586       cairo_surface_t *s  = cairo_image_surface_create (CAIRO_FORMAT_A8, 1, 1);
587       cairo_t         *cr = cairo_create (s);
588 
589       parse_path_data (cr, wilber_path);
590       cairo_fill_extents (cr, &wilber_x1, &wilber_y1, &wilber_x2, &wilber_y2);
591 
592       wilber_cairo_path = cairo_copy_path (cr);
593 
594       cairo_destroy (cr);
595       cairo_surface_destroy (s);
596     }
597 }
598 
599 static void
eyes_get_extents(cairo_t * unused)600 eyes_get_extents (cairo_t *unused)
601 {
602   if (! eyes_cairo_path)
603     {
604       cairo_surface_t *s  = cairo_image_surface_create (CAIRO_FORMAT_A8, 1, 1);
605       cairo_t         *cr = cairo_create (s);
606 
607       parse_path_data (cr, eyes_path);
608       cairo_fill_extents (cr, &eyes_x1, &eyes_y1, &eyes_x2, &eyes_y2);
609 
610       eyes_cairo_path = cairo_copy_path (cr);
611 
612       cairo_destroy (cr);
613       cairo_surface_destroy (s);
614     }
615 }
616 
617 /**********************************************************/
618 /*  Below is the code that parses the actual path data.   */
619 /*                                                        */
620 /*  This code is taken from librsvg and was originally    */
621 /*  written by Raph Levien <raph@artofcode.com> for Gill. */
622 /**********************************************************/
623 
624 typedef struct
625 {
626   cairo_t     *cr;
627   gdouble      cpx, cpy;  /* current point                               */
628   gdouble      rpx, rpy;  /* reflection point (for 's' and 't' commands) */
629   gchar        cmd;       /* current command (lowercase)                 */
630   gint         param;     /* number of parameters                        */
631   gboolean     rel;       /* true if relative coords                     */
632   gdouble      params[7]; /* parameters that have been parsed            */
633 } ParsePathContext;
634 
635 
636 static void  parse_path_default_xy (ParsePathContext *ctx,
637                                     gint              n_params);
638 static void  parse_path_do_cmd     (ParsePathContext *ctx,
639                                     gboolean          final);
640 
641 
642 static void
parse_path_data(cairo_t * cr,const gchar * data)643 parse_path_data (cairo_t     *cr,
644                  const gchar *data)
645 {
646   ParsePathContext ctx;
647 
648   gboolean  in_num        = FALSE;
649   gboolean  in_frac       = FALSE;
650   gboolean  in_exp        = FALSE;
651   gboolean  exp_wait_sign = FALSE;
652   gdouble   val           = 0.0;
653   gchar     c             = 0;
654   gint      sign          = 0;
655   gint      exp           = 0;
656   gint      exp_sign      = 0;
657   gdouble   frac          = 0.0;
658   gint      i;
659 
660   memset (&ctx, 0, sizeof (ParsePathContext));
661 
662   ctx.cr = cr;
663 
664   for (i = 0; ; i++)
665     {
666       c = data[i];
667       if (c >= '0' && c <= '9')
668         {
669           /* digit */
670           if (in_num)
671             {
672               if (in_exp)
673                 {
674                   exp = (exp * 10) + c - '0';
675                   exp_wait_sign = FALSE;
676                 }
677               else if (in_frac)
678                 val += (frac *= 0.1) * (c - '0');
679               else
680                 val = (val * 10) + c - '0';
681             }
682           else
683             {
684               in_num = TRUE;
685               in_frac = FALSE;
686               in_exp = FALSE;
687               exp = 0;
688               exp_sign = 1;
689               exp_wait_sign = FALSE;
690               val = c - '0';
691               sign = 1;
692             }
693         }
694       else if (c == '.')
695         {
696           if (!in_num)
697             {
698               in_num = TRUE;
699               val = 0;
700             }
701           in_frac = TRUE;
702           frac = 1;
703         }
704       else if ((c == 'E' || c == 'e') && in_num)
705         {
706           in_exp = TRUE;
707           exp_wait_sign = TRUE;
708           exp = 0;
709           exp_sign = 1;
710         }
711       else if ((c == '+' || c == '-') && in_exp)
712         {
713           exp_sign = c == '+' ? 1 : -1;
714         }
715       else if (in_num)
716         {
717           /* end of number */
718 
719           val *= sign * pow (10, exp_sign * exp);
720           if (ctx.rel)
721             {
722               /* Handle relative coordinates. This switch statement attempts
723                  to determine _what_ the coords are relative to. This is
724                  underspecified in the 12 Apr working draft. */
725               switch (ctx.cmd)
726                 {
727                 case 'l':
728                 case 'm':
729                 case 'c':
730                 case 's':
731                 case 'q':
732                 case 't':
733                   /* rule: even-numbered params are x-relative, odd-numbered
734                      are y-relative */
735                   if ((ctx.param & 1) == 0)
736                     val += ctx.cpx;
737                   else if ((ctx.param & 1) == 1)
738                     val += ctx.cpy;
739                   break;
740 
741                 case 'a':
742                   /* rule: sixth and seventh are x and y, rest are not
743                      relative */
744                   if (ctx.param == 5)
745                     val += ctx.cpx;
746                   else if (ctx.param == 6)
747                     val += ctx.cpy;
748                   break;
749                 case 'h':
750                   /* rule: x-relative */
751                   val += ctx.cpx;
752                   break;
753                 case 'v':
754                   /* rule: y-relative */
755                   val += ctx.cpy;
756                   break;
757                 }
758             }
759 
760           ctx.params[ctx.param++] = val;
761           parse_path_do_cmd (&ctx, FALSE);
762           in_num = FALSE;
763         }
764 
765       if (c == '\0')
766         break;
767       else if ((c == '+' || c == '-') && !exp_wait_sign)
768         {
769           sign = c == '+' ? 1 : -1;
770           val = 0;
771           in_num = TRUE;
772           in_frac = FALSE;
773           in_exp = FALSE;
774           exp = 0;
775           exp_sign = 1;
776           exp_wait_sign = FALSE;
777         }
778       else if (c == 'z' || c == 'Z')
779         {
780           if (ctx.param)
781             parse_path_do_cmd (&ctx, TRUE);
782 
783           cairo_close_path (ctx.cr);
784         }
785       else if (c >= 'A' && c <= 'Z' && c != 'E')
786         {
787           if (ctx.param)
788             parse_path_do_cmd (&ctx, TRUE);
789           ctx.cmd = c + 'a' - 'A';
790           ctx.rel = FALSE;
791         }
792       else if (c >= 'a' && c <= 'z' && c != 'e')
793         {
794           if (ctx.param)
795             parse_path_do_cmd (&ctx, TRUE);
796           ctx.cmd = c;
797           ctx.rel = TRUE;
798         }
799       /* else c _should_ be whitespace or , */
800     }
801 }
802 
803 /* supply defaults for missing parameters, assuming relative coordinates
804    are to be interpreted as x,y */
805 static void
parse_path_default_xy(ParsePathContext * ctx,gint n_params)806 parse_path_default_xy (ParsePathContext *ctx,
807                        gint              n_params)
808 {
809   gint i;
810 
811   if (ctx->rel)
812     {
813       for (i = ctx->param; i < n_params; i++)
814         {
815           if (i > 2)
816             ctx->params[i] = ctx->params[i - 2];
817           else if (i == 1)
818             ctx->params[i] = ctx->cpy;
819           else if (i == 0)
820             /* we shouldn't get here (ctx->param > 0 as precondition) */
821             ctx->params[i] = ctx->cpx;
822         }
823     }
824   else
825     {
826       for (i = ctx->param; i < n_params; i++)
827         ctx->params[i] = 0.0;
828     }
829 }
830 
831 static void
parse_path_do_cmd(ParsePathContext * ctx,gboolean final)832 parse_path_do_cmd (ParsePathContext *ctx,
833                    gboolean          final)
834 {
835   switch (ctx->cmd)
836     {
837     case 'm':
838       /* moveto */
839       if (ctx->param == 2 || final)
840         {
841           parse_path_default_xy (ctx, 2);
842 
843           ctx->cpx = ctx->rpx = ctx->params[0];
844           ctx->cpy = ctx->rpy = ctx->params[1];
845 
846           cairo_move_to (ctx->cr, ctx->cpx, ctx->cpy);
847 
848           ctx->param = 0;
849         }
850       break;
851 
852     case 'l':
853       /* lineto */
854       if (ctx->param == 2 || final)
855         {
856           parse_path_default_xy (ctx, 2);
857 
858           ctx->cpx = ctx->rpx = ctx->params[0];
859           ctx->cpy = ctx->rpy = ctx->params[1];
860 
861           cairo_line_to (ctx->cr, ctx->cpx, ctx->cpy);
862 
863           ctx->param = 0;
864         }
865       break;
866 
867     case 'c':
868       /* curveto */
869       if (ctx->param == 6 || final)
870         {
871           gdouble x, y;
872 
873           parse_path_default_xy (ctx, 6);
874 
875           x        = ctx->params[0];
876           y        = ctx->params[1];
877           ctx->rpx = ctx->params[2];
878           ctx->rpy = ctx->params[3];
879           ctx->cpx = ctx->params[4];
880           ctx->cpy = ctx->params[5];
881 
882           cairo_curve_to (ctx->cr,
883                           x, y, ctx->rpx, ctx->rpy, ctx->cpx, ctx->cpy);
884 
885           ctx->param = 0;
886         }
887       break;
888 
889     case 's':
890       /* smooth curveto */
891       if (ctx->param == 4 || final)
892         {
893           gdouble x, y;
894 
895           parse_path_default_xy (ctx, 4);
896 
897           x = 2 * ctx->cpx - ctx->rpx;
898           y = 2 * ctx->cpy - ctx->rpy;
899           ctx->rpx = ctx->params[0];
900           ctx->rpy = ctx->params[1];
901           ctx->cpx = ctx->params[2];
902           ctx->cpy = ctx->params[3];
903 
904           cairo_curve_to (ctx->cr,
905                           x, y, ctx->rpx, ctx->rpy, ctx->cpx, ctx->cpy);
906 
907           ctx->param = 0;
908         }
909       break;
910 
911     case 'h':
912       /* horizontal lineto */
913       if (ctx->param == 1)
914         {
915           ctx->cpx = ctx->rpx = ctx->params[0];
916 
917           cairo_line_to (ctx->cr, ctx->cpx, ctx->cpy);
918 
919           ctx->param = 0;
920         }
921       break;
922 
923     case 'v':
924       /* vertical lineto */
925       if (ctx->param == 1)
926         {
927           ctx->cpy = ctx->rpy = ctx->params[0];
928 
929           cairo_line_to (ctx->cr, ctx->cpx, ctx->cpy);
930 
931           ctx->param = 0;
932         }
933       break;
934 
935     case 'q':
936       /* quadratic bezier curveto */
937       if (ctx->param == 4 || final)
938         {
939           parse_path_default_xy (ctx, 4);
940 
941           ctx->rpx = ctx->params[0];
942           ctx->rpy = ctx->params[1];
943           ctx->cpx = ctx->params[2];
944           ctx->cpy = ctx->params[3];
945 
946           g_warning ("quadratic bezier curveto not implemented");
947 
948           ctx->param = 0;
949         }
950       break;
951 
952     case 't':
953       /* truetype quadratic bezier curveto */
954       if (ctx->param == 2 || final)
955         {
956           parse_path_default_xy (ctx, 2);
957 
958           ctx->rpx = 2 * ctx->cpx - ctx->rpx;
959           ctx->rpy = 2 * ctx->cpy - ctx->rpy;
960           ctx->cpx = ctx->params[0];
961           ctx->cpy = ctx->params[1];
962 
963           g_warning ("truetype quadratic bezier curveto not implemented");
964 
965           ctx->param = 0;
966         }
967       else if (final)
968         {
969           if (ctx->param > 2)
970             {
971               parse_path_default_xy (ctx, 4);
972 
973               ctx->rpx = ctx->params[0];
974               ctx->rpy = ctx->params[1];
975               ctx->cpx = ctx->params[2];
976               ctx->cpy = ctx->params[3];
977 
978               g_warning ("conicto not implemented");
979             }
980           else
981             {
982               parse_path_default_xy (ctx, 2);
983 
984               ctx->cpx = ctx->rpx = ctx->params[0];
985               ctx->cpy = ctx->rpy = ctx->params[1];
986 
987               cairo_line_to (ctx->cr, ctx->cpx, ctx->cpy);
988             }
989 
990           ctx->param = 0;
991         }
992       break;
993 
994     case 'a':
995       if (ctx->param == 7 || final)
996         {
997           ctx->cpx = ctx->rpx = ctx->params[5];
998           ctx->cpy = ctx->rpy = ctx->params[6];
999 
1000           g_warning ("arcto not implemented");
1001 
1002           ctx->param = 0;
1003         }
1004       break;
1005 
1006     default:
1007       ctx->param = 0;
1008       break;
1009     }
1010 }
1011