1017 lines
28 KiB
C
1017 lines
28 KiB
C
/* PIKA - Photo and Image Kooker Application
|
|
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
|
|
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
|
|
*
|
|
* Original copyright, applying to most contents (license remains unchanged):
|
|
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
|
*
|
|
* Wilber Cairo rendering
|
|
* Copyright (C) 2008 Sven Neumann <sven@gimp.org>
|
|
*
|
|
* Some code here is based on code from librsvg that was originally
|
|
* written by Raph Levien <raph@artofcode.com> for Gill.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <string.h>
|
|
|
|
#include <gtk/gtk.h>
|
|
|
|
#include "libpikamath/pikamath.h"
|
|
|
|
#include "widgets-types.h"
|
|
|
|
#include "pikacairo-mascot.h"
|
|
|
|
|
|
static void pika_cairo_mascot_internal (GtkWidget *widget,
|
|
cairo_t *cr,
|
|
gdouble x,
|
|
gdouble y,
|
|
gdouble factor,
|
|
gdouble max_eye_angle);
|
|
static void pika_cairo_eyes (GtkWidget *widget,
|
|
cairo_t *cr,
|
|
gdouble x,
|
|
gdouble y,
|
|
gdouble factor,
|
|
gdouble max_eye_angle);
|
|
|
|
|
|
static gboolean pointer_eyes = FALSE;
|
|
static GSList *cairo_mascot_widgets = NULL;
|
|
|
|
|
|
void
|
|
pika_cairo_mascot_toggle_pointer_eyes (void)
|
|
{
|
|
GSList *iter;
|
|
|
|
pointer_eyes = ! pointer_eyes;
|
|
|
|
for (iter = cairo_mascot_widgets; iter; iter = g_slist_next (iter))
|
|
{
|
|
if (pointer_eyes)
|
|
g_object_set_data (G_OBJECT (iter->data), "mascot-eyes-state", NULL);
|
|
|
|
gtk_widget_queue_draw (GTK_WIDGET (iter->data));
|
|
}
|
|
}
|
|
|
|
void
|
|
pika_cairo_draw_toolbox_mascot (GtkWidget *widget,
|
|
cairo_t *cr)
|
|
{
|
|
GtkStyleContext *context;
|
|
GtkAllocation allocation;
|
|
GdkRGBA color;
|
|
gdouble mascot_width;
|
|
gdouble mascot_height;
|
|
gdouble factor;
|
|
|
|
g_return_if_fail (GTK_IS_WIDGET (widget));
|
|
g_return_if_fail (cr != NULL);
|
|
|
|
context = gtk_widget_get_style_context (widget);
|
|
|
|
gtk_widget_get_allocation (widget, &allocation);
|
|
|
|
pika_cairo_mascot_get_size (cr, &mascot_width, &mascot_height);
|
|
|
|
factor = allocation.width / mascot_width * 0.9;
|
|
|
|
if (! gtk_widget_get_has_window (widget))
|
|
cairo_translate (cr, allocation.x, allocation.y);
|
|
|
|
cairo_scale (cr, factor, factor);
|
|
|
|
pika_cairo_mascot_internal (widget, cr,
|
|
(allocation.width / factor - mascot_width) / 2.0,
|
|
(allocation.height / factor - mascot_height) / 2.0,
|
|
factor, 30.0 * G_PI / 180.0);
|
|
|
|
gtk_style_context_get_color (context, gtk_widget_get_state_flags (widget),
|
|
&color);
|
|
color.alpha = 0.1;
|
|
gdk_cairo_set_source_rgba (cr, &color);
|
|
|
|
cairo_fill (cr);
|
|
}
|
|
|
|
void
|
|
pika_cairo_draw_drop_mascot (GtkWidget *widget,
|
|
cairo_t *cr,
|
|
gboolean blink)
|
|
{
|
|
GtkStyleContext *context;
|
|
GtkAllocation allocation;
|
|
GdkRGBA color;
|
|
gdouble mascot_width;
|
|
gdouble mascot_height;
|
|
gdouble width;
|
|
gdouble height;
|
|
gdouble side;
|
|
gdouble factor;
|
|
|
|
g_return_if_fail (GTK_IS_WIDGET (widget));
|
|
g_return_if_fail (cr != NULL);
|
|
|
|
context = gtk_widget_get_style_context (widget);
|
|
|
|
gtk_widget_get_allocation (widget, &allocation);
|
|
|
|
pika_cairo_mascot_get_size (cr, &mascot_width, &mascot_height);
|
|
|
|
mascot_width /= 2;
|
|
mascot_height /= 2;
|
|
|
|
side = MIN (MIN (allocation.width, allocation.height),
|
|
MAX (allocation.width, allocation.height) / 2);
|
|
|
|
width = MAX (mascot_width, side);
|
|
height = MAX (mascot_height, side);
|
|
|
|
factor = MIN (width / mascot_width, height / mascot_height);
|
|
|
|
if (! gtk_widget_get_has_window (widget))
|
|
cairo_translate (cr, allocation.x, allocation.y);
|
|
|
|
cairo_scale (cr, factor, factor);
|
|
|
|
/* magic factors depend on the image used, everything else is generic
|
|
*/
|
|
pika_cairo_mascot_internal (widget, cr,
|
|
- mascot_width * 0.6,
|
|
allocation.height / factor - mascot_height * 1.1,
|
|
factor, 50.0 * G_PI / 180.0);
|
|
|
|
gtk_style_context_get_color (context, gtk_widget_get_state_flags (widget),
|
|
&color);
|
|
color.alpha = 0.15;
|
|
|
|
gdk_cairo_set_source_rgba (cr, &color);
|
|
|
|
cairo_fill (cr);
|
|
|
|
if (blink)
|
|
{
|
|
pika_cairo_eyes (widget, cr,
|
|
- mascot_width * 0.6,
|
|
allocation.height / factor - mascot_height * 1.1,
|
|
factor, 50.0 * G_PI / 180.0);
|
|
|
|
cairo_set_source_rgba (cr,
|
|
color.red,
|
|
0,
|
|
0,
|
|
1.0);
|
|
cairo_fill (cr);
|
|
}
|
|
}
|
|
|
|
|
|
/* This string is a path description as found in SVG files. You can
|
|
* use Inkscape to create the SVG file, then copy the path from it.
|
|
* It works best if you combine all paths into one. Inkscape has a
|
|
* function to do that.
|
|
*/
|
|
static const gchar mascot_path[] = "m -207.49076,-47.11613 c -0.83216,0.01379 -1.75994,0.132794 -2.79626,0.374018 -3.0213,0.703292 -6.37381,5.151603 -6.05031,14.544176 -3.3946,0.694591 -9.06197,7.571488 -14.96578,13.773355 -5.10251,5.360107 -8.01715,9.8272102 -11.49916,16.1833234 -2.25177,2.9309451 -4.75772,3.6115694 -4.4792,9.7814676 0.1825,4.042811 2.18347,6.848398 4.34722,9.025833 3.50452,3.526666 6.90015,5.679308 10.28542,7.064055 4.20093,0.376871 7.16515,-0.637152 8.91693,-2.241495 1.79411,-1.643108 2.47524,-3.929753 1.86602,-6.6366 -0.17079,-0.760554 0.30681,-1.515692 1.06717,-1.687331 0.76085,-0.171332 1.51658,0.306392 1.6882,1.067174 0.79537,3.533949 -0.17735,7.017482 -2.71362,9.340293 -0.99661,0.91273 -2.21366,1.638713 -3.6202,2.15653 2.61496,0.458532 5.26434,0.718712 7.99253,1.048777 15.53163,1.879066 28.88302,-5.611083 38.57054,-21.5296844 16.58511,-10.995142 20.90751,-22.4499396 17.3264,-28.5055596 -6.37546,-10.780846 -20.0562,-5.820933 -29.08892,-3.471589 -1.94526,-2.902299 -4.67843,-4.841714 -7.86641,-6.2255 -1.82789,-4.48163 -0.9364,-14.194573 -8.98057,-14.061243 z m 29.60192,29.535646 c 1.73264,0.02249 3.3123,0.638684 4.04503,1.930544 3.44158,6.0677904 -2.18521,8.8680325 -5.76388,9.9108149 -1.6963,0.4942794 -0.35659,-2.7174403 -1.50163,-4.7086889 -1.16512,-2.026143 -3.8958,-2.136121 -3.19217,-3.684144 1.02102,-2.246332 3.88034,-3.481403 6.41265,-3.448526 z m -35.13515,7.116319 c 1.43053,0.0011 2.46779,0.5782959 3.18604,1.2376872 0.95765,0.8791847 1.21517,1.1382517 1.34834,1.9045536 0.1303,0.7497755 -0.0888,0.9983955 -0.61345,2.1991595 -0.52462,1.2007584 -1.485,2.6039192 -3.22895,3.4134837 -1.91429,0.88863155 -3.56872,0.6194623 -4.7446,0.1281774 -1.17588,-0.4912848 -1.54865,-0.5370616 -1.87331,-1.2046908 -0.47615,-0.97914 -0.15911,-1.5456442 0.30657,-3.2520216 0.46568,-1.7063774 1.55605,-3.5736123 4.05495,-4.217003 0.56701,-0.145986 1.08757,-0.209702 1.56441,-0.209346 z m -32.03934,15.4650644 c 1.1541,-0.06197 3.06966,1.7813368 4.21963,2.5720075 1.47196,1.012063 4.80239,2.0588852 4.0678,4.0181711 -0.64273,1.714304 -4.04364,2.034381 -6.83193,-0.256067 -1.9952,-1.638955 -2.61532,-4.8667661 -2.2123,-5.7688505 0.17023,-0.381035 0.43365,-0.5479098 0.7568,-0.5652611 z ";
|
|
|
|
|
|
static const gchar eyes_path[] = "M z"; // #heckimp skip
|
|
|
|
|
|
static cairo_path_t *mascot_cairo_path = NULL;
|
|
static gdouble mascot_x1, mascot_y1;
|
|
static gdouble mascot_x2, mascot_y2;
|
|
|
|
static cairo_path_t *eyes_cairo_path = NULL;
|
|
static gdouble eyes_x1, eyes_y1;
|
|
static gdouble eyes_x2, eyes_y2;
|
|
|
|
|
|
static void parse_path_data (cairo_t *cr,
|
|
const gchar *data);
|
|
static void mascot_get_extents (cairo_t *cr);
|
|
static void eyes_get_extents (cairo_t *cr);
|
|
|
|
|
|
/**
|
|
* pika_cairo_mascot:
|
|
* @cr: Cairo context
|
|
* @x: x position
|
|
* @y: y position
|
|
*
|
|
* Draw a Wilber path at position @x, @y.
|
|
*/
|
|
void
|
|
pika_cairo_mascot (cairo_t *cr,
|
|
gdouble x,
|
|
gdouble y)
|
|
{
|
|
pika_cairo_mascot_internal (NULL, cr, x, y, 1.0, 0.0);
|
|
}
|
|
|
|
static void
|
|
pika_cairo_mascot_weak_notify (gpointer data,
|
|
GObject *widget)
|
|
{
|
|
cairo_mascot_widgets = g_slist_remove (cairo_mascot_widgets, widget);
|
|
}
|
|
|
|
static void
|
|
pika_cairo_mascot_internal (GtkWidget *widget,
|
|
cairo_t *cr,
|
|
gdouble x,
|
|
gdouble y,
|
|
gdouble factor,
|
|
gdouble max_eye_angle)
|
|
{
|
|
mascot_get_extents (cr);
|
|
|
|
cairo_save (cr);
|
|
|
|
cairo_translate (cr, x - mascot_x1, y - mascot_y1);
|
|
cairo_append_path (cr, mascot_cairo_path);
|
|
|
|
cairo_restore (cr);
|
|
|
|
pika_cairo_eyes (widget, cr, x, y, factor, max_eye_angle);
|
|
|
|
if (widget && ! g_slist_find (cairo_mascot_widgets, widget))
|
|
{
|
|
cairo_mascot_widgets = g_slist_prepend (cairo_mascot_widgets, widget);
|
|
|
|
g_object_weak_ref (G_OBJECT (widget),
|
|
pika_cairo_mascot_weak_notify, NULL);
|
|
}
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
gdouble x;
|
|
gdouble y;
|
|
gdouble radius;
|
|
|
|
gdouble a;
|
|
gdouble b;
|
|
gdouble r;
|
|
} Eye;
|
|
|
|
static const Eye eyes[2] =
|
|
{
|
|
{ .x = (344.151 + 382.496) / 2.0,
|
|
.y = (501.128 + 539.473) / 2.0,
|
|
.radius = (382.496 - 344.151) / 2.0,
|
|
|
|
.a = 25.0 * G_PI / 180.0,
|
|
.b = 24.0 * G_PI / 180.0,
|
|
.r = 0.475
|
|
},
|
|
|
|
{ .x = (385.287 + 442.878) / 2.0,
|
|
.y = (491.431 + 549.022) / 2.0,
|
|
.radius = (442.878 - 385.287) / 2.0,
|
|
|
|
.a = 34.0 * G_PI / 180.0,
|
|
.b = 19.0 * G_PI / 180.0,
|
|
.r = 0.5
|
|
}
|
|
};
|
|
|
|
typedef struct
|
|
{
|
|
gdouble a;
|
|
gdouble b;
|
|
} EyeState;
|
|
|
|
typedef struct
|
|
{
|
|
EyeState eyes[2];
|
|
gdouble x;
|
|
gdouble y;
|
|
gdouble factor;
|
|
gdouble max_eye_angle;
|
|
gdouble t;
|
|
gint timeout_id;
|
|
} EyesState;
|
|
|
|
static EyesState *
|
|
eyes_state_new (void)
|
|
{
|
|
EyesState *state = g_slice_new0 (EyesState);
|
|
gint i;
|
|
|
|
for (i = 0; i < 2; i++)
|
|
{
|
|
state->eyes[i].a = eyes[i].a;
|
|
state->eyes[i].b = eyes[i].b;
|
|
}
|
|
|
|
state->t = (gdouble) g_get_monotonic_time () / G_TIME_SPAN_SECOND;
|
|
|
|
return state;
|
|
}
|
|
|
|
static void
|
|
eyes_state_free (EyesState *state)
|
|
{
|
|
if (state->timeout_id)
|
|
g_source_remove (state->timeout_id);
|
|
|
|
g_slice_free (EyesState, state);
|
|
}
|
|
|
|
static gboolean
|
|
pika_cairo_pointer_eyes_timeout (GtkWidget *widget)
|
|
{
|
|
GdkSeat *seat;
|
|
EyesState *state;
|
|
gdouble t;
|
|
gint pointer_x;
|
|
gint pointer_y;
|
|
GtkAllocation allocation;
|
|
GdkWindow *window;
|
|
gint window_x;
|
|
gint window_y;
|
|
gint redraw = 2;
|
|
gint i;
|
|
|
|
state = g_object_get_data (G_OBJECT (widget), "mascot-eyes-state");
|
|
|
|
t = (gdouble) g_get_monotonic_time () / G_TIME_SPAN_SECOND;
|
|
|
|
seat = gdk_display_get_default_seat (gdk_display_get_default ());
|
|
|
|
gdk_device_get_position (gdk_seat_get_pointer (seat),
|
|
NULL, &pointer_x, &pointer_y);
|
|
|
|
gtk_widget_get_allocation (widget, &allocation);
|
|
|
|
window = gtk_widget_get_window (widget);
|
|
|
|
if (window)
|
|
gdk_window_get_origin (window, &window_x, &window_y);
|
|
|
|
for (i = 0; i < 2; i++)
|
|
{
|
|
const Eye *eye = &eyes[i];
|
|
gdouble a;
|
|
gdouble b;
|
|
gdouble c;
|
|
PikaVector3 u;
|
|
PikaVector3 v;
|
|
PikaVector3 w;
|
|
|
|
if (pointer_eyes)
|
|
{
|
|
gdouble screen_x;
|
|
gdouble screen_y;
|
|
gdouble z = 220.0 * state->factor;
|
|
gdouble d;
|
|
|
|
screen_x = (eye->x + state->x - mascot_x1) * state->factor;
|
|
screen_y = (eye->y + state->y - mascot_y1) * state->factor;
|
|
|
|
if (! gtk_widget_get_has_window (widget))
|
|
{
|
|
screen_x += allocation.x;
|
|
screen_y += allocation.y;
|
|
}
|
|
|
|
if (window)
|
|
{
|
|
screen_x += window_x;
|
|
screen_y += window_y;
|
|
}
|
|
|
|
d = sqrt (SQR (pointer_x - screen_x) + SQR (pointer_y - screen_y));
|
|
a = atan2 (pointer_y - screen_y, pointer_x - screen_x);
|
|
b = atan (d / z);
|
|
b = MIN (b, state->max_eye_angle);
|
|
}
|
|
else
|
|
{
|
|
a = eyes[i].a;
|
|
b = eyes[i].b;
|
|
}
|
|
|
|
if (a == state->eyes[i].a && b == state->eyes[i].b)
|
|
{
|
|
redraw--;
|
|
|
|
continue;
|
|
}
|
|
|
|
u.x = sin (state->eyes[i].b) * cos (state->eyes[i].a);
|
|
u.y = sin (state->eyes[i].b) * sin (state->eyes[i].a);
|
|
u.z = cos (state->eyes[i].b);
|
|
|
|
v.x = sin (b) * cos (a);
|
|
v.y = sin (b) * sin (a);
|
|
v.z = cos (b);
|
|
|
|
c = acos (pika_vector3_inner_product (&u, &v));
|
|
|
|
if (c < 1e-2)
|
|
{
|
|
state->eyes[i].a = a;
|
|
state->eyes[i].b = b;
|
|
|
|
continue;
|
|
}
|
|
|
|
c *= 1.0 - exp (-(t - state->t) * 15.0);
|
|
|
|
w = pika_vector3_cross_product (&u, &v);
|
|
w = pika_vector3_cross_product (&w, &u);
|
|
pika_vector3_normalize (&w);
|
|
|
|
v.x = u.x * cos (c) + w.x * sin (c);
|
|
v.y = u.y * cos (c) + w.y * sin (c);
|
|
v.z = u.z * cos (c) + w.z * sin (c);
|
|
|
|
a = atan2 (v.y, v.x);
|
|
b = acos (v.z);
|
|
|
|
state->eyes[i].a = a;
|
|
state->eyes[i].b = b;
|
|
}
|
|
|
|
state->t = t;
|
|
|
|
if (redraw)
|
|
{
|
|
state->timeout_id = 0;
|
|
|
|
gtk_widget_queue_draw (widget);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
else if (! pointer_eyes)
|
|
{
|
|
state->timeout_id = 0;
|
|
|
|
g_object_set_data (G_OBJECT (widget), "mascot-eyes-state", NULL);
|
|
gtk_widget_queue_draw (widget);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
return G_SOURCE_CONTINUE;
|
|
}
|
|
|
|
static void
|
|
pika_cairo_pointer_eyes (GtkWidget *widget,
|
|
cairo_t *cr,
|
|
gdouble x,
|
|
gdouble y,
|
|
gdouble factor,
|
|
gdouble max_eye_angle)
|
|
{
|
|
EyesState *state;
|
|
gint i;
|
|
|
|
state = g_object_get_data (G_OBJECT (widget), "mascot-eyes-state");
|
|
|
|
if (! state)
|
|
{
|
|
state = eyes_state_new ();
|
|
|
|
g_object_set_data_full (G_OBJECT (widget), "mascot-eyes-state", state,
|
|
(GDestroyNotify) eyes_state_free);
|
|
}
|
|
|
|
for (i = 0; i < 2; i++)
|
|
{
|
|
const Eye *eye = &eyes[i];
|
|
gdouble R = eye->radius;
|
|
gdouble r = eye->r * eye->radius;
|
|
gint j;
|
|
|
|
cairo_save (cr);
|
|
|
|
cairo_translate (cr, eye->x, eye->y);
|
|
cairo_rotate (cr, state->eyes[i].a);
|
|
|
|
for (j = 0; j < 32; j++)
|
|
{
|
|
gdouble a = -2.0 * G_PI * j / 32.0;
|
|
gdouble u = r * cos (a);
|
|
gdouble v = r * sin (a);
|
|
gdouble w = sqrt (SQR (R) - SQR (v));
|
|
gdouble b = asin (u / w);
|
|
|
|
b = CLAMP (b + state->eyes[i].b, -G_PI / 2.0, +G_PI / 2.0);
|
|
u = w * sin (b);
|
|
|
|
if (j == 0)
|
|
cairo_move_to (cr, u, v);
|
|
else
|
|
cairo_line_to (cr, u, v);
|
|
}
|
|
|
|
cairo_close_path (cr);
|
|
|
|
cairo_restore (cr);
|
|
}
|
|
|
|
state->x = x;
|
|
state->y = y;
|
|
state->factor = factor;
|
|
state->max_eye_angle = max_eye_angle;
|
|
|
|
if (! state->timeout_id)
|
|
{
|
|
state->timeout_id =
|
|
g_timeout_add (17,
|
|
(GSourceFunc) pika_cairo_pointer_eyes_timeout,
|
|
widget);
|
|
}
|
|
}
|
|
|
|
static void
|
|
pika_cairo_eyes (GtkWidget *widget,
|
|
cairo_t *cr,
|
|
gdouble x,
|
|
gdouble y,
|
|
gdouble factor,
|
|
gdouble max_eye_angle)
|
|
{
|
|
mascot_get_extents (cr);
|
|
eyes_get_extents (cr);
|
|
|
|
cairo_save (cr);
|
|
|
|
cairo_translate (cr, x - mascot_x1, y - mascot_y1);
|
|
if (widget &&
|
|
(pointer_eyes ||
|
|
g_object_get_data (G_OBJECT (widget), "mascot-eyes-state")))
|
|
{
|
|
pika_cairo_pointer_eyes (widget, cr, x, y, factor, max_eye_angle);
|
|
}
|
|
else
|
|
{
|
|
cairo_append_path (cr, eyes_cairo_path);
|
|
}
|
|
|
|
cairo_restore (cr);
|
|
}
|
|
|
|
void
|
|
pika_cairo_mascot_get_size (cairo_t *cr,
|
|
gdouble *width,
|
|
gdouble *height)
|
|
{
|
|
mascot_get_extents (cr);
|
|
|
|
*width = mascot_x2 - mascot_x1;
|
|
*height = mascot_y2 - mascot_y1;
|
|
}
|
|
|
|
|
|
static void
|
|
mascot_get_extents (cairo_t *unused)
|
|
{
|
|
if (! mascot_cairo_path)
|
|
{
|
|
cairo_surface_t *s = cairo_image_surface_create (CAIRO_FORMAT_A8, 1, 1);
|
|
cairo_t *cr = cairo_create (s);
|
|
|
|
parse_path_data (cr, mascot_path);
|
|
cairo_fill_extents (cr, &mascot_x1, &mascot_y1, &mascot_x2, &mascot_y2);
|
|
|
|
mascot_cairo_path = cairo_copy_path (cr);
|
|
|
|
cairo_destroy (cr);
|
|
cairo_surface_destroy (s);
|
|
}
|
|
}
|
|
|
|
static void
|
|
eyes_get_extents (cairo_t *unused)
|
|
{
|
|
if (! eyes_cairo_path)
|
|
{
|
|
cairo_surface_t *s = cairo_image_surface_create (CAIRO_FORMAT_A8, 1, 1);
|
|
cairo_t *cr = cairo_create (s);
|
|
|
|
parse_path_data (cr, eyes_path);
|
|
cairo_fill_extents (cr, &eyes_x1, &eyes_y1, &eyes_x2, &eyes_y2);
|
|
|
|
eyes_cairo_path = cairo_copy_path (cr);
|
|
|
|
cairo_destroy (cr);
|
|
cairo_surface_destroy (s);
|
|
}
|
|
}
|
|
|
|
/**********************************************************/
|
|
/* Below is the code that parses the actual path data. */
|
|
/* */
|
|
/* This code is taken from librsvg and was originally */
|
|
/* written by Raph Levien <raph@artofcode.com> for Gill. */
|
|
/**********************************************************/
|
|
|
|
typedef struct
|
|
{
|
|
cairo_t *cr;
|
|
gdouble cpx, cpy; /* current point */
|
|
gdouble rpx, rpy; /* reflection point (for 's' and 't' commands) */
|
|
gchar cmd; /* current command (lowercase) */
|
|
gint param; /* number of parameters */
|
|
gboolean rel; /* true if relative coords */
|
|
gdouble params[7]; /* parameters that have been parsed */
|
|
} ParsePathContext;
|
|
|
|
|
|
static void parse_path_default_xy (ParsePathContext *ctx,
|
|
gint n_params);
|
|
static void parse_path_do_cmd (ParsePathContext *ctx,
|
|
gboolean final);
|
|
|
|
|
|
static void
|
|
parse_path_data (cairo_t *cr,
|
|
const gchar *data)
|
|
{
|
|
ParsePathContext ctx;
|
|
|
|
gboolean in_num = FALSE;
|
|
gboolean in_frac = FALSE;
|
|
gboolean in_exp = FALSE;
|
|
gboolean exp_wait_sign = FALSE;
|
|
gdouble val = 0.0;
|
|
gchar c = 0;
|
|
gint sign = 0;
|
|
gint exp = 0;
|
|
gint exp_sign = 0;
|
|
gdouble frac = 0.0;
|
|
gint i;
|
|
|
|
memset (&ctx, 0, sizeof (ParsePathContext));
|
|
|
|
ctx.cr = cr;
|
|
|
|
for (i = 0; ; i++)
|
|
{
|
|
c = data[i];
|
|
if (c >= '0' && c <= '9')
|
|
{
|
|
/* digit */
|
|
if (in_num)
|
|
{
|
|
if (in_exp)
|
|
{
|
|
exp = (exp * 10) + c - '0';
|
|
exp_wait_sign = FALSE;
|
|
}
|
|
else if (in_frac)
|
|
val += (frac *= 0.1) * (c - '0');
|
|
else
|
|
val = (val * 10) + c - '0';
|
|
}
|
|
else
|
|
{
|
|
in_num = TRUE;
|
|
in_frac = FALSE;
|
|
in_exp = FALSE;
|
|
exp = 0;
|
|
exp_sign = 1;
|
|
exp_wait_sign = FALSE;
|
|
val = c - '0';
|
|
sign = 1;
|
|
}
|
|
}
|
|
else if (c == '.')
|
|
{
|
|
if (!in_num)
|
|
{
|
|
in_num = TRUE;
|
|
val = 0;
|
|
}
|
|
in_frac = TRUE;
|
|
frac = 1;
|
|
}
|
|
else if ((c == 'E' || c == 'e') && in_num)
|
|
{
|
|
in_exp = TRUE;
|
|
exp_wait_sign = TRUE;
|
|
exp = 0;
|
|
exp_sign = 1;
|
|
}
|
|
else if ((c == '+' || c == '-') && in_exp)
|
|
{
|
|
exp_sign = c == '+' ? 1 : -1;
|
|
}
|
|
else if (in_num)
|
|
{
|
|
/* end of number */
|
|
|
|
val *= sign * pow (10, exp_sign * exp);
|
|
if (ctx.rel)
|
|
{
|
|
/* Handle relative coordinates. This switch statement attempts
|
|
to determine _what_ the coords are relative to. This is
|
|
underspecified in the 12 Apr working draft. */
|
|
switch (ctx.cmd)
|
|
{
|
|
case 'l':
|
|
case 'm':
|
|
case 'c':
|
|
case 's':
|
|
case 'q':
|
|
case 't':
|
|
/* rule: even-numbered params are x-relative, odd-numbered
|
|
are y-relative */
|
|
if ((ctx.param & 1) == 0)
|
|
val += ctx.cpx;
|
|
else if ((ctx.param & 1) == 1)
|
|
val += ctx.cpy;
|
|
break;
|
|
|
|
case 'a':
|
|
/* rule: sixth and seventh are x and y, rest are not
|
|
relative */
|
|
if (ctx.param == 5)
|
|
val += ctx.cpx;
|
|
else if (ctx.param == 6)
|
|
val += ctx.cpy;
|
|
break;
|
|
case 'h':
|
|
/* rule: x-relative */
|
|
val += ctx.cpx;
|
|
break;
|
|
case 'v':
|
|
/* rule: y-relative */
|
|
val += ctx.cpy;
|
|
break;
|
|
}
|
|
}
|
|
|
|
ctx.params[ctx.param++] = val;
|
|
parse_path_do_cmd (&ctx, FALSE);
|
|
in_num = FALSE;
|
|
}
|
|
|
|
if (c == '\0')
|
|
break;
|
|
else if ((c == '+' || c == '-') && !exp_wait_sign)
|
|
{
|
|
sign = c == '+' ? 1 : -1;
|
|
val = 0;
|
|
in_num = TRUE;
|
|
in_frac = FALSE;
|
|
in_exp = FALSE;
|
|
exp = 0;
|
|
exp_sign = 1;
|
|
exp_wait_sign = FALSE;
|
|
}
|
|
else if (c == 'z' || c == 'Z')
|
|
{
|
|
if (ctx.param)
|
|
parse_path_do_cmd (&ctx, TRUE);
|
|
|
|
cairo_close_path (ctx.cr);
|
|
}
|
|
else if (c >= 'A' && c <= 'Z' && c != 'E')
|
|
{
|
|
if (ctx.param)
|
|
parse_path_do_cmd (&ctx, TRUE);
|
|
ctx.cmd = c + 'a' - 'A';
|
|
ctx.rel = FALSE;
|
|
}
|
|
else if (c >= 'a' && c <= 'z' && c != 'e')
|
|
{
|
|
if (ctx.param)
|
|
parse_path_do_cmd (&ctx, TRUE);
|
|
ctx.cmd = c;
|
|
ctx.rel = TRUE;
|
|
}
|
|
/* else c _should_ be whitespace or , */
|
|
}
|
|
}
|
|
|
|
/* supply defaults for missing parameters, assuming relative coordinates
|
|
are to be interpreted as x,y */
|
|
static void
|
|
parse_path_default_xy (ParsePathContext *ctx,
|
|
gint n_params)
|
|
{
|
|
gint i;
|
|
|
|
if (ctx->rel)
|
|
{
|
|
for (i = ctx->param; i < n_params; i++)
|
|
{
|
|
if (i > 2)
|
|
ctx->params[i] = ctx->params[i - 2];
|
|
else if (i == 1)
|
|
ctx->params[i] = ctx->cpy;
|
|
else if (i == 0)
|
|
/* we shouldn't get here (ctx->param > 0 as precondition) */
|
|
ctx->params[i] = ctx->cpx;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (i = ctx->param; i < n_params; i++)
|
|
ctx->params[i] = 0.0;
|
|
}
|
|
}
|
|
|
|
static void
|
|
parse_path_do_cmd (ParsePathContext *ctx,
|
|
gboolean final)
|
|
{
|
|
switch (ctx->cmd)
|
|
{
|
|
case 'm':
|
|
/* moveto */
|
|
if (ctx->param == 2 || final)
|
|
{
|
|
parse_path_default_xy (ctx, 2);
|
|
|
|
ctx->cpx = ctx->rpx = ctx->params[0];
|
|
ctx->cpy = ctx->rpy = ctx->params[1];
|
|
|
|
cairo_move_to (ctx->cr, ctx->cpx, ctx->cpy);
|
|
|
|
ctx->param = 0;
|
|
}
|
|
break;
|
|
|
|
case 'l':
|
|
/* lineto */
|
|
if (ctx->param == 2 || final)
|
|
{
|
|
parse_path_default_xy (ctx, 2);
|
|
|
|
ctx->cpx = ctx->rpx = ctx->params[0];
|
|
ctx->cpy = ctx->rpy = ctx->params[1];
|
|
|
|
cairo_line_to (ctx->cr, ctx->cpx, ctx->cpy);
|
|
|
|
ctx->param = 0;
|
|
}
|
|
break;
|
|
|
|
case 'c':
|
|
/* curveto */
|
|
if (ctx->param == 6 || final)
|
|
{
|
|
gdouble x, y;
|
|
|
|
parse_path_default_xy (ctx, 6);
|
|
|
|
x = ctx->params[0];
|
|
y = ctx->params[1];
|
|
ctx->rpx = ctx->params[2];
|
|
ctx->rpy = ctx->params[3];
|
|
ctx->cpx = ctx->params[4];
|
|
ctx->cpy = ctx->params[5];
|
|
|
|
cairo_curve_to (ctx->cr,
|
|
x, y, ctx->rpx, ctx->rpy, ctx->cpx, ctx->cpy);
|
|
|
|
ctx->param = 0;
|
|
}
|
|
break;
|
|
|
|
case 's':
|
|
/* smooth curveto */
|
|
if (ctx->param == 4 || final)
|
|
{
|
|
gdouble x, y;
|
|
|
|
parse_path_default_xy (ctx, 4);
|
|
|
|
x = 2 * ctx->cpx - ctx->rpx;
|
|
y = 2 * ctx->cpy - ctx->rpy;
|
|
ctx->rpx = ctx->params[0];
|
|
ctx->rpy = ctx->params[1];
|
|
ctx->cpx = ctx->params[2];
|
|
ctx->cpy = ctx->params[3];
|
|
|
|
cairo_curve_to (ctx->cr,
|
|
x, y, ctx->rpx, ctx->rpy, ctx->cpx, ctx->cpy);
|
|
|
|
ctx->param = 0;
|
|
}
|
|
break;
|
|
|
|
case 'h':
|
|
/* horizontal lineto */
|
|
if (ctx->param == 1)
|
|
{
|
|
ctx->cpx = ctx->rpx = ctx->params[0];
|
|
|
|
cairo_line_to (ctx->cr, ctx->cpx, ctx->cpy);
|
|
|
|
ctx->param = 0;
|
|
}
|
|
break;
|
|
|
|
case 'v':
|
|
/* vertical lineto */
|
|
if (ctx->param == 1)
|
|
{
|
|
ctx->cpy = ctx->rpy = ctx->params[0];
|
|
|
|
cairo_line_to (ctx->cr, ctx->cpx, ctx->cpy);
|
|
|
|
ctx->param = 0;
|
|
}
|
|
break;
|
|
|
|
case 'q':
|
|
/* quadratic bezier curveto */
|
|
if (ctx->param == 4 || final)
|
|
{
|
|
parse_path_default_xy (ctx, 4);
|
|
|
|
ctx->rpx = ctx->params[0];
|
|
ctx->rpy = ctx->params[1];
|
|
ctx->cpx = ctx->params[2];
|
|
ctx->cpy = ctx->params[3];
|
|
|
|
g_warning ("quadratic bezier curveto not implemented");
|
|
|
|
ctx->param = 0;
|
|
}
|
|
break;
|
|
|
|
case 't':
|
|
/* truetype quadratic bezier curveto */
|
|
if (ctx->param == 2 || final)
|
|
{
|
|
parse_path_default_xy (ctx, 2);
|
|
|
|
ctx->rpx = 2 * ctx->cpx - ctx->rpx;
|
|
ctx->rpy = 2 * ctx->cpy - ctx->rpy;
|
|
ctx->cpx = ctx->params[0];
|
|
ctx->cpy = ctx->params[1];
|
|
|
|
g_warning ("truetype quadratic bezier curveto not implemented");
|
|
|
|
ctx->param = 0;
|
|
}
|
|
else if (final)
|
|
{
|
|
if (ctx->param > 2)
|
|
{
|
|
parse_path_default_xy (ctx, 4);
|
|
|
|
ctx->rpx = ctx->params[0];
|
|
ctx->rpy = ctx->params[1];
|
|
ctx->cpx = ctx->params[2];
|
|
ctx->cpy = ctx->params[3];
|
|
|
|
g_warning ("conicto not implemented");
|
|
}
|
|
else
|
|
{
|
|
parse_path_default_xy (ctx, 2);
|
|
|
|
ctx->cpx = ctx->rpx = ctx->params[0];
|
|
ctx->cpy = ctx->rpy = ctx->params[1];
|
|
|
|
cairo_line_to (ctx->cr, ctx->cpx, ctx->cpy);
|
|
}
|
|
|
|
ctx->param = 0;
|
|
}
|
|
break;
|
|
|
|
case 'a':
|
|
if (ctx->param == 7 || final)
|
|
{
|
|
ctx->cpx = ctx->rpx = ctx->params[5];
|
|
ctx->cpy = ctx->rpy = ctx->params[6];
|
|
|
|
g_warning ("arcto not implemented");
|
|
|
|
ctx->param = 0;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
ctx->param = 0;
|
|
break;
|
|
}
|
|
}
|