/* 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 * * 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 "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 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; } }