/* GIMP - The GNU Image Manipulation Program * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * Wilber Cairo rendering * Copyright (C) 2008 Sven Neumann * * Some code here is based on code from librsvg that was originally * written by Raph Levien 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 . */ #include "config.h" #include #include #include "libgimpmath/gimpmath.h" #include "widgets-types.h" #include "gimpcairo-wilber.h" static void gimp_cairo_wilber_internal (GtkWidget *widget, cairo_t *cr, gdouble x, gdouble y, gdouble factor, gdouble max_eye_angle); static void gimp_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_wilber_widgets = NULL; void gimp_cairo_wilber_toggle_pointer_eyes (void) { GSList *iter; pointer_eyes = ! pointer_eyes; for (iter = cairo_wilber_widgets; iter; iter = g_slist_next (iter)) { if (pointer_eyes) g_object_set_data (G_OBJECT (iter->data), "wilber-eyes-state", NULL); gtk_widget_queue_draw (GTK_WIDGET (iter->data)); } } void gimp_cairo_draw_toolbox_wilber (GtkWidget *widget, cairo_t *cr) { GtkStyle *style; GtkStateType state; GtkAllocation allocation; gdouble wilber_width; gdouble wilber_height; gdouble factor; g_return_if_fail (GTK_IS_WIDGET (widget)); g_return_if_fail (cr != NULL); style = gtk_widget_get_style (widget); state = gtk_widget_get_state (widget); gtk_widget_get_allocation (widget, &allocation); gimp_cairo_wilber_get_size (cr, &wilber_width, &wilber_height); factor = allocation.width / wilber_width * 0.9; if (! gtk_widget_get_has_window (widget)) cairo_translate (cr, allocation.x, allocation.y); cairo_scale (cr, factor, factor); gimp_cairo_wilber_internal (widget, cr, (allocation.width / factor - wilber_width) / 2.0, (allocation.height / factor - wilber_height) / 2.0, factor, 30.0 * G_PI / 180.0); cairo_set_source_rgba (cr, style->fg[state].red / 65535.0, style->fg[state].green / 65535.0, style->fg[state].blue / 65535.0, 0.10); cairo_fill (cr); } void gimp_cairo_draw_drop_wilber (GtkWidget *widget, cairo_t *cr, gboolean blink) { GtkStyle *style; GtkStateType state; GtkAllocation allocation; gdouble wilber_width; gdouble wilber_height; gdouble width; gdouble height; gdouble side; gdouble factor; g_return_if_fail (GTK_IS_WIDGET (widget)); g_return_if_fail (cr != NULL); style = gtk_widget_get_style (widget); state = gtk_widget_get_state (widget); gtk_widget_get_allocation (widget, &allocation); gimp_cairo_wilber_get_size (cr, &wilber_width, &wilber_height); wilber_width /= 2; wilber_height /= 2; side = MIN (MIN (allocation.width, allocation.height), MAX (allocation.width, allocation.height) / 2); width = MAX (wilber_width, side); height = MAX (wilber_height, side); factor = MIN (width / wilber_width, height / wilber_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 */ gimp_cairo_wilber_internal (widget, cr, - wilber_width * 0.6, allocation.height / factor - wilber_height * 1.1, factor, 50.0 * G_PI / 180.0); cairo_set_source_rgba (cr, style->fg[state].red / 65535.0, style->fg[state].green / 65535.0, style->fg[state].blue / 65535.0, 0.15); cairo_fill (cr); if (blink) { gimp_cairo_eyes (widget, cr, - wilber_width * 0.6, allocation.height / factor - wilber_height * 1.1, factor, 50.0 * G_PI / 180.0); cairo_set_source_rgba (cr, style->fg[state].red / 65535.0, 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 wilber_path[] = "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 "; static const gchar eyes_path[] = "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 "; static cairo_path_t *wilber_cairo_path = NULL; static gdouble wilber_x1, wilber_y1; static gdouble wilber_x2, wilber_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 wilber_get_extents (cairo_t *cr); static void eyes_get_extents (cairo_t *cr); /** * gimp_cairo_wilber: * @cr: Cairo context * @x: x position * @y: y position * * Draw a Wilber path at position @x, @y. */ void gimp_cairo_wilber (cairo_t *cr, gdouble x, gdouble y) { gimp_cairo_wilber_internal (NULL, cr, x, y, 1.0, 0.0); } static void gimp_cairo_wilber_weak_notify (gpointer data, GObject *widget) { cairo_wilber_widgets = g_slist_remove (cairo_wilber_widgets, widget); } static void gimp_cairo_wilber_internal (GtkWidget *widget, cairo_t *cr, gdouble x, gdouble y, gdouble factor, gdouble max_eye_angle) { wilber_get_extents (cr); cairo_save (cr); cairo_translate (cr, x - wilber_x1, y - wilber_y1); cairo_append_path (cr, wilber_cairo_path); cairo_restore (cr); gimp_cairo_eyes (widget, cr, x, y, factor, max_eye_angle); if (widget && ! g_slist_find (cairo_wilber_widgets, widget)) { cairo_wilber_widgets = g_slist_prepend (cairo_wilber_widgets, widget); g_object_weak_ref (G_OBJECT (widget), gimp_cairo_wilber_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 gimp_cairo_pointer_eyes_timeout (GtkWidget *widget) { 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), "wilber-eyes-state"); t = (gdouble) g_get_monotonic_time () / G_TIME_SPAN_SECOND; gdk_display_get_pointer (gtk_widget_get_display (widget), NULL, &pointer_x, &pointer_y, NULL); 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; GimpVector3 u; GimpVector3 v; GimpVector3 w; if (pointer_eyes) { gdouble screen_x; gdouble screen_y; gdouble z = 220.0 * state->factor; gdouble d; screen_x = (eye->x + state->x - wilber_x1) * state->factor; screen_y = (eye->y + state->y - wilber_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 (gimp_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 = gimp_vector3_cross_product (&u, &v); w = gimp_vector3_cross_product (&w, &u); gimp_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), "wilber-eyes-state", NULL); gtk_widget_queue_draw (widget); return G_SOURCE_REMOVE; } return G_SOURCE_CONTINUE; } static void gimp_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), "wilber-eyes-state"); if (! state) { state = eyes_state_new (); g_object_set_data_full (G_OBJECT (widget), "wilber-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) gimp_cairo_pointer_eyes_timeout, widget); } } static void gimp_cairo_eyes (GtkWidget *widget, cairo_t *cr, gdouble x, gdouble y, gdouble factor, gdouble max_eye_angle) { wilber_get_extents (cr); eyes_get_extents (cr); cairo_save (cr); cairo_translate (cr, x - wilber_x1, y - wilber_y1); if (widget && (pointer_eyes || g_object_get_data (G_OBJECT (widget), "wilber-eyes-state"))) { gimp_cairo_pointer_eyes (widget, cr, x, y, factor, max_eye_angle); } else { cairo_append_path (cr, eyes_cairo_path); } cairo_restore (cr); } void gimp_cairo_wilber_get_size (cairo_t *cr, gdouble *width, gdouble *height) { wilber_get_extents (cr); *width = wilber_x2 - wilber_x1; *height = wilber_y2 - wilber_y1; } static void wilber_get_extents (cairo_t *unused) { if (! wilber_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, wilber_path); cairo_fill_extents (cr, &wilber_x1, &wilber_y1, &wilber_x2, &wilber_y2); wilber_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 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; } }