1 #define __SP_DYNA_DRAW_CONTEXT_C__
3 /*
4 * Handwriting-like drawing mode
5 *
6 * Authors:
7 * Mitsuru Oka <oka326@parkcity.ne.jp>
8 * Lauris Kaplinski <lauris@kaplinski.com>
9 * bulia byak <buliabyak@users.sf.net>
10 * MenTaLguY <mental@rydia.net>
11 *
12 * The original dynadraw code:
13 * Paul Haeberli <paul@sgi.com>
14 *
15 * Copyright (C) 1998 The Free Software Foundation
16 * Copyright (C) 1999-2005 authors
17 * Copyright (C) 2001-2002 Ximian, Inc.
18 * Copyright (C) 2005-2006 bulia byak
19 * Copyright (C) 2006 MenTaLguY
20 *
21 * Released under GNU GPL, read the file 'COPYING' for more information
22 */
24 #define noDYNA_DRAW_VERBOSE
26 #include "config.h"
28 #include <gtk/gtk.h>
29 #include <gdk/gdkkeysyms.h>
30 #include <glibmm/i18n.h>
32 #include "svg/svg.h"
33 #include "display/canvas-bpath.h"
34 #include "display/bezier-utils.h"
36 #include <glib/gmem.h>
37 #include "macros.h"
38 #include "document.h"
39 #include "selection.h"
40 #include "desktop.h"
41 #include "desktop-events.h"
42 #include "desktop-handles.h"
43 #include "desktop-affine.h"
44 #include "desktop-style.h"
45 #include "message-context.h"
46 #include "pixmaps/cursor-calligraphy.xpm"
47 #include "pixmaps/cursor-calligraphy.pixbuf"
48 #include "dyna-draw-context.h"
49 #include "libnr/n-art-bpath.h"
50 #include "libnr/nr-path.h"
51 #include "xml/repr.h"
52 #include "context-fns.h"
53 #include "sp-item.h"
54 #include "inkscape.h"
55 #include "color.h"
57 #define DDC_RED_RGBA 0xff0000ff
59 #define SAMPLE_TIMEOUT 10
60 #define TOLERANCE_LINE 1.0
61 #define TOLERANCE_CALLIGRAPHIC 3.0
62 #define DYNA_EPSILON 0.5e-2
64 #define DYNA_MIN_WIDTH 1.0e-6
66 #define DRAG_MIN 0.0
67 #define DRAG_DEFAULT 1.0
68 #define DRAG_MAX 1.0
70 static void sp_dyna_draw_context_class_init(SPDynaDrawContextClass *klass);
71 static void sp_dyna_draw_context_init(SPDynaDrawContext *ddc);
72 static void sp_dyna_draw_context_dispose(GObject *object);
74 static void sp_dyna_draw_context_setup(SPEventContext *ec);
75 static void sp_dyna_draw_context_set(SPEventContext *ec, gchar const *key, gchar const *val);
76 static gint sp_dyna_draw_context_root_handler(SPEventContext *ec, GdkEvent *event);
78 static void clear_current(SPDynaDrawContext *dc);
79 static void set_to_accumulated(SPDynaDrawContext *dc);
80 static void add_cap(SPCurve *curve, NR::Point const &pre, NR::Point const &from, NR::Point const &to, NR::Point const &post, double rounding);
81 static void accumulate_calligraphic(SPDynaDrawContext *dc);
83 static void fit_and_split(SPDynaDrawContext *ddc, gboolean release);
85 static void sp_dyna_draw_reset(SPDynaDrawContext *ddc, NR::Point p);
86 static NR::Point sp_dyna_draw_get_npoint(SPDynaDrawContext const *ddc, NR::Point v);
87 static NR::Point sp_dyna_draw_get_vpoint(SPDynaDrawContext const *ddc, NR::Point n);
88 static void draw_temporary_box(SPDynaDrawContext *dc);
91 static SPEventContextClass *parent_class;
93 GtkType
94 sp_dyna_draw_context_get_type(void)
95 {
96 static GType type = 0;
97 if (!type) {
98 GTypeInfo info = {
99 sizeof(SPDynaDrawContextClass),
100 NULL, NULL,
101 (GClassInitFunc) sp_dyna_draw_context_class_init,
102 NULL, NULL,
103 sizeof(SPDynaDrawContext),
104 4,
105 (GInstanceInitFunc) sp_dyna_draw_context_init,
106 NULL, /* value_table */
107 };
108 type = g_type_register_static(SP_TYPE_EVENT_CONTEXT, "SPDynaDrawContext", &info, (GTypeFlags)0);
109 }
110 return type;
111 }
113 static void
114 sp_dyna_draw_context_class_init(SPDynaDrawContextClass *klass)
115 {
116 GObjectClass *object_class = (GObjectClass *) klass;
117 SPEventContextClass *event_context_class = (SPEventContextClass *) klass;
119 parent_class = (SPEventContextClass*)g_type_class_peek_parent(klass);
121 object_class->dispose = sp_dyna_draw_context_dispose;
123 event_context_class->setup = sp_dyna_draw_context_setup;
124 event_context_class->set = sp_dyna_draw_context_set;
125 event_context_class->root_handler = sp_dyna_draw_context_root_handler;
126 }
128 static void
129 sp_dyna_draw_context_init(SPDynaDrawContext *ddc)
130 {
131 SPEventContext *event_context = SP_EVENT_CONTEXT(ddc);
133 event_context->cursor_shape = cursor_calligraphy_xpm;
134 event_context->cursor_pixbuf = gdk_pixbuf_new_from_inline(
135 -1,
136 cursor_calligraphy_pixbuf,
137 FALSE,
138 NULL);
139 event_context->hot_x = 4;
140 event_context->hot_y = 4;
142 ddc->accumulated = NULL;
143 ddc->segments = NULL;
144 ddc->currentcurve = NULL;
145 ddc->currentshape = NULL;
146 ddc->npoints = 0;
147 ddc->cal1 = NULL;
148 ddc->cal2 = NULL;
149 ddc->repr = NULL;
151 /* DynaDraw values */
152 ddc->cur = NR::Point(0,0);
153 ddc->last = NR::Point(0,0);
154 ddc->vel = NR::Point(0,0);
155 ddc->acc = NR::Point(0,0);
156 ddc->ang = NR::Point(0,0);
157 ddc->del = NR::Point(0,0);
159 /* attributes */
160 ddc->dragging = FALSE;
162 ddc->mass = 0.3;
163 ddc->drag = DRAG_DEFAULT;
164 ddc->angle = 30.0;
165 ddc->width = 0.2;
167 ddc->vel_thin = 0.1;
168 ddc->flatness = 0.9;
169 ddc->cap_rounding = 0.0;
171 ddc->abs_width = false;
172 ddc->keep_selected = true;
173 }
175 static void
176 sp_dyna_draw_context_dispose(GObject *object)
177 {
178 SPDynaDrawContext *ddc = SP_DYNA_DRAW_CONTEXT(object);
180 if (ddc->accumulated) {
181 ddc->accumulated = sp_curve_unref(ddc->accumulated);
182 }
184 while (ddc->segments) {
185 gtk_object_destroy(GTK_OBJECT(ddc->segments->data));
186 ddc->segments = g_slist_remove(ddc->segments, ddc->segments->data);
187 }
189 if (ddc->currentcurve) ddc->currentcurve = sp_curve_unref(ddc->currentcurve);
190 if (ddc->cal1) ddc->cal1 = sp_curve_unref(ddc->cal1);
191 if (ddc->cal2) ddc->cal2 = sp_curve_unref(ddc->cal2);
193 if (ddc->currentshape) {
194 gtk_object_destroy(GTK_OBJECT(ddc->currentshape));
195 ddc->currentshape = NULL;
196 }
198 if (ddc->_message_context) {
199 delete ddc->_message_context;
200 }
202 G_OBJECT_CLASS(parent_class)->dispose(object);
203 }
205 static void
206 sp_dyna_draw_context_setup(SPEventContext *ec)
207 {
208 SPDynaDrawContext *ddc = SP_DYNA_DRAW_CONTEXT(ec);
210 if (((SPEventContextClass *) parent_class)->setup)
211 ((SPEventContextClass *) parent_class)->setup(ec);
213 ddc->accumulated = sp_curve_new_sized(32);
214 ddc->currentcurve = sp_curve_new_sized(4);
216 ddc->cal1 = sp_curve_new_sized(32);
217 ddc->cal2 = sp_curve_new_sized(32);
219 ddc->currentshape = sp_canvas_item_new(sp_desktop_sketch(ec->desktop), SP_TYPE_CANVAS_BPATH, NULL);
220 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(ddc->currentshape), DDC_RED_RGBA, SP_WIND_RULE_EVENODD);
221 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(ddc->currentshape), 0x00000000, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
222 /* fixme: Cannot we cascade it to root more clearly? */
223 g_signal_connect(G_OBJECT(ddc->currentshape), "event", G_CALLBACK(sp_desktop_root_handler), ec->desktop);
225 sp_event_context_read(ec, "mass");
226 sp_event_context_read(ec, "wiggle");
227 sp_event_context_read(ec, "angle");
228 sp_event_context_read(ec, "width");
229 sp_event_context_read(ec, "thinning");
230 sp_event_context_read(ec, "tremor");
231 sp_event_context_read(ec, "flatness");
232 sp_event_context_read(ec, "usepressure");
233 sp_event_context_read(ec, "usetilt");
234 sp_event_context_read(ec, "abs_width");
235 sp_event_context_read(ec, "keep_selected");
236 sp_event_context_read(ec, "cap_rounding");
238 ddc->is_drawing = false;
240 ddc->_message_context = new Inkscape::MessageContext((ec->desktop)->messageStack());
241 }
243 static void
244 sp_dyna_draw_context_set(SPEventContext *ec, gchar const *key, gchar const *val)
245 {
246 SPDynaDrawContext *ddc = SP_DYNA_DRAW_CONTEXT(ec);
248 if (!strcmp(key, "mass")) {
249 double const dval = ( val ? g_ascii_strtod (val, NULL) : 0.2 );
250 ddc->mass = CLAMP(dval, -1000.0, 1000.0);
251 } else if (!strcmp(key, "wiggle")) {
252 double const dval = ( val ? g_ascii_strtod (val, NULL) : (1 - DRAG_DEFAULT));
253 ddc->drag = CLAMP((1 - dval), DRAG_MIN, DRAG_MAX); // drag is inverse to wiggle
254 } else if (!strcmp(key, "angle")) {
255 double const dval = ( val ? g_ascii_strtod (val, NULL) : 0.0);
256 ddc->angle = CLAMP (dval, -90, 90);
257 } else if (!strcmp(key, "width")) {
258 double const dval = ( val ? g_ascii_strtod (val, NULL) : 0.1 );
259 ddc->width = CLAMP(dval, -1000.0, 1000.0);
260 } else if (!strcmp(key, "thinning")) {
261 double const dval = ( val ? g_ascii_strtod (val, NULL) : 0.1 );
262 ddc->vel_thin = CLAMP(dval, -1.0, 1.0);
263 } else if (!strcmp(key, "tremor")) {
264 double const dval = ( val ? g_ascii_strtod (val, NULL) : 0.0 );
265 ddc->tremor = CLAMP(dval, 0.0, 1.0);
266 } else if (!strcmp(key, "flatness")) {
267 double const dval = ( val ? g_ascii_strtod (val, NULL) : 1.0 );
268 ddc->flatness = CLAMP(dval, 0, 1.0);
269 } else if (!strcmp(key, "usepressure")) {
270 ddc->usepressure = (val && strcmp(val, "0"));
271 } else if (!strcmp(key, "usetilt")) {
272 ddc->usetilt = (val && strcmp(val, "0"));
273 } else if (!strcmp(key, "abs_width")) {
274 ddc->abs_width = (val && strcmp(val, "0"));
275 } else if (!strcmp(key, "keep_selected")) {
276 ddc->keep_selected = (val && strcmp(val, "0"));
277 } else if (!strcmp(key, "cap_rounding")) {
278 ddc->cap_rounding = ( val ? g_ascii_strtod (val, NULL) : 0.0 );
279 }
281 //g_print("DDC: %g %g %g %g\n", ddc->mass, ddc->drag, ddc->angle, ddc->width);
282 }
284 static double
285 flerp(double f0, double f1, double p)
286 {
287 return f0 + ( f1 - f0 ) * p;
288 }
290 /* Get normalized point */
291 static NR::Point
292 sp_dyna_draw_get_npoint(SPDynaDrawContext const *dc, NR::Point v)
293 {
294 NR::Rect drect = SP_EVENT_CONTEXT(dc)->desktop->get_display_area();
295 double const max = MAX ( drect.dimensions()[NR::X], drect.dimensions()[NR::Y] );
296 return NR::Point(( v[NR::X] - drect.min()[NR::X] ) / max, ( v[NR::Y] - drect.min()[NR::Y] ) / max);
297 }
299 /* Get view point */
300 static NR::Point
301 sp_dyna_draw_get_vpoint(SPDynaDrawContext const *dc, NR::Point n)
302 {
303 NR::Rect drect = SP_EVENT_CONTEXT(dc)->desktop->get_display_area();
304 double const max = MAX ( drect.dimensions()[NR::X], drect.dimensions()[NR::Y] );
305 return NR::Point(n[NR::X] * max + drect.min()[NR::X], n[NR::Y] * max + drect.min()[NR::Y]);
306 }
308 static void
309 sp_dyna_draw_reset(SPDynaDrawContext *dc, NR::Point p)
310 {
311 dc->last = dc->cur = sp_dyna_draw_get_npoint(dc, p);
312 dc->vel = NR::Point(0,0);
313 dc->acc = NR::Point(0,0);
314 dc->ang = NR::Point(0,0);
315 dc->del = NR::Point(0,0);
316 }
318 static void
319 sp_dyna_draw_extinput(SPDynaDrawContext *dc, GdkEvent *event)
320 {
321 if (gdk_event_get_axis (event, GDK_AXIS_PRESSURE, &dc->pressure))
322 dc->pressure = CLAMP (dc->pressure, DDC_MIN_PRESSURE, DDC_MAX_PRESSURE);
323 else
324 dc->pressure = DDC_DEFAULT_PRESSURE;
326 if (gdk_event_get_axis (event, GDK_AXIS_XTILT, &dc->xtilt))
327 dc->xtilt = CLAMP (dc->xtilt, DDC_MIN_TILT, DDC_MAX_TILT);
328 else
329 dc->xtilt = DDC_DEFAULT_TILT;
331 if (gdk_event_get_axis (event, GDK_AXIS_YTILT, &dc->ytilt))
332 dc->ytilt = CLAMP (dc->ytilt, DDC_MIN_TILT, DDC_MAX_TILT);
333 else
334 dc->ytilt = DDC_DEFAULT_TILT;
335 }
338 static gboolean
339 sp_dyna_draw_apply(SPDynaDrawContext *dc, NR::Point p)
340 {
341 NR::Point n = sp_dyna_draw_get_npoint(dc, p);
343 /* Calculate mass and drag */
344 double const mass = flerp(1.0, 160.0, dc->mass);
345 double const drag = flerp(0.0, 0.5, dc->drag * dc->drag);
347 /* Calculate force and acceleration */
348 NR::Point force = n - dc->cur;
349 if ( NR::L2(force) < DYNA_EPSILON ) {
350 return FALSE;
351 }
353 dc->acc = force / mass;
355 /* Calculate new velocity */
356 dc->vel += dc->acc;
358 /* Calculate angle of drawing tool */
360 double a1;
361 if (dc->usetilt) {
362 // 1a. calculate nib angle from input device tilt:
363 gdouble length = std::sqrt(dc->xtilt*dc->xtilt + dc->ytilt*dc->ytilt);;
365 if (length > 0) {
366 NR::Point ang1 = NR::Point(dc->ytilt/length, dc->xtilt/length);
367 a1 = atan2(ang1);
368 }
369 else
370 a1 = 0.0;
371 }
372 else {
373 // 1b. fixed dc->angle (absolutely flat nib):
374 double const radians = ( (dc->angle - 90) / 180.0 ) * M_PI;
375 NR::Point ang1 = NR::Point(-sin(radians), cos(radians));
376 a1 = atan2(ang1);
377 }
379 // 2. perpendicular to dc->vel (absolutely non-flat nib):
380 gdouble const mag_vel = NR::L2(dc->vel);
381 if ( mag_vel < DYNA_EPSILON ) {
382 return FALSE;
383 }
384 NR::Point ang2 = NR::rot90(dc->vel) / mag_vel;
386 // 3. Average them using flatness parameter:
387 // calculate angles
388 double a2 = atan2(ang2);
389 // flip a2 to force it to be in the same half-circle as a1
390 bool flipped = false;
391 if (fabs (a2-a1) > 0.5*M_PI) {
392 a2 += M_PI;
393 flipped = true;
394 }
395 // normalize a2
396 if (a2 > M_PI)
397 a2 -= 2*M_PI;
398 if (a2 < -M_PI)
399 a2 += 2*M_PI;
400 // find the flatness-weighted bisector angle, unflip if a2 was flipped
401 // FIXME: when dc->vel is oscillating around the fixed angle, the new_ang flips back and forth. How to avoid this?
402 double new_ang = a1 + (1 - dc->flatness) * (a2 - a1) - (flipped? M_PI : 0);
403 // convert to point
404 dc->ang = NR::Point (cos (new_ang), sin (new_ang));
406 /* Apply drag */
407 dc->vel *= 1.0 - drag;
409 /* Update position */
410 dc->last = dc->cur;
411 dc->cur += dc->vel;
413 return TRUE;
414 }
416 static void
417 sp_dyna_draw_brush(SPDynaDrawContext *dc)
418 {
419 g_assert( dc->npoints >= 0 && dc->npoints < SAMPLING_SIZE );
421 // How much velocity thins strokestyle
422 double vel_thin = flerp (0, 160, dc->vel_thin);
424 // Influence of pressure on thickness
425 double pressure_thick = (dc->usepressure ? dc->pressure : 1.0);
427 double width = ( pressure_thick - vel_thin * NR::L2(dc->vel) ) * dc->width;
429 double tremble_left = 0, tremble_right = 0;
430 if (dc->tremor > 0) {
431 // obtain two normally distributed random variables, using polar Box-Muller transform
432 double x1, x2, w, y1, y2;
433 do {
434 x1 = 2.0 * g_random_double_range(0,1) - 1.0;
435 x2 = 2.0 * g_random_double_range(0,1) - 1.0;
436 w = x1 * x1 + x2 * x2;
437 } while ( w >= 1.0 );
438 w = sqrt( (-2.0 * log( w ) ) / w );
439 y1 = x1 * w;
440 y2 = x2 * w;
442 // deflect both left and right edges randomly and independently, so that:
443 // (1) dc->tremor=1 corresponds to sigma=1, decreasing dc->tremor narrows the bell curve;
444 // (2) deflection depends on width, but is upped for small widths for better visual uniformity across widths;
445 // (3) deflection somewhat depends on speed, to prevent fast strokes looking
446 // comparatively smooth and slow ones excessively jittery
447 tremble_left = (y1)*dc->tremor * (0.15 + 0.8*width) * (0.35 + 14*NR::L2(dc->vel));
448 tremble_right = (y2)*dc->tremor * (0.15 + 0.8*width) * (0.35 + 14*NR::L2(dc->vel));
449 }
451 if ( width < 0.02 * dc->width ) {
452 width = 0.02 * dc->width;
453 }
455 double dezoomify_factor = 0.05 * 1000;
456 if (!dc->abs_width) {
457 dezoomify_factor /= SP_EVENT_CONTEXT(dc)->desktop->current_zoom();
458 }
460 NR::Point del_left = dezoomify_factor * (width + tremble_left) * dc->ang;
461 NR::Point del_right = dezoomify_factor * (width + tremble_right) * dc->ang;
463 NR::Point abs_middle = sp_dyna_draw_get_vpoint(dc, dc->cur);
465 dc->point1[dc->npoints] = abs_middle + del_left;
466 dc->point2[dc->npoints] = abs_middle - del_right;
468 dc->del = 0.5*(del_left + del_right);
470 dc->npoints++;
471 }
473 void
474 sp_ddc_update_toolbox (SPDesktop *desktop, const gchar *id, double value)
475 {
476 desktop->setToolboxAdjustmentValue (id, value);
477 }
479 gint
480 sp_dyna_draw_context_root_handler(SPEventContext *event_context,
481 GdkEvent *event)
482 {
483 SPDynaDrawContext *dc = SP_DYNA_DRAW_CONTEXT(event_context);
484 SPDesktop *desktop = event_context->desktop;
486 gint ret = FALSE;
488 switch (event->type) {
489 case GDK_BUTTON_PRESS:
490 if ( event->button.button == 1 ) {
492 SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(dc);
494 if (Inkscape::have_viable_layer(desktop, dc->_message_context) == false) {
495 return TRUE;
496 }
498 NR::Point const button_w(event->button.x,
499 event->button.y);
500 NR::Point const button_dt(desktop->w2d(button_w));
501 sp_dyna_draw_reset(dc, button_dt);
502 sp_dyna_draw_extinput(dc, event);
503 sp_dyna_draw_apply(dc, button_dt);
504 sp_curve_reset(dc->accumulated);
505 if (dc->repr) {
506 dc->repr = NULL;
507 }
509 /* initialize first point */
510 dc->npoints = 0;
512 sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
513 ( GDK_KEY_PRESS_MASK |
514 GDK_BUTTON_RELEASE_MASK |
515 GDK_POINTER_MOTION_MASK |
516 GDK_BUTTON_PRESS_MASK ),
517 NULL,
518 event->button.time);
520 ret = TRUE;
522 dc->is_drawing = true;
523 }
524 break;
525 case GDK_MOTION_NOTIFY:
526 if ( dc->is_drawing && ( event->motion.state & GDK_BUTTON1_MASK ) ) {
527 dc->dragging = TRUE;
529 NR::Point const motion_w(event->motion.x,
530 event->motion.y);
531 NR::Point const motion_dt(desktop->w2d(motion_w));
533 sp_dyna_draw_extinput(dc, event);
534 if (!sp_dyna_draw_apply(dc, motion_dt)) {
535 ret = TRUE;
536 break;
537 }
539 if ( dc->cur != dc->last ) {
540 sp_dyna_draw_brush(dc);
541 g_assert( dc->npoints > 0 );
542 fit_and_split(dc, FALSE);
543 }
544 ret = TRUE;
545 }
546 break;
548 case GDK_BUTTON_RELEASE:
549 sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), event->button.time);
550 dc->is_drawing = false;
552 if ( dc->dragging && event->button.button == 1 ) {
553 dc->dragging = FALSE;
555 NR::Point const motion_w(event->button.x, event->button.y);
556 NR::Point const motion_dt(desktop->w2d(motion_w));
557 sp_dyna_draw_apply(dc, motion_dt);
559 /* Remove all temporary line segments */
560 while (dc->segments) {
561 gtk_object_destroy(GTK_OBJECT(dc->segments->data));
562 dc->segments = g_slist_remove(dc->segments, dc->segments->data);
563 }
564 /* Create object */
565 fit_and_split(dc, TRUE);
566 accumulate_calligraphic(dc);
567 set_to_accumulated(dc); /* temporal implementation */
568 /* reset accumulated curve */
569 sp_curve_reset(dc->accumulated);
570 clear_current(dc);
571 if (dc->repr) {
572 dc->repr = NULL;
573 }
574 ret = TRUE;
575 }
576 break;
577 case GDK_KEY_PRESS:
578 switch (get_group0_keyval (&event->key)) {
579 case GDK_Up:
580 case GDK_KP_Up:
581 if (!MOD__CTRL_ONLY) {
582 dc->angle += 5.0;
583 if (dc->angle > 90.0)
584 dc->angle = 90.0;
585 sp_ddc_update_toolbox (desktop, "calligraphy-angle", dc->angle);
586 ret = TRUE;
587 }
588 break;
589 case GDK_Down:
590 case GDK_KP_Down:
591 if (!MOD__CTRL_ONLY) {
592 dc->angle -= 5.0;
593 if (dc->angle < -90.0)
594 dc->angle = -90.0;
595 sp_ddc_update_toolbox (desktop, "calligraphy-angle", dc->angle);
596 ret = TRUE;
597 }
598 break;
599 case GDK_Right:
600 case GDK_KP_Right:
601 if (!MOD__CTRL_ONLY) {
602 dc->width += 0.01;
603 if (dc->width > 1.0)
604 dc->width = 1.0;
605 sp_ddc_update_toolbox (desktop, "altx-calligraphy", dc->width * 100); // the same spinbutton is for alt+x
606 ret = TRUE;
607 }
608 break;
609 case GDK_Left:
610 case GDK_KP_Left:
611 if (!MOD__CTRL_ONLY) {
612 dc->width -= 0.01;
613 if (dc->width < 0.01)
614 dc->width = 0.01;
615 sp_ddc_update_toolbox (desktop, "altx-calligraphy", dc->width * 100);
616 ret = TRUE;
617 }
618 break;
619 case GDK_x:
620 case GDK_X:
621 if (MOD__ALT_ONLY) {
622 desktop->setToolboxFocusTo ("altx-calligraphy");
623 ret = TRUE;
624 }
625 break;
626 case GDK_Escape:
627 sp_desktop_selection(desktop)->clear();
628 break;
630 default:
631 break;
632 }
633 default:
634 break;
635 }
637 if (!ret) {
638 if (((SPEventContextClass *) parent_class)->root_handler) {
639 ret = ((SPEventContextClass *) parent_class)->root_handler(event_context, event);
640 }
641 }
643 return ret;
644 }
647 static void
648 clear_current(SPDynaDrawContext *dc)
649 {
650 /* reset bpath */
651 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(dc->currentshape), NULL);
652 /* reset curve */
653 sp_curve_reset(dc->currentcurve);
654 sp_curve_reset(dc->cal1);
655 sp_curve_reset(dc->cal2);
656 /* reset points */
657 dc->npoints = 0;
658 }
660 static void
661 set_to_accumulated(SPDynaDrawContext *dc)
662 {
663 SPDesktop *desktop = SP_EVENT_CONTEXT(dc)->desktop;
665 if (!sp_curve_empty(dc->accumulated)) {
666 NArtBpath *abp;
667 gchar *str;
669 if (!dc->repr) {
670 /* Create object */
671 Inkscape::XML::Node *repr = sp_repr_new("svg:path");
673 /* Set style */
674 sp_desktop_apply_style_tool (desktop, repr, "tools.calligraphic", false);
676 dc->repr = repr;
678 SPItem *item=SP_ITEM(desktop->currentLayer()->appendChildRepr(dc->repr));
679 Inkscape::GC::release(dc->repr);
680 item->transform = SP_ITEM(desktop->currentRoot())->getRelativeTransform(desktop->currentLayer());
681 item->updateRepr();
682 if (dc->keep_selected) {
683 sp_desktop_selection(desktop)->set(dc->repr);
684 } else {
685 sp_desktop_selection(desktop)->clear();
686 }
687 }
688 abp = nr_artpath_affine(sp_curve_first_bpath(dc->accumulated), sp_desktop_dt2root_affine(desktop));
689 str = sp_svg_write_path(abp);
690 g_assert( str != NULL );
691 g_free(abp);
692 dc->repr->setAttribute("d", str);
693 g_free(str);
694 } else {
695 if (dc->repr) {
696 sp_repr_unparent(dc->repr);
697 }
698 dc->repr = NULL;
699 }
701 sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_CALLIGRAPHIC,
702 _("Create calligraphic stroke"));
703 }
705 static void
706 add_cap(SPCurve *curve,
707 NR::Point const &pre, NR::Point const &from,
708 NR::Point const &to, NR::Point const &post,
709 double rounding)
710 {
711 NR::Point vel = rounding * NR::rot90( to - from ) / sqrt(2.0);
712 double mag = NR::L2(vel);
714 NR::Point v_in = from - pre;
715 double mag_in = NR::L2(v_in);
716 if ( mag_in > DYNA_EPSILON ) {
717 v_in = mag * v_in / mag_in;
718 } else {
719 v_in = NR::Point(0, 0);
720 }
722 NR::Point v_out = to - post;
723 double mag_out = NR::L2(v_out);
724 if ( mag_out > DYNA_EPSILON ) {
725 v_out = mag * v_out / mag_out;
726 } else {
727 v_out = NR::Point(0, 0);
728 }
730 if ( NR::L2(v_in) > DYNA_EPSILON || NR::L2(v_out) > DYNA_EPSILON ) {
731 sp_curve_curveto(curve, from + v_in, to + v_out, to);
732 }
733 }
735 static void
736 accumulate_calligraphic(SPDynaDrawContext *dc)
737 {
738 if ( !sp_curve_empty(dc->cal1) && !sp_curve_empty(dc->cal2) ) {
739 sp_curve_reset(dc->accumulated); /* Is this required ?? */
740 SPCurve *rev_cal2 = sp_curve_reverse(dc->cal2);
742 g_assert(dc->cal1->end > 1);
743 g_assert(rev_cal2->end > 1);
744 g_assert(SP_CURVE_SEGMENT(dc->cal1, 0)->code == NR_MOVETO_OPEN);
745 g_assert(SP_CURVE_SEGMENT(rev_cal2, 0)->code == NR_MOVETO_OPEN);
746 g_assert(SP_CURVE_SEGMENT(dc->cal1, 1)->code == NR_CURVETO);
747 g_assert(SP_CURVE_SEGMENT(rev_cal2, 1)->code == NR_CURVETO);
748 g_assert(SP_CURVE_SEGMENT(dc->cal1, dc->cal1->end-1)->code == NR_CURVETO);
749 g_assert(SP_CURVE_SEGMENT(rev_cal2, rev_cal2->end-1)->code == NR_CURVETO);
751 sp_curve_append(dc->accumulated, dc->cal1, FALSE);
753 add_cap(dc->accumulated, SP_CURVE_SEGMENT(dc->cal1, dc->cal1->end-1)->c(2), SP_CURVE_SEGMENT(dc->cal1, dc->cal1->end-1)->c(3), SP_CURVE_SEGMENT(rev_cal2, 0)->c(3), SP_CURVE_SEGMENT(rev_cal2, 1)->c(1), dc->cap_rounding);
755 sp_curve_append(dc->accumulated, rev_cal2, TRUE);
757 add_cap(dc->accumulated, SP_CURVE_SEGMENT(rev_cal2, rev_cal2->end-1)->c(2), SP_CURVE_SEGMENT(rev_cal2, rev_cal2->end-1)->c(3), SP_CURVE_SEGMENT(dc->cal1, 0)->c(3), SP_CURVE_SEGMENT(dc->cal1, 1)->c(1), dc->cap_rounding);
759 sp_curve_closepath(dc->accumulated);
761 sp_curve_unref(rev_cal2);
763 sp_curve_reset(dc->cal1);
764 sp_curve_reset(dc->cal2);
765 }
766 }
768 static double square(double const x)
769 {
770 return x * x;
771 }
773 static void
774 fit_and_split(SPDynaDrawContext *dc, gboolean release)
775 {
776 double const tolerance_sq = square( NR::expansion(SP_EVENT_CONTEXT(dc)->desktop->w2d()) * TOLERANCE_CALLIGRAPHIC );
778 #ifdef DYNA_DRAW_VERBOSE
779 g_print("[F&S:R=%c]", release?'T':'F');
780 #endif
782 if (!( dc->npoints > 0 && dc->npoints < SAMPLING_SIZE ))
783 return; // just clicked
785 if ( dc->npoints == SAMPLING_SIZE - 1 || release ) {
786 #define BEZIER_SIZE 4
787 #define BEZIER_MAX_BEZIERS 8
788 #define BEZIER_MAX_LENGTH ( BEZIER_SIZE * BEZIER_MAX_BEZIERS )
790 #ifdef DYNA_DRAW_VERBOSE
791 g_print("[F&S:#] dc->npoints:%d, release:%s\n",
792 dc->npoints, release ? "TRUE" : "FALSE");
793 #endif
795 /* Current calligraphic */
796 if ( dc->cal1->end == 0 || dc->cal2->end == 0 ) {
797 /* dc->npoints > 0 */
798 /* g_print("calligraphics(1|2) reset\n"); */
799 sp_curve_reset(dc->cal1);
800 sp_curve_reset(dc->cal2);
802 sp_curve_moveto(dc->cal1, dc->point1[0]);
803 sp_curve_moveto(dc->cal2, dc->point2[0]);
804 }
806 NR::Point b1[BEZIER_MAX_LENGTH];
807 gint const nb1 = sp_bezier_fit_cubic_r(b1, dc->point1, dc->npoints,
808 tolerance_sq, BEZIER_MAX_BEZIERS);
809 g_assert( nb1 * BEZIER_SIZE <= gint(G_N_ELEMENTS(b1)) );
811 NR::Point b2[BEZIER_MAX_LENGTH];
812 gint const nb2 = sp_bezier_fit_cubic_r(b2, dc->point2, dc->npoints,
813 tolerance_sq, BEZIER_MAX_BEZIERS);
814 g_assert( nb2 * BEZIER_SIZE <= gint(G_N_ELEMENTS(b2)) );
816 if ( nb1 != -1 && nb2 != -1 ) {
817 /* Fit and draw and reset state */
818 #ifdef DYNA_DRAW_VERBOSE
819 g_print("nb1:%d nb2:%d\n", nb1, nb2);
820 #endif
821 /* CanvasShape */
822 if (! release) {
823 sp_curve_reset(dc->currentcurve);
824 sp_curve_moveto(dc->currentcurve, b1[0]);
825 for (NR::Point *bp1 = b1; bp1 < b1 + BEZIER_SIZE * nb1; bp1 += BEZIER_SIZE) {
826 sp_curve_curveto(dc->currentcurve, bp1[1],
827 bp1[2], bp1[3]);
828 }
829 sp_curve_lineto(dc->currentcurve,
830 b2[BEZIER_SIZE*(nb2-1) + 3]);
831 for (NR::Point *bp2 = b2 + BEZIER_SIZE * ( nb2 - 1 ); bp2 >= b2; bp2 -= BEZIER_SIZE) {
832 sp_curve_curveto(dc->currentcurve, bp2[2], bp2[1], bp2[0]);
833 }
834 // FIXME: dc->segments is always NULL at this point??
835 if (!dc->segments) { // first segment
836 add_cap(dc->currentcurve, b2[1], b2[0], b1[0], b1[1], dc->cap_rounding);
837 }
838 sp_curve_closepath(dc->currentcurve);
839 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(dc->currentshape), dc->currentcurve);
840 }
842 /* Current calligraphic */
843 for (NR::Point *bp1 = b1; bp1 < b1 + BEZIER_SIZE * nb1; bp1 += BEZIER_SIZE) {
844 sp_curve_curveto(dc->cal1, bp1[1], bp1[2], bp1[3]);
845 }
846 for (NR::Point *bp2 = b2; bp2 < b2 + BEZIER_SIZE * nb2; bp2 += BEZIER_SIZE) {
847 sp_curve_curveto(dc->cal2, bp2[1], bp2[2], bp2[3]);
848 }
849 } else {
850 /* fixme: ??? */
851 #ifdef DYNA_DRAW_VERBOSE
852 g_print("[fit_and_split] failed to fit-cubic.\n");
853 #endif
854 draw_temporary_box(dc);
856 for (gint i = 1; i < dc->npoints; i++) {
857 sp_curve_lineto(dc->cal1, dc->point1[i]);
858 }
859 for (gint i = 1; i < dc->npoints; i++) {
860 sp_curve_lineto(dc->cal2, dc->point2[i]);
861 }
862 }
864 /* Fit and draw and copy last point */
865 #ifdef DYNA_DRAW_VERBOSE
866 g_print("[%d]Yup\n", dc->npoints);
867 #endif
868 if (!release) {
869 g_assert(!sp_curve_empty(dc->currentcurve));
871 SPCanvasItem *cbp = sp_canvas_item_new(sp_desktop_sketch(SP_EVENT_CONTEXT(dc)->desktop),
872 SP_TYPE_CANVAS_BPATH,
873 NULL);
874 SPCurve *curve = sp_curve_copy(dc->currentcurve);
875 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH (cbp), curve);
876 sp_curve_unref(curve);
878 guint32 fillColor = sp_desktop_get_color_tool (SP_ACTIVE_DESKTOP, "tools.calligraphic", true);
879 //guint32 strokeColor = sp_desktop_get_color_tool (SP_ACTIVE_DESKTOP, "tools.calligraphic", false);
880 double opacity = sp_desktop_get_master_opacity_tool (SP_ACTIVE_DESKTOP, "tools.calligraphic");
881 double fillOpacity = sp_desktop_get_opacity_tool (SP_ACTIVE_DESKTOP, "tools.calligraphic", true);
882 //double strokeOpacity = sp_desktop_get_opacity_tool (SP_ACTIVE_DESKTOP, "tools.calligraphic", false);
883 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(cbp), ((fillColor & 0xffffff00) | SP_COLOR_F_TO_U(opacity*fillOpacity)), SP_WIND_RULE_EVENODD);
884 //on second thougtht don't do stroke yet because we don't have stoke-width yet and because stoke appears between segments while drawing
885 //sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(cbp), ((strokeColor & 0xffffff00) | SP_COLOR_F_TO_U(opacity*strokeOpacity)), 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
886 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(cbp), 0x00000000, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
887 /* fixme: Cannot we cascade it to root more clearly? */
888 g_signal_connect(G_OBJECT(cbp), "event", G_CALLBACK(sp_desktop_root_handler), SP_EVENT_CONTEXT(dc)->desktop);
890 dc->segments = g_slist_prepend(dc->segments, cbp);
891 }
893 dc->point1[0] = dc->point1[dc->npoints - 1];
894 dc->point2[0] = dc->point2[dc->npoints - 1];
895 dc->npoints = 1;
896 } else {
897 draw_temporary_box(dc);
898 }
899 }
901 static void
902 draw_temporary_box(SPDynaDrawContext *dc)
903 {
904 sp_curve_reset(dc->currentcurve);
905 sp_curve_moveto(dc->currentcurve, dc->point1[0]);
906 for (gint i = 1; i < dc->npoints; i++) {
907 sp_curve_lineto(dc->currentcurve, dc->point1[i]);
908 }
909 for (gint i = dc->npoints-1; i >= 0; i--) {
910 sp_curve_lineto(dc->currentcurve, dc->point2[i]);
911 }
912 add_cap(dc->currentcurve, dc->point2[1], dc->point2[0], dc->point1[0], dc->point1[1], dc->cap_rounding);
913 sp_curve_closepath(dc->currentcurve);
914 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(dc->currentshape), dc->currentcurve);
915 }
917 /*
918 Local Variables:
919 mode:c++
920 c-file-style:"stroustrup"
921 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
922 indent-tabs-mode:nil
923 fill-column:99
924 End:
925 */
926 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :