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