Code

Fix bbox snapping as reported in LP bug #562205
[inkscape.git] / src / display / curve.cpp
index 075f7df9e5a36cbd4970c7ed7315164e841334e0..1b54c981c25c8277917bc8477283972d549e2485 100644 (file)
@@ -51,10 +51,10 @@ SPCurve::new_from_rect(Geom::Rect const &rect)
     Geom::Point p = rect.corner(0);
     c->moveto(p);
 
-    for (int i=3; i>=0; i--) {
+    for (int i=3; i>=1; i--) {
         c->lineto(rect.corner(i));
     }
-    c->closepath_current();
+    c->closepath();
 
     return c;
 }
@@ -200,6 +200,7 @@ SPCurve::moveto(gdouble x, gdouble y)
 }
 /**
  * Perform a moveto to a point, thus starting a new subpath.
+ * Point p must be finite.
  */
 void
 SPCurve::moveto(Geom::Point const &p)
@@ -209,43 +210,62 @@ SPCurve::moveto(Geom::Point const &p)
 }
 
 /**
- * Calls SPCurve::lineto() with a point's coordinates.
+ * Adds a line to the current subpath.
+ * Point p must be finite.
  */
 void
 SPCurve::lineto(Geom::Point const &p)
 {
-    lineto(p[Geom::X], p[Geom::Y]);
+    if (_pathv.empty())  g_message("SPCurve::lineto - path is empty!");
+    else _pathv.back().appendNew<Geom::LineSegment>( p );
 }
 /**
- * Adds a line to the current subpath.
+ * Calls SPCurve::lineto( Geom::Point(x,y) )
  */
 void
 SPCurve::lineto(gdouble x, gdouble y)
 {
-    if (_pathv.empty())  g_message("leeg");
-    else _pathv.back().appendNew<Geom::LineSegment>( Geom::Point(x,y) );
+    lineto(Geom::Point(x,y));
 }
 
 /**
- * Calls SPCurve::curveto() with coordinates of three points.
+ * Adds a quadratic bezier segment to the current subpath.
+ * All points must be finite.
  */
 void
-SPCurve::curveto(Geom::Point const &p0, Geom::Point const &p1, Geom::Point const &p2)
+SPCurve::quadto(Geom::Point const &p1, Geom::Point const &p2)
 {
-    using Geom::X;
-    using Geom::Y;
-    curveto( p0[X], p0[Y],
-             p1[X], p1[Y],
-             p2[X], p2[Y] );
+    if (_pathv.empty())  g_message("SPCurve::quadto - path is empty!");
+    else _pathv.back().appendNew<Geom::QuadraticBezier>( p1, p2);
+}
+/**
+ * Calls SPCurve::quadto( Geom::Point(x1,y1), Geom::Point(x2,y2) )
+ * All coordinates must be finite.
+ */
+void
+SPCurve::quadto(gdouble x1, gdouble y1, gdouble x2, gdouble y2)
+{
+    quadto( Geom::Point(x1,y1), Geom::Point(x2,y2) );
 }
+
 /**
  * Adds a bezier segment to the current subpath.
+ * All points must be finite.
+ */
+void
+SPCurve::curveto(Geom::Point const &p0, Geom::Point const &p1, Geom::Point const &p2)
+{
+    if (_pathv.empty())  g_message("SPCurve::curveto - path is empty!");
+    else _pathv.back().appendNew<Geom::CubicBezier>( p0, p1, p2 );
+}
+/**
+ * Calls SPCurve::curveto( Geom::Point(x0,y0), Geom::Point(x1,y1), Geom::Point(x2,y2) )
+ * All coordinates must be finite.
  */
 void
 SPCurve::curveto(gdouble x0, gdouble y0, gdouble x1, gdouble y1, gdouble x2, gdouble y2)
 {
-    if (_pathv.empty())  g_message("leeg");
-    else _pathv.back().appendNew<Geom::CubicBezier>( Geom::Point(x0,y0), Geom::Point(x1,y1), Geom::Point(x2,y2) );
+    curveto( Geom::Point(x0,y0), Geom::Point(x1,y1), Geom::Point(x2,y2) );
 }
 
 /**
@@ -265,7 +285,11 @@ SPCurve::closepath()
 void
 SPCurve::closepath_current()
 {
-    _pathv.back().setFinal(_pathv.back().initialPoint());
+    if (_pathv.back().size() > 0 && dynamic_cast<Geom::LineSegment const *>(&_pathv.back().back_open())) {
+        _pathv.back().erase_last();
+    } else {
+        _pathv.back().setFinal(_pathv.back().initialPoint());
+    }
     _pathv.back().close(true);
 }
 
@@ -360,65 +384,81 @@ SPCurve::first_path() const
 }
 
 /**
- * Return first point of first subpath or (0,0).  TODO: shouldn't this be (NR_HUGE, NR_HUGE) to be able to tell it apart from normal (0,0) ?
+ * Return first point of first subpath or nothing when the path is empty.
  */
-Geom::Point
+boost::optional<Geom::Point>
 SPCurve::first_point() const
 {
-    if (is_empty())
-        return Geom::Point(0, 0);
+    boost::optional<Geom::Point> retval;
 
-    return _pathv.front().initialPoint();
+    if (!is_empty()) {
+        retval = _pathv.front().initialPoint();
+    }
+
+    return retval;
 }
 
 /**
  * Return the second point of first subpath or _movePos if curve too short.
- * If the pathvector is empty, this returns (0,0). If the first path is only a moveto, this method
+ * If the pathvector is empty, this returns nothing. If the first path is only a moveto, this method
  * returns the first point of the second path, if it exists. If there is no 2nd path, it returns the
  * first point of the first path.
- *
- * FIXME: for empty paths shouldn't this return (NR_HUGE,NR_HUGE)
  */
-Geom::Point
+boost::optional<Geom::Point>
 SPCurve::second_point() const
 {
-    if (is_empty()) {
-        return Geom::Point(0,0);
-    }
-    else if (_pathv.front().empty()) {
-        // first path is only a moveto
-        // check if there is second path
-        if (_pathv.size() > 1) {
-            return _pathv[1].initialPoint();
+    boost::optional<Geom::Point> retval;
+    if (!is_empty()) {
+        if (_pathv.front().empty()) {
+            // first path is only a moveto
+            // check if there is second path
+            if (_pathv.size() > 1) {
+                retval = _pathv[1].initialPoint();
+            } else {
+                retval = _pathv[0].initialPoint();
+            }
         } else {
-            return _pathv[0].initialPoint();
+            retval = _pathv.front()[0].finalPoint();
         }
     }
-    else
-        return _pathv.front()[0].finalPoint();
+
+    return retval;
 }
 
 /**
- * TODO: fix comment: Return the second-last point of last subpath or _movePos if curve too short.
+ * Return the second-last point of last subpath or first point when that last subpath has only a moveto.
  */
-Geom::Point
+boost::optional<Geom::Point>
 SPCurve::penultimate_point() const
 {
-    Geom::Curve const& back = _pathv.back().back_default();
-    return back.initialPoint();
+    boost::optional<Geom::Point> retval;
+    if (!is_empty()) {
+        Geom::Path const &lastpath = _pathv.back();
+        if (!lastpath.empty()) {
+            Geom::Curve const &back = lastpath.back_default();
+            retval = back.initialPoint();
+        } else {
+            retval = lastpath.initialPoint();
+        }
+    }
+
+    return retval;
 }
 
 /**
- * Return last point of last subpath or (0,0).  TODO: shouldn't this be (NR_HUGE, NR_HUGE) to be able to tell it apart from normal (0,0) ?
+ * Return last point of last subpath or nothing when the curve is empty.
  * If the last path is only a moveto, then return that point.
  */
-Geom::Point
+boost::optional<Geom::Point>
 SPCurve::last_point() const
 {
-    if (is_empty())
-        return Geom::Point(0, 0);
+    boost::optional<Geom::Point> retval;
 
-    return _pathv.back().finalPoint();
+    if (!is_empty()) {
+        retval = _pathv.back().finalPoint();
+    }
+
+    return retval;
 }
 
 /**
@@ -493,8 +533,8 @@ SPCurve::append_continuous(SPCurve const *c1, gdouble tolerance)
         return this;
     }
 
-    if ( (fabs(this->last_point()[X] - c1->first_point()[X]) <= tolerance)
-         && (fabs(this->last_point()[Y] - c1->first_point()[Y]) <= tolerance) )
+    if ( (fabs((*this->last_point())[X] - (*c1->first_point())[X]) <= tolerance)
+         && (fabs((*this->last_point())[Y] - (*c1->first_point())[Y]) <= tolerance) )
     {
     // c1's first subpath can be appended to this curve's last subpath
         Geom::PathVector::const_iterator path_it = c1->_pathv.begin();
@@ -509,7 +549,7 @@ SPCurve::append_continuous(SPCurve const *c1, gdouble tolerance)
         }
 
     } else {
-        append(c1, false);
+        append(c1, true);
     }
 
     return this;
@@ -533,6 +573,12 @@ SPCurve::backspace()
 
 /**
  * TODO: add comments about what this method does and what assumptions are made and requirements are put on SPCurve
+ (2:08:18 AM) Johan: basically, i convert the path to pw<d2>
+(2:08:27 AM) Johan: then i calculate an offset path
+(2:08:29 AM) Johan: to move the knots
+(2:08:36 AM) Johan: then i add it
+(2:08:40 AM) Johan: then convert back to path
+If I remember correctly, this moves the firstpoint to new_p0, and the lastpoint to new_p1, and moves all nodes in between according to their arclength (interpolates the movement amount)
  */
 void
 SPCurve::stretch_endpoints(Geom::Point const &new_p0, Geom::Point const &new_p1)
@@ -541,8 +587,8 @@ SPCurve::stretch_endpoints(Geom::Point const &new_p0, Geom::Point const &new_p1)
         return;
     }
 
-    Geom::Point const offset0( new_p0 - first_point() );
-    Geom::Point const offset1( new_p1 - last_point() );
+    Geom::Point const offset0( new_p0 - *first_point() );
+    Geom::Point const offset1( new_p1 - *last_point() );
 
     Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2 = _pathv.front().toPwSb();
     Geom::Piecewise<Geom::SBasis> arclength = Geom::arcLengthSb(pwd2);
@@ -575,6 +621,8 @@ SPCurve::move_endpoints(Geom::Point const &new_p0, Geom::Point const &new_p1)
 
 /**
  * returns the number of nodes in a path, used for statusbar text when selecting an spcurve.
+ * Sum of nodes in all the paths. When a path is closed, and its closing line segment is of zero-length,
+ * this function will not count the closing knot double (so basically ignores the closing line segment when it has zero length)
  */
 guint
 SPCurve::nodes_in_path() const
@@ -584,6 +632,17 @@ SPCurve::nodes_in_path() const
         nr += (*it).size();
 
         nr++; // count last node (this works also for closed paths because although they don't have a 'last node', they do have an extra segment
+
+        // do not count closing knot double for zero-length closing line segments
+        // however, if the path is only a moveto, and is closed, do not subtract 1 (otherwise the result will be zero nodes)
+        if ( it->closed()
+             && ((*it).size() != 0) )
+        {
+            Geom::Curve const &c = it->back_closed();
+            if (are_near(c.initialPoint(), c.finalPoint())) {
+                nr--;   
+            }
+        }
     }
 
     return nr;