Code

make spcurve::first_point and last_point boost::optional
[inkscape.git] / src / sp-shape.cpp
index e38dba48f140a4db506162fe47267d3ef32061b2..92c1e51288d00141959d7bd05d0868e62ee23ab1 100644 (file)
@@ -16,7 +16,6 @@
 # include "config.h"
 #endif
 
-#include <libnr/n-art-bpath.h>
 #include <libnr/nr-matrix-fns.h>
 #include <libnr/nr-matrix-ops.h>
 #include <libnr/nr-matrix-translate-ops.h>
@@ -25,6 +24,7 @@
 #include <2geom/transforms.h>
 #include <2geom/pathvector.h>
 #include "helper/geom.h"
+#include "helper/geom-nodetype.h"
 
 #include <sigc++/functors/ptr_fun.h>
 #include <sigc++/adaptors/bind.h>
@@ -272,7 +272,7 @@ sp_shape_update (SPObject *object, SPCtx *ctx, unsigned int flags)
        if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_PARENT_MODIFIED_FLAG)) {
                /* This is suboptimal, because changing parent style schedules recalculation */
                /* But on the other hand - how can we know that parent does not tie style and transform */
-                NR::Maybe<NR::Rect> paintbox = SP_ITEM(object)->getBounds(NR::identity());
+                boost::optional<NR::Rect> paintbox = SP_ITEM(object)->getBounds(NR::identity());
                for (SPItemView *v = SP_ITEM (shape)->display; v != NULL; v = v->next) {
                     NRArenaShape * const s = NR_ARENA_SHAPE(v->arenaitem);
                     if (flags & SP_OBJECT_MODIFIED_FLAG) {
@@ -362,23 +362,37 @@ Geom::Matrix
 sp_shape_marker_get_transform_at_start(Geom::Curve const & c)
 {
     Geom::Point p = c.pointAt(0);
-    Geom::Point tang = c.unitTangentAt(0);
-
-    double const angle = Geom::atan2(tang);
+    Geom::Matrix ret = Geom::Translate(p);
+
+    if ( !c.isDegenerate() ) {
+        Geom::Point tang = c.unitTangentAt(0);
+        double const angle = Geom::atan2(tang);
+        ret = Geom::Rotate(angle) * Geom::Translate(p);
+    } else {
+        /* FIXME: the svg spec says to search for a better alternative than zero angle directionality:
+         * http://www.w3.org/TR/SVG11/implnote.html#PathElementImplementationNotes */
+    }
 
-    return Geom::Rotate(angle) * Geom::Translate(p);
+    return ret;
 }
 Geom::Matrix
 sp_shape_marker_get_transform_at_end(Geom::Curve const & c)
 {
     Geom::Point p = c.pointAt(1);
-    Geom::Curve * c_reverse = c.reverse();
-    Geom::Point tang = - c_reverse->unitTangentAt(0);
-    delete c_reverse;
-
-    double const angle = Geom::atan2(tang);
+    Geom::Matrix ret = Geom::Translate(p);
+
+    if ( !c.isDegenerate() ) {
+        Geom::Curve * c_reverse = c.reverse();
+        Geom::Point tang = - c_reverse->unitTangentAt(0);
+        delete c_reverse;
+        double const angle = Geom::atan2(tang);
+        ret = Geom::Rotate(angle) * Geom::Translate(p);
+    } else {
+        /* FIXME: the svg spec says to search for a better alternative than zero angle directionality:
+         * http://www.w3.org/TR/SVG11/implnote.html#PathElementImplementationNotes */
+    }
 
-    return Geom::Rotate(angle) * Geom::Translate(p);
+    return ret;
 }
 
 /**
@@ -410,7 +424,7 @@ sp_shape_update_marker_view (SPShape *shape, NRArenaItem *ai)
              start_pos++;
         }
 
-        if ( shape->marker[SP_MARKER_LOC_MID] ) {
+        if ( shape->marker[SP_MARKER_LOC_MID] && (path_it->size_default() > 1) ) {
             Geom::Path::const_iterator curve_it1 = path_it->begin();      // incoming curve
             Geom::Path::const_iterator curve_it2 = ++(path_it->begin());  // outgoing curve
             while (curve_it2 != path_it->end_default())
@@ -431,7 +445,15 @@ sp_shape_update_marker_view (SPShape *shape, NRArenaItem *ai)
         }
 
         if ( shape->marker[SP_MARKER_LOC_END] ) {
-            Geom::Matrix const m (sp_shape_marker_get_transform_at_end(path_it->back_default()));
+            /* Get reference to last curve in the path.
+             * For moveto-only path, this returns the "closing line segment". */
+            unsigned int index = path_it->size_default();
+            if (index > 0) {
+                index--;
+            }
+            Geom::Curve const &lastcurve = (*path_it)[index];
+
+            Geom::Matrix const m = sp_shape_marker_get_transform_at_end(lastcurve);
             sp_marker_show_instance ((SPMarker* ) shape->marker[SP_MARKER_LOC_END], ai,
                                      NR_ARENA_ITEM_GET_KEY(ai) + SP_MARKER_LOC_END, end_pos, m,
                                      style->stroke_width.computed);
@@ -471,7 +493,7 @@ static void sp_shape_bbox(SPItem const *item, NRRect *bbox, NR::Matrix const &tr
 
         NRRect  cbbox;
 
-        Geom::Rect geombbox = bounds_exact_transformed(shape->curve->get_pathvector(), to_2geom(transform));
+        Geom::Rect geombbox = bounds_exact_transformed(shape->curve->get_pathvector(), transform);
         cbbox.x0 = geombbox[0][0];
         cbbox.y0 = geombbox[1][0];
         cbbox.x1 = geombbox[0][1];
@@ -502,7 +524,7 @@ static void sp_shape_bbox(SPItem const *item, NRRect *bbox, NR::Matrix const &tr
                         SPMarker* marker = SP_MARKER (shape->marker[SP_MARKER_LOC_START]);
                         SPItem* marker_item = sp_item_first_item_child (SP_OBJECT (shape->marker[SP_MARKER_LOC_START]));
 
-                        NR::Matrix tr(from_2geom(sp_shape_marker_get_transform_at_start(path_it->front())));
+                        NR::Matrix tr(sp_shape_marker_get_transform_at_start(path_it->front()));
 
                         if (marker->markerUnits == SP_MARKER_UNITS_STROKEWIDTH) {
                             tr = NR::scale(style->stroke_width.computed) * tr;
@@ -518,7 +540,7 @@ static void sp_shape_bbox(SPItem const *item, NRRect *bbox, NR::Matrix const &tr
                         nr_rect_d_union (&cbbox, &cbbox, &marker_bbox);
                     }
 
-                    if ( shape->marker[SP_MARKER_LOC_MID] ) {
+                    if ( shape->marker[SP_MARKER_LOC_MID] && (path_it->size_default() > 1) ) {
                         Geom::Path::const_iterator curve_it1 = path_it->begin();      // incoming curve
                         Geom::Path::const_iterator curve_it2 = ++(path_it->begin());  // outgoing curve
                         while (curve_it2 != path_it->end_default())
@@ -530,7 +552,7 @@ static void sp_shape_bbox(SPItem const *item, NRRect *bbox, NR::Matrix const &tr
                             SPMarker* marker = SP_MARKER (shape->marker[SP_MARKER_LOC_MID]);
                             SPItem* marker_item = sp_item_first_item_child (SP_OBJECT (shape->marker[SP_MARKER_LOC_MID]));
 
-                            NR::Matrix tr(from_2geom(sp_shape_marker_get_transform(*curve_it1, *curve_it2)));
+                            NR::Matrix tr(sp_shape_marker_get_transform(*curve_it1, *curve_it2));
 
                             if (marker->markerUnits == SP_MARKER_UNITS_STROKEWIDTH) {
                                 tr = NR::scale(style->stroke_width.computed) * tr;
@@ -554,7 +576,15 @@ static void sp_shape_bbox(SPItem const *item, NRRect *bbox, NR::Matrix const &tr
                         SPMarker* marker = SP_MARKER (shape->marker[SP_MARKER_LOC_END]);
                         SPItem* marker_item = sp_item_first_item_child (SP_OBJECT (shape->marker[SP_MARKER_LOC_END]));
 
-                        NR::Matrix tr(from_2geom(sp_shape_marker_get_transform_at_end(path_it->back_default())));
+                        /* Get reference to last curve in the path.
+                         * For moveto-only path, this returns the "closing line segment". */
+                        unsigned int index = path_it->size_default();
+                        if (index > 0) {
+                            index--;
+                        }
+                        Geom::Curve const &lastcurve = (*path_it)[index];
+
+                        NR::Matrix tr = sp_shape_marker_get_transform_at_end(lastcurve);
 
                         if (marker->markerUnits == SP_MARKER_UNITS_STROKEWIDTH) {
                             tr = NR::scale(style->stroke_width.computed) * tr;
@@ -601,27 +631,23 @@ sp_shape_print (SPItem *item, SPPrintContext *ctx)
             g_free(comment);
         }
 
-       /* fixme: Think (Lauris) */
-       sp_item_invoke_bbox(item, &pbox, NR::identity(), TRUE);
-       dbox.x0 = 0.0;
-       dbox.y0 = 0.0;
-       dbox.x1 = sp_document_width (SP_OBJECT_DOCUMENT (item));
-       dbox.y1 = sp_document_height (SP_OBJECT_DOCUMENT (item));
-       sp_item_bbox_desktop (item, &bbox);
-       NR::Matrix const i2d = from_2geom(sp_item_i2d_affine(item));
+    /* fixme: Think (Lauris) */
+    sp_item_invoke_bbox(item, &pbox, NR::identity(), TRUE);
+    dbox.x0 = 0.0;
+    dbox.y0 = 0.0;
+    dbox.x1 = sp_document_width (SP_OBJECT_DOCUMENT (item));
+    dbox.y1 = sp_document_height (SP_OBJECT_DOCUMENT (item));
+    sp_item_bbox_desktop (item, &bbox);
+    Geom::Matrix const i2d(sp_item_i2d_affine(item));
 
         SPStyle* style = SP_OBJECT_STYLE (item);
 
     if (!style->fill.isNone()) {
-        const_NRBPath bp;
-        bp.path = SP_CURVE_BPATH(shape->curve);
-        sp_print_fill (ctx, &bp, &i2d, style, &pbox, &dbox, &bbox);
+        sp_print_fill (ctx, shape->curve->get_pathvector(), &i2d, style, &pbox, &dbox, &bbox);
     }
 
     if (!style->stroke.isNone()) {
-        const_NRBPath bp;
-        bp.path = SP_CURVE_BPATH(shape->curve);
-        sp_print_stroke (ctx, &bp, &i2d, style, &pbox, &dbox, &bbox);
+        sp_print_stroke (ctx, shape->curve->get_pathvector(), &i2d, style, &pbox, &dbox, &bbox);
     }
 
     /* TODO: make code prettier: lots of variables can be taken out of the loop! */
@@ -631,7 +657,7 @@ sp_shape_print (SPItem *item, SPPrintContext *ctx)
             SPMarker* marker = SP_MARKER (shape->marker[SP_MARKER_LOC_START]);
             SPItem* marker_item = sp_item_first_item_child (SP_OBJECT (shape->marker[SP_MARKER_LOC_START]));
 
-            NR::Matrix tr(from_2geom(sp_shape_marker_get_transform_at_start(path_it->front())));
+            NR::Matrix tr(sp_shape_marker_get_transform_at_start(path_it->front()));
 
             if (marker->markerUnits == SP_MARKER_UNITS_STROKEWIDTH) {
                 tr = NR::scale(style->stroke_width.computed) * tr;
@@ -645,7 +671,7 @@ sp_shape_print (SPItem *item, SPPrintContext *ctx)
             marker_item->transform = old_tr;
         }
 
-        if ( shape->marker[SP_MARKER_LOC_MID] ) {
+        if ( shape->marker[SP_MARKER_LOC_MID] && (path_it->size_default() > 1) ) {
             Geom::Path::const_iterator curve_it1 = path_it->begin();      // incoming curve
             Geom::Path::const_iterator curve_it2 = ++(path_it->begin());  // outgoing curve
             while (curve_it2 != path_it->end_default())
@@ -657,7 +683,7 @@ sp_shape_print (SPItem *item, SPPrintContext *ctx)
                 SPMarker* marker = SP_MARKER (shape->marker[SP_MARKER_LOC_MID]);
                 SPItem* marker_item = sp_item_first_item_child (SP_OBJECT (shape->marker[SP_MARKER_LOC_MID]));
 
-                NR::Matrix tr(from_2geom(sp_shape_marker_get_transform(*curve_it1, *curve_it2)));
+                NR::Matrix tr(sp_shape_marker_get_transform(*curve_it1, *curve_it2));
 
                 if (marker->markerUnits == SP_MARKER_UNITS_STROKEWIDTH) {
                     tr = NR::scale(style->stroke_width.computed) * tr;
@@ -679,7 +705,15 @@ sp_shape_print (SPItem *item, SPPrintContext *ctx)
             SPMarker* marker = SP_MARKER (shape->marker[SP_MARKER_LOC_END]);
             SPItem* marker_item = sp_item_first_item_child (SP_OBJECT (shape->marker[SP_MARKER_LOC_END]));
 
-            NR::Matrix tr(from_2geom(sp_shape_marker_get_transform_at_end(path_it->back_default())));
+            /* Get reference to last curve in the path.
+             * For moveto-only path, this returns the "closing line segment". */
+            unsigned int index = path_it->size_default();
+            if (index > 0) {
+                index--;
+            }
+            Geom::Curve const &lastcurve = (*path_it)[index];
+
+            NR::Matrix tr = sp_shape_marker_get_transform_at_end(lastcurve);
 
             if (marker->markerUnits == SP_MARKER_UNITS_STROKEWIDTH) {
                 tr = NR::scale(style->stroke_width.computed) * tr;
@@ -715,7 +749,7 @@ sp_shape_show (SPItem *item, NRArena *arena, unsigned int /*key*/, unsigned int
         NRArenaShape * const s = NR_ARENA_SHAPE(arenaitem);
        nr_arena_shape_set_style(s, object->style);
        nr_arena_shape_set_path(s, shape->curve, false);
-        NR::Maybe<NR::Rect> paintbox = item->getBounds(NR::identity());
+        boost::optional<NR::Rect> paintbox = item->getBounds(NR::identity());
         if (paintbox) {
             s->setPaintBox(*paintbox);
         }
@@ -799,20 +833,44 @@ sp_shape_has_markers (SPShape const *shape)
 int
 sp_shape_number_of_markers (SPShape *shape, int type)
 {
-// TODO fixme: this looks very bad that the type parameter is ignored.
     Geom::PathVector const & pathv = shape->curve->get_pathvector();
 
-    guint n = shape->marker[SP_MARKER_LOC_START] ?  pathv.size() : 0;
+    switch(type) {
+        case SP_MARKER_LOC_START:
+            return shape->marker[SP_MARKER_LOC_START] ? pathv.size() : 0;
 
-    for(Geom::PathVector::const_iterator path_it = pathv.begin(); path_it != pathv.end(); ++path_it) {
-        n += shape->marker[SP_MARKER_LOC_MID] ?  path_it->size() : 0;
+        case SP_MARKER_LOC_MID:
+        {
+            if ( shape->marker[SP_MARKER_LOC_MID] ) {
+            guint n = 0;
+                for(Geom::PathVector::const_iterator path_it = pathv.begin(); path_it != pathv.end(); ++path_it) {
+                    n += path_it->size();
+                    n += path_it->closed() ? 1 : 0;
+                }
+                return n;
+            } else {
+                return 0;
+            }
+        }
 
-        if ( shape->marker[SP_MARKER_LOC_END] && !path_it->empty()) {
-            n++;
+        case SP_MARKER_LOC_END:
+        {
+            if ( shape->marker[SP_MARKER_LOC_END] ) {
+                guint n = 0;
+                for(Geom::PathVector::const_iterator path_it = pathv.begin(); path_it != pathv.end(); ++path_it) {
+                    if (!path_it->empty()) {
+                        n++;
+                    }
+                }
+                return n;
+            } else {
+                return 0;
+            }
         }
-    }
 
-    return n;
+        default:
+            return 0;
+    }
 }
 
 /**
@@ -981,37 +1039,39 @@ static void sp_shape_snappoints(SPItem const *item, SnapPointsIter p)
     if (shape->curve == NULL) {
         return;
     }
-    
-    NR::Matrix const i2d (from_2geom(sp_item_i2d_affine (item)));
-    NArtBpath const *b = SP_CURVE_BPATH(shape->curve);    
-    
-    // Cycle through the nodes in the concatenated subpaths
-    while (b->code != NR_END) {
-        NR::Point pos = b->c(3) * i2d; // this is the current node
-        
-        // NR_MOVETO Indicates the start of a closed subpath, see nr-path-code.h
-        // If we're looking at a closed subpath, then we can skip this first 
-        // point of the subpath because it's coincident with the last point.  
-        if (b->code != NR_MOVETO) {
-            if (b->code == NR_MOVETO_OPEN || b->code == NR_LINETO || b[1].code == NR_LINETO || b[1].code == NR_END) {
-                // end points of a line segment are always considered for snapping
-                *p = pos; 
-            } else {        
-                // g_assert(b->code == NR_CURVETO);
-                NR::Point ppos, npos;
-                ppos = b->code == NR_CURVETO ? b->c(2) * i2d : pos; // backward handle 
-                npos = b[1].code == NR_CURVETO ? b[1].c(1) * i2d : pos; // forward handle            
-                // Determine whether a node is at a smooth part of the path, by 
-                // calculating a measure for the collinearity of the handles
-                bool c1 = fabs (Inkscape::Util::triangle_area (pos, ppos, npos)) < 1; // points are (almost) collinear
-                bool c2 = NR::L2(pos - ppos) < 1e-6 || NR::L2(pos - npos) < 1e-6; // endnode, or a node with a retracted handle
-                if (!(c1 & !c2)) {
-                    *p = pos; // only return non-smooth nodes ("cusps")
-                }
+
+    Geom::PathVector const &pathv = shape->curve->get_pathvector();
+    if (pathv.empty())
+        return;
+
+    Geom::Matrix const i2d (sp_item_i2d_affine (item));
+
+    for(Geom::PathVector::const_iterator path_it = pathv.begin(); path_it != pathv.end(); ++path_it) {
+        *p = from_2geom(path_it->initialPoint() * i2d);
+
+        Geom::Path::const_iterator curve_it1 = path_it->begin();      // incoming curve
+        Geom::Path::const_iterator curve_it2 = ++(path_it->begin());  // outgoing curve
+        while (curve_it2 != path_it->end_closed())
+        {
+            /* Test whether to add the node between curve_it1 and curve_it2.
+             * Loop to end_closed (so always including closing segment), the last node to be added
+             * is the node between the closing segment and the segment before that one. Regardless
+             * of the path being closed. If the path is closed, the final point was already added by
+             * adding the initial point. */
+
+            Geom::NodeType nodetype = Geom::get_nodetype(*curve_it1, *curve_it2);
+
+            // Only add cusp nodes. TODO: Shouldn't this be a preference instead?
+            if (nodetype == Geom::NODE_NONE) {
+                *p = from_2geom(curve_it1->finalPoint() * i2d);
+            }
+            if (nodetype == Geom::NODE_CUSP) {
+                *p = from_2geom(curve_it1->finalPoint() * i2d);
             }
+
+            ++curve_it1;
+            ++curve_it2;
         }
-        
-        b++;
     }
 }