Code

added fix from Dale Harvey to expand incomplete JIDs specified in user
[inkscape.git] / src / dyna-draw-context.cpp
index 8b90ce034c3484e916be536adc7c308fa83177d6..02ca80b202c9da06a7c0eb2fb6993df81808114c 100644 (file)
  * Released under GNU GPL, read the file 'COPYING' for more information
  */
 
-/*
- * TODO: Tue Oct  2 22:57:15 2001
- *  - Decide control point behavior when use_calligraphic==1.
- *  - Decide to use NORMALIZED_COORDINATE or not.
- *  - Bug fix.
- */
-
 #define noDYNA_DRAW_VERBOSE
 
 #include "config.h"
@@ -54,7 +47,6 @@
 #include "sp-item.h"
 
 #define DDC_RED_RGBA 0xff0000ff
-#define DDC_GREEN_RGBA 0x000000ff
 
 #define SAMPLE_TIMEOUT 10
 #define TOLERANCE_LINE 1.0
@@ -77,17 +69,14 @@ static gint sp_dyna_draw_context_root_handler(SPEventContext *ec, GdkEvent *even
 
 static void clear_current(SPDynaDrawContext *dc);
 static void set_to_accumulated(SPDynaDrawContext *dc);
-static void concat_current_line(SPDynaDrawContext *dc);
 static void accumulate_calligraphic(SPDynaDrawContext *dc);
 
 static void fit_and_split(SPDynaDrawContext *ddc, gboolean release);
-static void fit_and_split_line(SPDynaDrawContext *ddc, gboolean release);
 static void fit_and_split_calligraphics(SPDynaDrawContext *ddc, gboolean release);
 
 static void sp_dyna_draw_reset(SPDynaDrawContext *ddc, NR::Point p);
 static NR::Point sp_dyna_draw_get_npoint(SPDynaDrawContext const *ddc, NR::Point v);
 static NR::Point sp_dyna_draw_get_vpoint(SPDynaDrawContext const *ddc, NR::Point n);
-static NR::Point sp_dyna_draw_get_curr_vpoint(SPDynaDrawContext const *ddc);
 static void draw_temporary_box(SPDynaDrawContext *dc);
 
 
@@ -136,7 +125,7 @@ sp_dyna_draw_context_init(SPDynaDrawContext *ddc)
     event_context->cursor_shape = cursor_calligraphy_xpm;
     event_context->hot_x = 4;
     event_context->hot_y = 4;
-    
+
     ddc->accumulated = NULL;
     ddc->segments = NULL;
     ddc->currentcurve = NULL;
@@ -156,10 +145,8 @@ sp_dyna_draw_context_init(SPDynaDrawContext *ddc)
 
     /* attributes */
     ddc->use_timeout = FALSE;
-    ddc->use_calligraphic = TRUE;
     ddc->timer_id = 0;
     ddc->dragging = FALSE;
-    ddc->dynahand = FALSE;
 
     ddc->mass = 0.3;
     ddc->drag = DRAG_DEFAULT;
@@ -214,7 +201,6 @@ sp_dyna_draw_context_setup(SPEventContext *ec)
     ddc->cal1 = sp_curve_new_sized(32);
     ddc->cal2 = sp_curve_new_sized(32);
 
-    /* style should be changed when dc->use_calligraphc is touched */
     ddc->currentshape = sp_canvas_item_new(SP_DT_SKETCH(ec->desktop), SP_TYPE_CANVAS_BPATH, NULL);
     sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(ddc->currentshape), DDC_RED_RGBA, SP_WIND_RULE_EVENODD);
     sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(ddc->currentshape), 0x00000000, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
@@ -226,6 +212,7 @@ sp_dyna_draw_context_setup(SPEventContext *ec)
     sp_event_context_read(ec, "angle");
     sp_event_context_read(ec, "width");
     sp_event_context_read(ec, "thinning");
+    sp_event_context_read(ec, "tremor");
     sp_event_context_read(ec, "flatness");
     sp_event_context_read(ec, "usepressure");
     sp_event_context_read(ec, "usetilt");
@@ -255,6 +242,9 @@ sp_dyna_draw_context_set(SPEventContext *ec, gchar const *key, gchar const *val)
     } else if (!strcmp(key, "thinning")) {
         double const dval = ( val ? g_ascii_strtod (val, NULL) : 0.1 );
         ddc->vel_thin = CLAMP(dval, -1.0, 1.0);
+    } else if (!strcmp(key, "tremor")) {
+        double const dval = ( val ? g_ascii_strtod (val, NULL) : 0.0 );
+        ddc->tremor = CLAMP(dval, 0.0, 1.0);
     } else if (!strcmp(key, "flatness")) {
         double const dval = ( val ? g_ascii_strtod (val, NULL) : 1.0 );
         ddc->flatness = CLAMP(dval, 0, 1.0);
@@ -291,14 +281,6 @@ sp_dyna_draw_get_vpoint(SPDynaDrawContext const *dc, NR::Point n)
     return NR::Point(n[NR::X] * max + drect.min()[NR::X], n[NR::Y] * max + drect.min()[NR::Y]);
 }
 
-/* Get current view point */
-static NR::Point sp_dyna_draw_get_curr_vpoint(SPDynaDrawContext const *dc)
-{
-    NR::Rect drect = SP_EVENT_CONTEXT(dc)->desktop->get_display_area();
-    double const max = MAX ( drect.dimensions()[NR::X], drect.dimensions()[NR::Y] );
-    return NR::Point(dc->cur[NR::X] * max + drect.min()[NR::X], dc->cur[NR::Y] * max + drect.min()[NR::Y]);
-}
-
 static void
 sp_dyna_draw_reset(SPDynaDrawContext *dc, NR::Point p)
 {
@@ -412,29 +394,47 @@ sp_dyna_draw_brush(SPDynaDrawContext *dc)
 {
     g_assert( dc->npoints >= 0 && dc->npoints < SAMPLING_SIZE );
 
-    if (dc->use_calligraphic) {
-        /* calligraphics */
-
-        // How much velocity thins strokestyle
-        double vel_thin = flerp (0, 160, dc->vel_thin);
-
-        // Influence of pressure on thickness
-        double pressure_thick = (dc->usepressure ? dc->pressure : 1.0);
+    // How much velocity thins strokestyle
+    double vel_thin = flerp (0, 160, dc->vel_thin);
+
+    // Influence of pressure on thickness
+    double pressure_thick = (dc->usepressure ? dc->pressure : 1.0);
+
+    double width = ( pressure_thick - vel_thin * NR::L2(dc->vel) ) * dc->width;
+
+    double tremble_left = 0, tremble_right = 0;
+    if (dc->tremor > 0) {
+        // obtain two normally distributed random variables, using polar Box-Muller transform
+        double x1, x2, w, y1, y2;
+        do {
+            x1 = 2.0 * g_random_double_range(0,1) - 1.0;
+            x2 = 2.0 * g_random_double_range(0,1) - 1.0;
+            w = x1 * x1 + x2 * x2;
+        } while ( w >= 1.0 );
+        w = sqrt( (-2.0 * log( w ) ) / w );
+        y1 = x1 * w;
+        y2 = x2 * w;
+
+        // deflect both left and right edges randomly and independently, so that:
+        // (1) dc->tremor=1 corresponds to sigma=1, decreasing dc->tremor narrows the bell curve;
+        // (2) deflection depends on width, but is upped for small widths for better visual uniformity across widths;
+        // (3) deflection somewhat depends on speed, to prevent fast strokes looking
+        // comparatively smooth and slow ones excessively jittery
+        tremble_left  = (y1)*dc->tremor * (0.15 + 0.8*width) * (0.35 + 14*NR::L2(dc->vel));
+        tremble_right = (y2)*dc->tremor * (0.15 + 0.8*width) * (0.35 + 14*NR::L2(dc->vel));
+    }
 
-        double width = ( pressure_thick - vel_thin * NR::L2(dc->vel) ) * dc->width;
-        if ( width < 0.02 * dc->width ) {
-            width = 0.02 * dc->width;
-        }
+    if ( width < 0.02 * dc->width ) {
+        width = 0.02 * dc->width;
+    }
 
-        NR::Point del = 0.05 * width * dc->ang;
+    NR::Point del_left = 0.05 * (width + tremble_left) * dc->ang;
+    NR::Point del_right = 0.05 * (width + tremble_right) * dc->ang;
 
-        dc->point1[dc->npoints] = sp_dyna_draw_get_vpoint(dc, dc->cur + del);
-        dc->point2[dc->npoints] = sp_dyna_draw_get_vpoint(dc, dc->cur - del);
+    dc->point1[dc->npoints] = sp_dyna_draw_get_vpoint(dc, dc->cur + del_left);
+    dc->point2[dc->npoints] = sp_dyna_draw_get_vpoint(dc, dc->cur - del_right);
 
-        dc->del = del;
-    } else {
-        dc->point1[dc->npoints] = sp_dyna_draw_get_curr_vpoint(dc);
-    }
+    dc->del = 0.5*(del_left + del_right);
 
     dc->npoints++;
 }
@@ -447,7 +447,6 @@ sp_dyna_draw_timeout_handler(gpointer data)
     SPCanvas *canvas = SP_CANVAS(SP_DT_CANVAS(desktop));
 
     dc->dragging = TRUE;
-    dc->dynahand = TRUE;
 
     int x, y;
     gtk_widget_get_pointer(GTK_WIDGET(canvas), &x, &y);
@@ -490,7 +489,7 @@ sp_dyna_draw_context_root_handler(SPEventContext *event_context,
             if (Inkscape::have_viable_layer(desktop, dc->_message_context) == false) {
                 return TRUE;
             }
-            
+
             NR::Point const button_w(event->button.x,
                                      event->button.y);
             NR::Point const button_dt(desktop->w2d(button_w));
@@ -510,7 +509,7 @@ sp_dyna_draw_context_root_handler(SPEventContext *event_context,
                                   ? ( GDK_KEY_PRESS_MASK |
                                       GDK_BUTTON_RELEASE_MASK |
                                       GDK_BUTTON_PRESS_MASK    )
-                                  : ( GDK_KEY_PRESS_MASK | 
+                                  : ( GDK_KEY_PRESS_MASK |
                                       GDK_BUTTON_RELEASE_MASK |
                                       GDK_POINTER_MOTION_MASK |
                                       GDK_BUTTON_PRESS_MASK    ) ),
@@ -528,7 +527,6 @@ sp_dyna_draw_context_root_handler(SPEventContext *event_context,
     case GDK_MOTION_NOTIFY:
         if ( dc->is_drawing && !dc->use_timeout && ( event->motion.state & GDK_BUTTON1_MASK ) ) {
             dc->dragging = TRUE;
-            dc->dynahand = TRUE;
 
             NR::Point const motion_w(event->motion.x,
                                      event->motion.y);
@@ -562,30 +560,20 @@ sp_dyna_draw_context_root_handler(SPEventContext *event_context,
         if ( dc->dragging && event->button.button == 1 ) {
             dc->dragging = FALSE;
 
-            /* release */
-            if (dc->dynahand) {
-                dc->dynahand = FALSE;
-                /* Remove all temporary line segments */
-                while (dc->segments) {
-                    gtk_object_destroy(GTK_OBJECT(dc->segments->data));
-                    dc->segments = g_slist_remove(dc->segments, dc->segments->data);
-                }
-                /* Create object */
-                fit_and_split(dc, TRUE);
-                if (dc->use_calligraphic) {
-                    accumulate_calligraphic(dc);
-                } else {
-                    concat_current_line(dc);
-                }
-                set_to_accumulated(dc); /* temporal implementation */
-                if (dc->use_calligraphic /* || dc->cinside*/) {
-                    /* reset accumulated curve */
-                    sp_curve_reset(dc->accumulated);
-                    clear_current(dc);
-                    if (dc->repr) {
-                        dc->repr = NULL;
-                    }
-                }
+            /* Remove all temporary line segments */
+            while (dc->segments) {
+                gtk_object_destroy(GTK_OBJECT(dc->segments->data));
+                dc->segments = g_slist_remove(dc->segments, dc->segments->data);
+            }
+            /* Create object */
+            fit_and_split(dc, TRUE);
+            accumulate_calligraphic(dc);
+            set_to_accumulated(dc); /* temporal implementation */
+            /* reset accumulated curve */
+            sp_curve_reset(dc->accumulated);
+            clear_current(dc);
+            if (dc->repr) {
+                dc->repr = NULL;
             }
             ret = TRUE;
         }
@@ -596,7 +584,7 @@ sp_dyna_draw_context_root_handler(SPEventContext *event_context,
         case GDK_KP_Up:
             if (!MOD__CTRL_ONLY) {
                 dc->angle += 5.0;
-                if (dc->angle > 90.0) 
+                if (dc->angle > 90.0)
                     dc->angle = 90.0;
                 sp_ddc_update_toolbox (desktop, "calligraphy-angle", dc->angle);
                 ret = TRUE;
@@ -606,7 +594,7 @@ sp_dyna_draw_context_root_handler(SPEventContext *event_context,
         case GDK_KP_Down:
             if (!MOD__CTRL_ONLY) {
                 dc->angle -= 5.0;
-                if (dc->angle < -90.0) 
+                if (dc->angle < -90.0)
                     dc->angle = -90.0;
                 sp_ddc_update_toolbox (desktop, "calligraphy-angle", dc->angle);
                 ret = TRUE;
@@ -616,7 +604,7 @@ sp_dyna_draw_context_root_handler(SPEventContext *event_context,
         case GDK_KP_Right:
             if (!MOD__CTRL_ONLY) {
                 dc->width += 0.01;
-                if (dc->width > 1.0) 
+                if (dc->width > 1.0)
                     dc->width = 1.0;
                 sp_ddc_update_toolbox (desktop, "altx-calligraphy", dc->width); // the same spinbutton is for alt+x
                 ret = TRUE;
@@ -626,9 +614,9 @@ sp_dyna_draw_context_root_handler(SPEventContext *event_context,
         case GDK_KP_Left:
             if (!MOD__CTRL_ONLY) {
                 dc->width -= 0.01;
-                if (dc->width < 0.01) 
+                if (dc->width < 0.01)
                     dc->width = 0.01;
-                sp_ddc_update_toolbox (desktop, "altx-calligraphy", dc->width); 
+                sp_ddc_update_toolbox (desktop, "altx-calligraphy", dc->width);
                 ret = TRUE;
             }
             break;
@@ -713,30 +701,6 @@ set_to_accumulated(SPDynaDrawContext *dc)
     sp_document_done(SP_DT_DOCUMENT(desktop));
 }
 
-static void
-concat_current_line(SPDynaDrawContext *dc)
-{
-    if (!sp_curve_empty(dc->currentcurve)) {
-        NArtBpath *bpath;
-        if (sp_curve_empty(dc->accumulated)) {
-            bpath = sp_curve_first_bpath(dc->currentcurve);
-            g_assert( bpath->code == NR_MOVETO_OPEN );
-            sp_curve_moveto(dc->accumulated, bpath->x3, bpath->y3);
-        }
-        bpath = sp_curve_last_bpath(dc->currentcurve);
-        if ( bpath->code == NR_CURVETO ) {
-            sp_curve_curveto(dc->accumulated,
-                             bpath->x1, bpath->y1,
-                             bpath->x2, bpath->y2,
-                             bpath->x3, bpath->y3);
-        } else if ( bpath->code == NR_LINETO ) {
-            sp_curve_lineto(dc->accumulated, bpath->x3, bpath->y3);
-        } else {
-            g_assert_not_reached();
-        }
-    }
-}
-
 static void
 accumulate_calligraphic(SPDynaDrawContext *dc)
 {
@@ -758,11 +722,7 @@ static void
 fit_and_split(SPDynaDrawContext *dc,
               gboolean release)
 {
-    if (dc->use_calligraphic) {
-        fit_and_split_calligraphics(dc, release);
-    } else {
-        fit_and_split_line(dc, release);
-    }
+    fit_and_split_calligraphics(dc, release);
 }
 
 static double square(double const x)
@@ -770,51 +730,6 @@ static double square(double const x)
     return x * x;
 }
 
-static void
-fit_and_split_line(SPDynaDrawContext *dc,
-                   gboolean release)
-{
-    double const tolerance_sq = square( NR::expansion(SP_EVENT_CONTEXT(dc)->desktop->w2d()) * TOLERANCE_LINE );
-
-    NR::Point b[4];
-    double const n_segs = sp_bezier_fit_cubic(b, dc->point1, dc->npoints, tolerance_sq);
-    if ( n_segs > 0
-         && dc->npoints < SAMPLING_SIZE )
-    {
-        /* Fit and draw and reset state */
-#ifdef DYNA_DRAW_VERBOSE
-        g_print("%d", dc->npoints);
-#endif
-        sp_curve_reset(dc->currentcurve);
-        sp_curve_moveto(dc->currentcurve, b[0]);
-        sp_curve_curveto(dc->currentcurve, b[1], b[2], b[3]);
-        sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(dc->currentshape), dc->currentcurve);
-    } else {
-        /* Fit and draw and copy last point */
-#ifdef DYNA_DRAW_VERBOSE
-        g_print("[%d]Yup\n", dc->npoints);
-#endif
-        g_assert(!sp_curve_empty(dc->currentcurve));
-        concat_current_line(dc);
-
-        SPCanvasItem *cbp = sp_canvas_item_new(SP_DT_SKETCH(SP_EVENT_CONTEXT(dc)->desktop),
-                                               SP_TYPE_CANVAS_BPATH,
-                                               NULL);
-        SPCurve *curve = sp_curve_copy(dc->currentcurve);
-        sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cbp), curve);
-        sp_curve_unref(curve);
-        /* fixme: We have to parse style color somehow */
-        sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(cbp), DDC_GREEN_RGBA, SP_WIND_RULE_EVENODD);
-        sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(cbp), 0x000000ff, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
-        /* fixme: Cannot we cascade it to root more clearly? */
-        g_signal_connect(G_OBJECT(cbp), "event", G_CALLBACK(sp_desktop_root_handler), SP_EVENT_CONTEXT(dc)->desktop);
-
-        dc->segments = g_slist_prepend(dc->segments, cbp);
-        dc->point1[0] = dc->point1[dc->npoints - 2];
-        dc->npoints = 1;
-    }
-}
-
 static void
 fit_and_split_calligraphics(SPDynaDrawContext *dc, gboolean release)
 {