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 *
11 * The original dynadraw code:
12 * Paul Haeberli <paul@sgi.com>
13 *
14 * Copyright (C) 1998 The Free Software Foundation
15 * Copyright (C) 1999-2005 authors
16 * Copyright (C) 2001-2002 Ximian, Inc.
17 *
18 * Released under GNU GPL, read the file 'COPYING' for more information
19 */
21 #define noDYNA_DRAW_VERBOSE
23 #include "config.h"
25 #include <gtk/gtk.h>
26 #include <gdk/gdkkeysyms.h>
28 #include "svg/svg.h"
29 #include "display/canvas-bpath.h"
30 #include "display/bezier-utils.h"
32 #include "macros.h"
33 #include "document.h"
34 #include "selection.h"
35 #include "desktop.h"
36 #include "desktop-events.h"
37 #include "desktop-handles.h"
38 #include "desktop-affine.h"
39 #include "desktop-style.h"
40 #include "message-context.h"
41 #include "pixmaps/cursor-calligraphy.xpm"
42 #include "dyna-draw-context.h"
43 #include "libnr/n-art-bpath.h"
44 #include "libnr/nr-path.h"
45 #include "xml/repr.h"
46 #include "context-fns.h"
47 #include "sp-item.h"
49 #define DDC_RED_RGBA 0xff0000ff
51 #define SAMPLE_TIMEOUT 10
52 #define TOLERANCE_LINE 1.0
53 #define TOLERANCE_CALLIGRAPHIC 3.0
54 #define DYNA_EPSILON 1.0e-6
56 #define DYNA_MIN_WIDTH 1.0e-6
58 #define DRAG_MIN 0.0
59 #define DRAG_DEFAULT 1.0
60 #define DRAG_MAX 1.0
62 static void sp_dyna_draw_context_class_init(SPDynaDrawContextClass *klass);
63 static void sp_dyna_draw_context_init(SPDynaDrawContext *ddc);
64 static void sp_dyna_draw_context_dispose(GObject *object);
66 static void sp_dyna_draw_context_setup(SPEventContext *ec);
67 static void sp_dyna_draw_context_set(SPEventContext *ec, gchar const *key, gchar const *val);
68 static gint sp_dyna_draw_context_root_handler(SPEventContext *ec, GdkEvent *event);
70 static void clear_current(SPDynaDrawContext *dc);
71 static void set_to_accumulated(SPDynaDrawContext *dc);
72 static void accumulate_calligraphic(SPDynaDrawContext *dc);
74 static void fit_and_split(SPDynaDrawContext *ddc, gboolean release);
75 static void fit_and_split_calligraphics(SPDynaDrawContext *ddc, gboolean release);
77 static void sp_dyna_draw_reset(SPDynaDrawContext *ddc, NR::Point p);
78 static NR::Point sp_dyna_draw_get_npoint(SPDynaDrawContext const *ddc, NR::Point v);
79 static NR::Point sp_dyna_draw_get_vpoint(SPDynaDrawContext const *ddc, NR::Point n);
80 static void draw_temporary_box(SPDynaDrawContext *dc);
83 static SPEventContextClass *parent_class;
85 GtkType
86 sp_dyna_draw_context_get_type(void)
87 {
88 static GType type = 0;
89 if (!type) {
90 GTypeInfo info = {
91 sizeof(SPDynaDrawContextClass),
92 NULL, NULL,
93 (GClassInitFunc) sp_dyna_draw_context_class_init,
94 NULL, NULL,
95 sizeof(SPDynaDrawContext),
96 4,
97 (GInstanceInitFunc) sp_dyna_draw_context_init,
98 NULL, /* value_table */
99 };
100 type = g_type_register_static(SP_TYPE_EVENT_CONTEXT, "SPDynaDrawContext", &info, (GTypeFlags)0);
101 }
102 return type;
103 }
105 static void
106 sp_dyna_draw_context_class_init(SPDynaDrawContextClass *klass)
107 {
108 GObjectClass *object_class = (GObjectClass *) klass;
109 SPEventContextClass *event_context_class = (SPEventContextClass *) klass;
111 parent_class = (SPEventContextClass*)g_type_class_peek_parent(klass);
113 object_class->dispose = sp_dyna_draw_context_dispose;
115 event_context_class->setup = sp_dyna_draw_context_setup;
116 event_context_class->set = sp_dyna_draw_context_set;
117 event_context_class->root_handler = sp_dyna_draw_context_root_handler;
118 }
120 static void
121 sp_dyna_draw_context_init(SPDynaDrawContext *ddc)
122 {
123 SPEventContext *event_context = SP_EVENT_CONTEXT(ddc);
125 event_context->cursor_shape = cursor_calligraphy_xpm;
126 event_context->hot_x = 4;
127 event_context->hot_y = 4;
129 ddc->accumulated = NULL;
130 ddc->segments = NULL;
131 ddc->currentcurve = NULL;
132 ddc->currentshape = NULL;
133 ddc->npoints = 0;
134 ddc->cal1 = NULL;
135 ddc->cal2 = NULL;
136 ddc->repr = NULL;
138 /* DynaDraw values */
139 ddc->cur = NR::Point(0,0);
140 ddc->last = NR::Point(0,0);
141 ddc->vel = NR::Point(0,0);
142 ddc->acc = NR::Point(0,0);
143 ddc->ang = NR::Point(0,0);
144 ddc->del = NR::Point(0,0);
146 /* attributes */
147 ddc->dragging = FALSE;
149 ddc->mass = 0.3;
150 ddc->drag = DRAG_DEFAULT;
151 ddc->angle = 30.0;
152 ddc->width = 0.2;
154 ddc->vel_thin = 0.1;
155 ddc->flatness = 0.9;
157 ddc->abs_width = false;
158 }
160 static void
161 sp_dyna_draw_context_dispose(GObject *object)
162 {
163 SPDynaDrawContext *ddc = SP_DYNA_DRAW_CONTEXT(object);
165 if (ddc->accumulated) {
166 ddc->accumulated = sp_curve_unref(ddc->accumulated);
167 }
169 while (ddc->segments) {
170 gtk_object_destroy(GTK_OBJECT(ddc->segments->data));
171 ddc->segments = g_slist_remove(ddc->segments, ddc->segments->data);
172 }
174 if (ddc->currentcurve) ddc->currentcurve = sp_curve_unref(ddc->currentcurve);
175 if (ddc->cal1) ddc->cal1 = sp_curve_unref(ddc->cal1);
176 if (ddc->cal2) ddc->cal2 = sp_curve_unref(ddc->cal2);
178 if (ddc->currentshape) {
179 gtk_object_destroy(GTK_OBJECT(ddc->currentshape));
180 ddc->currentshape = NULL;
181 }
183 if (ddc->_message_context) {
184 delete ddc->_message_context;
185 }
187 G_OBJECT_CLASS(parent_class)->dispose(object);
188 }
190 static void
191 sp_dyna_draw_context_setup(SPEventContext *ec)
192 {
193 SPDynaDrawContext *ddc = SP_DYNA_DRAW_CONTEXT(ec);
195 if (((SPEventContextClass *) parent_class)->setup)
196 ((SPEventContextClass *) parent_class)->setup(ec);
198 ddc->accumulated = sp_curve_new_sized(32);
199 ddc->currentcurve = sp_curve_new_sized(4);
201 ddc->cal1 = sp_curve_new_sized(32);
202 ddc->cal2 = sp_curve_new_sized(32);
204 ddc->currentshape = sp_canvas_item_new(sp_desktop_sketch(ec->desktop), SP_TYPE_CANVAS_BPATH, NULL);
205 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(ddc->currentshape), DDC_RED_RGBA, SP_WIND_RULE_EVENODD);
206 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(ddc->currentshape), 0x00000000, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
207 /* fixme: Cannot we cascade it to root more clearly? */
208 g_signal_connect(G_OBJECT(ddc->currentshape), "event", G_CALLBACK(sp_desktop_root_handler), ec->desktop);
210 sp_event_context_read(ec, "mass");
211 sp_event_context_read(ec, "drag");
212 sp_event_context_read(ec, "angle");
213 sp_event_context_read(ec, "width");
214 sp_event_context_read(ec, "thinning");
215 sp_event_context_read(ec, "tremor");
216 sp_event_context_read(ec, "flatness");
217 sp_event_context_read(ec, "usepressure");
218 sp_event_context_read(ec, "usetilt");
219 sp_event_context_read(ec, "abs_width");
221 ddc->is_drawing = false;
223 ddc->_message_context = new Inkscape::MessageContext((ec->desktop)->messageStack());
224 }
226 static void
227 sp_dyna_draw_context_set(SPEventContext *ec, gchar const *key, gchar const *val)
228 {
229 SPDynaDrawContext *ddc = SP_DYNA_DRAW_CONTEXT(ec);
231 if (!strcmp(key, "mass")) {
232 double const dval = ( val ? g_ascii_strtod (val, NULL) : 0.2 );
233 ddc->mass = CLAMP(dval, -1000.0, 1000.0);
234 } else if (!strcmp(key, "drag")) {
235 double const dval = ( val ? g_ascii_strtod (val, NULL) : DRAG_DEFAULT );
236 ddc->drag = CLAMP(dval, DRAG_MIN, DRAG_MAX);
237 } else if (!strcmp(key, "angle")) {
238 double const dval = ( val ? g_ascii_strtod (val, NULL) : 0.0);
239 ddc->angle = CLAMP (dval, -90, 90);
240 } else if (!strcmp(key, "width")) {
241 double const dval = ( val ? g_ascii_strtod (val, NULL) : 0.1 );
242 ddc->width = CLAMP(dval, -1000.0, 1000.0);
243 } else if (!strcmp(key, "thinning")) {
244 double const dval = ( val ? g_ascii_strtod (val, NULL) : 0.1 );
245 ddc->vel_thin = CLAMP(dval, -1.0, 1.0);
246 } else if (!strcmp(key, "tremor")) {
247 double const dval = ( val ? g_ascii_strtod (val, NULL) : 0.0 );
248 ddc->tremor = CLAMP(dval, 0.0, 1.0);
249 } else if (!strcmp(key, "flatness")) {
250 double const dval = ( val ? g_ascii_strtod (val, NULL) : 1.0 );
251 ddc->flatness = CLAMP(dval, 0, 1.0);
252 } else if (!strcmp(key, "usepressure")) {
253 ddc->usepressure = (val && strcmp(val, "0"));
254 } else if (!strcmp(key, "usetilt")) {
255 ddc->usetilt = (val && strcmp(val, "0"));
256 } else if (!strcmp(key, "abs_width")) {
257 ddc->abs_width = (val && strcmp(val, "0"));
258 }
260 //g_print("DDC: %g %g %g %g\n", ddc->mass, ddc->drag, ddc->angle, ddc->width);
261 }
263 static double
264 flerp(double f0, double f1, double p)
265 {
266 return f0 + ( f1 - f0 ) * p;
267 }
269 /* Get normalized point */
270 static NR::Point
271 sp_dyna_draw_get_npoint(SPDynaDrawContext const *dc, NR::Point v)
272 {
273 NR::Rect drect = SP_EVENT_CONTEXT(dc)->desktop->get_display_area();
274 double const max = MAX ( drect.dimensions()[NR::X], drect.dimensions()[NR::Y] );
275 return NR::Point(( v[NR::X] - drect.min()[NR::X] ) / max, ( v[NR::Y] - drect.min()[NR::Y] ) / max);
276 }
278 /* Get view point */
279 static NR::Point
280 sp_dyna_draw_get_vpoint(SPDynaDrawContext const *dc, NR::Point n)
281 {
282 NR::Rect drect = SP_EVENT_CONTEXT(dc)->desktop->get_display_area();
283 double const max = MAX ( drect.dimensions()[NR::X], drect.dimensions()[NR::Y] );
284 return NR::Point(n[NR::X] * max + drect.min()[NR::X], n[NR::Y] * max + drect.min()[NR::Y]);
285 }
287 static void
288 sp_dyna_draw_reset(SPDynaDrawContext *dc, NR::Point p)
289 {
290 dc->last = dc->cur = sp_dyna_draw_get_npoint(dc, p);
291 dc->vel = NR::Point(0,0);
292 dc->acc = NR::Point(0,0);
293 dc->ang = NR::Point(0,0);
294 dc->del = NR::Point(0,0);
295 }
297 static void
298 sp_dyna_draw_extinput(SPDynaDrawContext *dc, GdkEvent *event)
299 {
300 if (gdk_event_get_axis (event, GDK_AXIS_PRESSURE, &dc->pressure))
301 dc->pressure = CLAMP (dc->pressure, DDC_MIN_PRESSURE, DDC_MAX_PRESSURE);
302 else
303 dc->pressure = DDC_DEFAULT_PRESSURE;
305 if (gdk_event_get_axis (event, GDK_AXIS_XTILT, &dc->xtilt))
306 dc->xtilt = CLAMP (dc->xtilt, DDC_MIN_TILT, DDC_MAX_TILT);
307 else
308 dc->xtilt = DDC_DEFAULT_TILT;
310 if (gdk_event_get_axis (event, GDK_AXIS_YTILT, &dc->ytilt))
311 dc->ytilt = CLAMP (dc->ytilt, DDC_MIN_TILT, DDC_MAX_TILT);
312 else
313 dc->ytilt = DDC_DEFAULT_TILT;
314 }
317 static gboolean
318 sp_dyna_draw_apply(SPDynaDrawContext *dc, NR::Point p)
319 {
320 NR::Point n = sp_dyna_draw_get_npoint(dc, p);
322 /* Calculate mass and drag */
323 double const mass = flerp(1.0, 160.0, dc->mass);
324 double const drag = flerp(0.0, 0.5, dc->drag * dc->drag);
326 /* Calculate force and acceleration */
327 NR::Point force = n - dc->cur;
328 if ( NR::L2(force) < DYNA_EPSILON ) {
329 return FALSE;
330 }
332 dc->acc = force / mass;
334 /* Calculate new velocity */
335 dc->vel += dc->acc;
337 /* Calculate angle of drawing tool */
339 double a1;
340 if (dc->usetilt) {
341 // 1a. calculate nib angle from input device tilt:
342 gdouble length = std::sqrt(dc->xtilt*dc->xtilt + dc->ytilt*dc->ytilt);;
344 if (length > 0) {
345 NR::Point ang1 = NR::Point(dc->ytilt/length, dc->xtilt/length);
346 a1 = atan2(ang1);
347 }
348 else
349 a1 = 0.0;
350 }
351 else {
352 // 1b. fixed dc->angle (absolutely flat nib):
353 double const radians = ( (dc->angle - 90) / 180.0 ) * M_PI;
354 NR::Point ang1 = NR::Point(-sin(radians), cos(radians));
355 a1 = atan2(ang1);
356 }
358 // 2. perpendicular to dc->vel (absolutely non-flat nib):
359 gdouble const mag_vel = NR::L2(dc->vel);
360 if ( mag_vel < DYNA_EPSILON ) {
361 return FALSE;
362 }
363 NR::Point ang2 = NR::rot90(dc->vel) / mag_vel;
365 // 3. Average them using flatness parameter:
366 // calculate angles
367 double a2 = atan2(ang2);
368 // flip a2 to force it to be in the same half-circle as a1
369 bool flipped = false;
370 if (fabs (a2-a1) > 0.5*M_PI) {
371 a2 += M_PI;
372 flipped = true;
373 }
374 // normalize a2
375 if (a2 > M_PI)
376 a2 -= 2*M_PI;
377 if (a2 < -M_PI)
378 a2 += 2*M_PI;
379 // find the flatness-weighted bisector angle, unflip if a2 was flipped
380 // FIXME: when dc->vel is oscillating around the fixed angle, the new_ang flips back and forth. How to avoid this?
381 double new_ang = a1 + (1 - dc->flatness) * (a2 - a1) - (flipped? M_PI : 0);
382 // convert to point
383 dc->ang = NR::Point (cos (new_ang), sin (new_ang));
385 /* Apply drag */
386 dc->vel *= 1.0 - drag;
388 /* Update position */
389 dc->last = dc->cur;
390 dc->cur += dc->vel;
392 return TRUE;
393 }
395 static void
396 sp_dyna_draw_brush(SPDynaDrawContext *dc)
397 {
398 g_assert( dc->npoints >= 0 && dc->npoints < SAMPLING_SIZE );
400 // How much velocity thins strokestyle
401 double vel_thin = flerp (0, 160, dc->vel_thin);
403 // Influence of pressure on thickness
404 double pressure_thick = (dc->usepressure ? dc->pressure : 1.0);
406 double width = ( pressure_thick - vel_thin * NR::L2(dc->vel) ) * dc->width;
408 double tremble_left = 0, tremble_right = 0;
409 if (dc->tremor > 0) {
410 // obtain two normally distributed random variables, using polar Box-Muller transform
411 double x1, x2, w, y1, y2;
412 do {
413 x1 = 2.0 * g_random_double_range(0,1) - 1.0;
414 x2 = 2.0 * g_random_double_range(0,1) - 1.0;
415 w = x1 * x1 + x2 * x2;
416 } while ( w >= 1.0 );
417 w = sqrt( (-2.0 * log( w ) ) / w );
418 y1 = x1 * w;
419 y2 = x2 * w;
421 // deflect both left and right edges randomly and independently, so that:
422 // (1) dc->tremor=1 corresponds to sigma=1, decreasing dc->tremor narrows the bell curve;
423 // (2) deflection depends on width, but is upped for small widths for better visual uniformity across widths;
424 // (3) deflection somewhat depends on speed, to prevent fast strokes looking
425 // comparatively smooth and slow ones excessively jittery
426 tremble_left = (y1)*dc->tremor * (0.15 + 0.8*width) * (0.35 + 14*NR::L2(dc->vel));
427 tremble_right = (y2)*dc->tremor * (0.15 + 0.8*width) * (0.35 + 14*NR::L2(dc->vel));
428 }
430 if ( width < 0.02 * dc->width ) {
431 width = 0.02 * dc->width;
432 }
434 double dezoomify_factor = 0.05 * 1000;
435 if (!dc->abs_width) {
436 dezoomify_factor /= SP_EVENT_CONTEXT(dc)->desktop->current_zoom();
437 }
439 NR::Point del_left = dezoomify_factor * (width + tremble_left) * dc->ang;
440 NR::Point del_right = dezoomify_factor * (width + tremble_right) * dc->ang;
442 NR::Point abs_middle = sp_dyna_draw_get_vpoint(dc, dc->cur);
444 dc->point1[dc->npoints] = abs_middle + del_left;
445 dc->point2[dc->npoints] = abs_middle - del_right;
447 dc->del = 0.5*(del_left + del_right);
449 dc->npoints++;
450 }
452 void
453 sp_ddc_update_toolbox (SPDesktop *desktop, const gchar *id, double value)
454 {
455 desktop->setToolboxAdjustmentValue (id, value);
456 }
458 gint
459 sp_dyna_draw_context_root_handler(SPEventContext *event_context,
460 GdkEvent *event)
461 {
462 SPDynaDrawContext *dc = SP_DYNA_DRAW_CONTEXT(event_context);
463 SPDesktop *desktop = event_context->desktop;
465 gint ret = FALSE;
467 switch (event->type) {
468 case GDK_BUTTON_PRESS:
469 if ( event->button.button == 1 ) {
471 SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(dc);
473 if (Inkscape::have_viable_layer(desktop, dc->_message_context) == false) {
474 return TRUE;
475 }
477 NR::Point const button_w(event->button.x,
478 event->button.y);
479 NR::Point const button_dt(desktop->w2d(button_w));
480 sp_dyna_draw_reset(dc, button_dt);
481 sp_dyna_draw_extinput(dc, event);
482 sp_dyna_draw_apply(dc, button_dt);
483 sp_curve_reset(dc->accumulated);
484 if (dc->repr) {
485 dc->repr = NULL;
486 }
488 /* initialize first point */
489 dc->npoints = 0;
491 sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
492 ( GDK_KEY_PRESS_MASK |
493 GDK_BUTTON_RELEASE_MASK |
494 GDK_POINTER_MOTION_MASK |
495 GDK_BUTTON_PRESS_MASK ),
496 NULL,
497 event->button.time);
499 ret = TRUE;
501 dc->is_drawing = true;
502 }
503 break;
504 case GDK_MOTION_NOTIFY:
505 if ( dc->is_drawing && ( event->motion.state & GDK_BUTTON1_MASK ) ) {
506 dc->dragging = TRUE;
508 NR::Point const motion_w(event->motion.x,
509 event->motion.y);
510 NR::Point const motion_dt(desktop->w2d(motion_w));
512 sp_dyna_draw_extinput(dc, event);
513 if (!sp_dyna_draw_apply(dc, motion_dt)) {
514 ret = TRUE;
515 break;
516 }
518 if ( dc->cur != dc->last ) {
519 sp_dyna_draw_brush(dc);
520 g_assert( dc->npoints > 0 );
521 fit_and_split(dc, FALSE);
522 }
523 ret = TRUE;
524 }
525 break;
527 case GDK_BUTTON_RELEASE:
528 sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), event->button.time);
529 dc->is_drawing = false;
530 if ( dc->dragging && event->button.button == 1 ) {
531 dc->dragging = FALSE;
533 /* Remove all temporary line segments */
534 while (dc->segments) {
535 gtk_object_destroy(GTK_OBJECT(dc->segments->data));
536 dc->segments = g_slist_remove(dc->segments, dc->segments->data);
537 }
538 /* Create object */
539 fit_and_split(dc, TRUE);
540 accumulate_calligraphic(dc);
541 set_to_accumulated(dc); /* temporal implementation */
542 /* reset accumulated curve */
543 sp_curve_reset(dc->accumulated);
544 clear_current(dc);
545 if (dc->repr) {
546 dc->repr = NULL;
547 }
548 ret = TRUE;
549 }
550 break;
551 case GDK_KEY_PRESS:
552 switch (get_group0_keyval (&event->key)) {
553 case GDK_Up:
554 case GDK_KP_Up:
555 if (!MOD__CTRL_ONLY) {
556 dc->angle += 5.0;
557 if (dc->angle > 90.0)
558 dc->angle = 90.0;
559 sp_ddc_update_toolbox (desktop, "calligraphy-angle", dc->angle);
560 ret = TRUE;
561 }
562 break;
563 case GDK_Down:
564 case GDK_KP_Down:
565 if (!MOD__CTRL_ONLY) {
566 dc->angle -= 5.0;
567 if (dc->angle < -90.0)
568 dc->angle = -90.0;
569 sp_ddc_update_toolbox (desktop, "calligraphy-angle", dc->angle);
570 ret = TRUE;
571 }
572 break;
573 case GDK_Right:
574 case GDK_KP_Right:
575 if (!MOD__CTRL_ONLY) {
576 dc->width += 0.01;
577 if (dc->width > 1.0)
578 dc->width = 1.0;
579 sp_ddc_update_toolbox (desktop, "altx-calligraphy", dc->width * 100); // the same spinbutton is for alt+x
580 ret = TRUE;
581 }
582 break;
583 case GDK_Left:
584 case GDK_KP_Left:
585 if (!MOD__CTRL_ONLY) {
586 dc->width -= 0.01;
587 if (dc->width < 0.01)
588 dc->width = 0.01;
589 sp_ddc_update_toolbox (desktop, "altx-calligraphy", dc->width * 100);
590 ret = TRUE;
591 }
592 break;
593 case GDK_x:
594 case GDK_X:
595 if (MOD__ALT_ONLY) {
596 desktop->setToolboxFocusTo ("altx-calligraphy");
597 ret = TRUE;
598 }
599 break;
600 case GDK_Escape:
601 sp_desktop_selection(desktop)->clear();
602 break;
604 default:
605 break;
606 }
607 default:
608 break;
609 }
611 if (!ret) {
612 if (((SPEventContextClass *) parent_class)->root_handler) {
613 ret = ((SPEventContextClass *) parent_class)->root_handler(event_context, event);
614 }
615 }
617 return ret;
618 }
621 static void
622 clear_current(SPDynaDrawContext *dc)
623 {
624 /* reset bpath */
625 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(dc->currentshape), NULL);
626 /* reset curve */
627 sp_curve_reset(dc->currentcurve);
628 sp_curve_reset(dc->cal1);
629 sp_curve_reset(dc->cal2);
630 /* reset points */
631 dc->npoints = 0;
632 }
634 static void
635 set_to_accumulated(SPDynaDrawContext *dc)
636 {
637 SPDesktop *desktop = SP_EVENT_CONTEXT(dc)->desktop;
639 if (!sp_curve_empty(dc->accumulated)) {
640 NArtBpath *abp;
641 gchar *str;
643 if (!dc->repr) {
644 /* Create object */
645 Inkscape::XML::Node *repr = sp_repr_new("svg:path");
647 /* Set style */
648 sp_desktop_apply_style_tool (desktop, repr, "tools.calligraphic", false);
650 dc->repr = repr;
652 SPItem *item=SP_ITEM(desktop->currentLayer()->appendChildRepr(dc->repr));
653 Inkscape::GC::release(dc->repr);
654 item->transform = SP_ITEM(desktop->currentRoot())->getRelativeTransform(desktop->currentLayer());
655 item->updateRepr();
656 sp_desktop_selection(desktop)->set(dc->repr);
657 }
658 abp = nr_artpath_affine(sp_curve_first_bpath(dc->accumulated), sp_desktop_dt2root_affine(desktop));
659 str = sp_svg_write_path(abp);
660 g_assert( str != NULL );
661 nr_free(abp);
662 dc->repr->setAttribute("d", str);
663 g_free(str);
664 } else {
665 if (dc->repr) {
666 sp_repr_unparent(dc->repr);
667 }
668 dc->repr = NULL;
669 }
671 sp_document_done(sp_desktop_document(desktop));
672 }
674 static void
675 accumulate_calligraphic(SPDynaDrawContext *dc)
676 {
677 if ( !sp_curve_empty(dc->cal1) && !sp_curve_empty(dc->cal2) ) {
678 sp_curve_reset(dc->accumulated); /* Is this required ?? */
679 SPCurve *rev_cal2 = sp_curve_reverse(dc->cal2);
680 sp_curve_append(dc->accumulated, dc->cal1, FALSE);
681 sp_curve_append(dc->accumulated, rev_cal2, TRUE);
682 sp_curve_closepath(dc->accumulated);
684 sp_curve_unref(rev_cal2);
686 sp_curve_reset(dc->cal1);
687 sp_curve_reset(dc->cal2);
688 }
689 }
691 static void
692 fit_and_split(SPDynaDrawContext *dc,
693 gboolean release)
694 {
695 fit_and_split_calligraphics(dc, release);
696 }
698 static double square(double const x)
699 {
700 return x * x;
701 }
703 static void
704 fit_and_split_calligraphics(SPDynaDrawContext *dc, gboolean release)
705 {
706 double const tolerance_sq = square( NR::expansion(SP_EVENT_CONTEXT(dc)->desktop->w2d()) * TOLERANCE_CALLIGRAPHIC );
708 #ifdef DYNA_DRAW_VERBOSE
709 g_print("[F&S:R=%c]", release?'T':'F');
710 #endif
712 if (!( dc->npoints > 0 && dc->npoints < SAMPLING_SIZE ))
713 return; // just clicked
715 if ( dc->npoints == SAMPLING_SIZE - 1 || release ) {
716 #define BEZIER_SIZE 4
717 #define BEZIER_MAX_BEZIERS 8
718 #define BEZIER_MAX_LENGTH ( BEZIER_SIZE * BEZIER_MAX_BEZIERS )
720 #ifdef DYNA_DRAW_VERBOSE
721 g_print("[F&S:#] dc->npoints:%d, release:%s\n",
722 dc->npoints, release ? "TRUE" : "FALSE");
723 #endif
725 /* Current calligraphic */
726 if ( dc->cal1->end == 0 || dc->cal2->end == 0 ) {
727 /* dc->npoints > 0 */
728 /* g_print("calligraphics(1|2) reset\n"); */
729 sp_curve_reset(dc->cal1);
730 sp_curve_reset(dc->cal2);
732 sp_curve_moveto(dc->cal1, dc->point1[0]);
733 sp_curve_moveto(dc->cal2, dc->point2[0]);
734 }
736 NR::Point b1[BEZIER_MAX_LENGTH];
737 gint const nb1 = sp_bezier_fit_cubic_r(b1, dc->point1, dc->npoints,
738 tolerance_sq, BEZIER_MAX_BEZIERS);
739 g_assert( nb1 * BEZIER_SIZE <= gint(G_N_ELEMENTS(b1)) );
741 NR::Point b2[BEZIER_MAX_LENGTH];
742 gint const nb2 = sp_bezier_fit_cubic_r(b2, dc->point2, dc->npoints,
743 tolerance_sq, BEZIER_MAX_BEZIERS);
744 g_assert( nb2 * BEZIER_SIZE <= gint(G_N_ELEMENTS(b2)) );
746 if ( nb1 != -1 && nb2 != -1 ) {
747 /* Fit and draw and reset state */
748 #ifdef DYNA_DRAW_VERBOSE
749 g_print("nb1:%d nb2:%d\n", nb1, nb2);
750 #endif
751 /* CanvasShape */
752 if (! release) {
753 sp_curve_reset(dc->currentcurve);
754 sp_curve_moveto(dc->currentcurve, b1[0]);
755 for (NR::Point *bp1 = b1; bp1 < b1 + BEZIER_SIZE * nb1; bp1 += BEZIER_SIZE) {
756 sp_curve_curveto(dc->currentcurve, bp1[1],
757 bp1[2], bp1[3]);
758 }
759 sp_curve_lineto(dc->currentcurve,
760 b2[BEZIER_SIZE*(nb2-1) + 3]);
761 for (NR::Point *bp2 = b2 + BEZIER_SIZE * ( nb2 - 1 ); bp2 >= b2; bp2 -= BEZIER_SIZE) {
762 sp_curve_curveto(dc->currentcurve, bp2[2], bp2[1], bp2[0]);
763 }
764 sp_curve_closepath(dc->currentcurve);
765 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(dc->currentshape), dc->currentcurve);
766 }
768 /* Current calligraphic */
769 for (NR::Point *bp1 = b1; bp1 < b1 + BEZIER_SIZE * nb1; bp1 += BEZIER_SIZE) {
770 sp_curve_curveto(dc->cal1, bp1[1], bp1[2], bp1[3]);
771 }
772 for (NR::Point *bp2 = b2; bp2 < b2 + BEZIER_SIZE * nb2; bp2 += BEZIER_SIZE) {
773 sp_curve_curveto(dc->cal2, bp2[1], bp2[2], bp2[3]);
774 }
775 } else {
776 /* fixme: ??? */
777 #ifdef DYNA_DRAW_VERBOSE
778 g_print("[fit_and_split_calligraphics] failed to fit-cubic.\n");
779 #endif
780 draw_temporary_box(dc);
782 for (gint i = 1; i < dc->npoints; i++) {
783 sp_curve_lineto(dc->cal1, dc->point1[i]);
784 }
785 for (gint i = 1; i < dc->npoints; i++) {
786 sp_curve_lineto(dc->cal2, dc->point2[i]);
787 }
788 }
790 /* Fit and draw and copy last point */
791 #ifdef DYNA_DRAW_VERBOSE
792 g_print("[%d]Yup\n", dc->npoints);
793 #endif
794 if (!release) {
795 g_assert(!sp_curve_empty(dc->currentcurve));
797 SPCanvasItem *cbp = sp_canvas_item_new(sp_desktop_sketch(SP_EVENT_CONTEXT(dc)->desktop),
798 SP_TYPE_CANVAS_BPATH,
799 NULL);
800 SPCurve *curve = sp_curve_copy(dc->currentcurve);
801 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH (cbp), curve);
802 sp_curve_unref(curve);
803 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(cbp), 0x000000ff, SP_WIND_RULE_EVENODD);
804 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(cbp), 0x00000000, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
805 /* fixme: Cannot we cascade it to root more clearly? */
806 g_signal_connect(G_OBJECT(cbp), "event", G_CALLBACK(sp_desktop_root_handler), SP_EVENT_CONTEXT(dc)->desktop);
808 dc->segments = g_slist_prepend(dc->segments, cbp);
809 }
811 dc->point1[0] = dc->point1[dc->npoints - 1];
812 dc->point2[0] = dc->point2[dc->npoints - 1];
813 dc->npoints = 1;
814 } else {
815 draw_temporary_box(dc);
816 }
817 }
819 static void
820 draw_temporary_box(SPDynaDrawContext *dc)
821 {
822 sp_curve_reset(dc->currentcurve);
823 sp_curve_moveto(dc->currentcurve, dc->point1[0]);
824 for (gint i = 1; i < dc->npoints; i++) {
825 sp_curve_lineto(dc->currentcurve, dc->point1[i]);
826 }
827 for (gint i = dc->npoints-1; i >= 0; i--) {
828 sp_curve_lineto(dc->currentcurve, dc->point2[i]);
829 }
830 sp_curve_closepath(dc->currentcurve);
831 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(dc->currentshape), dc->currentcurve);
832 }
834 /*
835 Local Variables:
836 mode:c++
837 c-file-style:"stroustrup"
838 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
839 indent-tabs-mode:nil
840 fill-column:99
841 End:
842 */
843 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :