Code

add comments to SPCurve about 2geomify status of functions
[inkscape.git] / src / display / curve.cpp
1 #define __CURVE_C__
3 /** \file
4  * Routines for SPCurve and for NArtBpath arrays in general.
5  */
7 /*
8  * Author:
9  *   Lauris Kaplinski <lauris@kaplinski.com>
10  *
11  * Copyright (C) 2000 Lauris Kaplinski
12  * Copyright (C) 2000-2001 Ximian, Inc.
13  * Copyright (C) 2002 Lauris Kaplinski
14  * Copyright (C) 2008 Johan Engelen
15  *
16  * Released under GNU GPL
17  */
19 #include "display/curve.h"
21 #include <string.h>
22 #include <glib/gmem.h>
23 #include "libnr/nr-point.h"
24 #include "libnr/nr-rect.h"
25 #include <libnr/n-art-bpath.h>
26 #include <libnr/nr-point-matrix-ops.h>
27 #include <libnr/nr-translate-ops.h>
28 #include <libnr/n-art-bpath-2geom.h>
29 #include <libnr/nr-convert2geom.h>
30 #include <cstring>
31 #include <string>
32 #include <2geom/pathvector.h>
33 #include <2geom/sbasis-geometric.h>
34 #include <2geom/sbasis-to-bezier.h>
35 #include "svg/svg.h"
37 static unsigned sp_bpath_length(NArtBpath const bpath[]);
38 static bool sp_bpath_closed(NArtBpath const bpath[]);
40 #define NO_CHECKS   // define this to disable the checking for unequal paths in SPCurve, improves performance by a lot!
42 static void debug_out( char const * text, Geom::PathVector const & pathv) {
43 #ifndef NO_CHECKS
44     char * str = sp_svg_write_path(pathv);
45     g_message("%s : %s", text, str);
46     g_free(str);
47 #endif
48 }
49 static void debug_out( char const * text, NArtBpath const * bpath) {
50 #ifndef NO_CHECKS
51     char * str = sp_svg_write_path(bpath);
52     g_message("%s : %s", text, str);
53     g_free(str);
54 #endif
55 }
56 void SPCurve::debug_check( char const * text, SPCurve const * curve) {
57 #ifndef NO_CHECKS
58     char * pathv_str = sp_svg_write_path(curve->_pathv);
59     char * bpath_str = sp_svg_write_path(curve->_bpath);
60     if ( strcmp(pathv_str, bpath_str) ) {
61         g_message("%s : unequal paths", text);
62         g_message("bpath : %s", bpath_str);
63         g_message("pathv : %s", pathv_str);
64     }
65     g_free(pathv_str);
66     g_free(bpath_str);
67 #endif
68 }
69 void SPCurve::debug_check( char const * text, bool a) {
70 #ifndef NO_CHECKS
71     if ( !a ) {
72         g_message("%s : bool fail", text);
73     }
74 #endif
75 }
77 /* Constructors */
79 /**
80  * The returned curve's state is as if SPCurve::reset has just been called on it.
81  * \param length Initial number of NArtBpath elements allocated for bpath (including NR_END
82  *    element).
83  * 2GEOMproof
84  */
85 SPCurve::SPCurve(guint length)
86   : _refcount(1),
87     _bpath(NULL),
88     _pathv(),
89     _end(0),
90     _length(length),
91     _substart(0),
92     _hascpt(false),
93     _posSet(false),
94     _moving(false),
95     _closed(false)
96 {
97     if (length <= 0) {
98         g_error("SPCurve::SPCurve called with invalid length parameter");
99         throw;
100     }
102     _bpath = g_new(NArtBpath, length);
103     _bpath->code = NR_END;
105     _pathv.clear();
107     debug_check("SPCurve::SPCurve(guint length)", this);
110 SPCurve::SPCurve(Geom::PathVector const& pathv)
111   : _refcount(1),
112     _bpath(NULL),
113     _pathv(pathv),
114     _end(0),
115     _length(0),
116     _substart(0),
117     _hascpt(false),
118     _posSet(false),
119     _moving(false),
120     _closed(false)
122     // temporary code to convert to _bpath as well:
123     _bpath = BPath_from_2GeomPath(_pathv);
124     unsigned const len = sp_bpath_length(_bpath);
125     _length = len;
126     _end = _length - 1;
127     gint i = _end;
128     for (; i > 0; i--)
129         if ((_bpath[i].code == NR_MOVETO) ||
130             (_bpath[i].code == NR_MOVETO_OPEN))
131             break;
132     _substart = i;
133     _closed = sp_bpath_closed(_bpath);
135     debug_check("SPCurve::SPCurve(Geom::PathVector const& pathv)", this);
138 // * 2GEOMproof
139 SPCurve *
140 SPCurve::new_from_foreign_bpath(NArtBpath const *bpath)
142     g_return_val_if_fail(bpath != NULL, NULL);
144     NArtBpath *new_bpath;
145     unsigned const len = sp_bpath_length(bpath);
146     new_bpath = g_new(NArtBpath, len);
147     memcpy(new_bpath, bpath, len * sizeof(NArtBpath));
149     SPCurve *curve = new SPCurve();
151     curve->_bpath = new_bpath;
152     curve->_length = len;
153     curve->_end = curve->_length - 1;
154     gint i = curve->_end;
155     for (; i > 0; i--)
156         if ((curve->_bpath[i].code == NR_MOVETO) ||
157             (curve->_bpath[i].code == NR_MOVETO_OPEN))
158             break;
159     curve->_substart = i;
160     curve->_closed = sp_bpath_closed(new_bpath);
162     curve->_pathv = BPath_to_2GeomPath(curve->_bpath);
164     debug_check("SPCurve::new_from_foreign_bpath", curve);
166     return curve;
169 /**
170  * Convert NArtBpath object to SPCurve object.
171  *
172  * \return new SPCurve, or NULL if the curve was not created for some reason.
173  * 2GEOMproof
174  */
175 SPCurve *
176 SPCurve::new_from_bpath(NArtBpath *bpath)
178     g_return_val_if_fail(bpath != NULL, NULL);
180     SPCurve *curve = SPCurve::new_from_foreign_bpath(bpath);
181     g_free(bpath);
183     debug_check("SPCurve::new_from_bpath", curve);
185     return curve;
188 // * 2GEOMproof
189 SPCurve *
190 SPCurve::new_from_rect(NR::Maybe<NR::Rect> const &rect)
192     g_return_val_if_fail(rect, NULL);
194     SPCurve *c =  new SPCurve();
196     NR::Point p = rect->corner(0);
197     c->moveto(p);
199     for (int i=3; i>=0; i--) {
200         c->lineto(rect->corner(i));
201     }
202     c->closepath_current();
204     debug_check("SPCurve::new_from_rect", c);
206     return c;
209 // * 2GEOMproof
210 SPCurve::~SPCurve()
212     if (_bpath) {
213         g_free(_bpath);
214         _bpath = NULL;
215     }
218 /* Methods */
220 void
221 SPCurve::set_pathv(Geom::PathVector const & new_pathv)
223     _pathv = new_pathv;
225     _hascpt = false;
226     _posSet = false;
227     _moving = false;
229     // temporary code to convert to _bpath as well:
230     if (_bpath) {
231         g_free(_bpath);
232         _bpath = NULL;
233     }
234     _bpath = BPath_from_2GeomPath(_pathv);
235     unsigned const len = sp_bpath_length(_bpath);
236     _length = len;
237     _end = _length - 1;
238     gint i = _end;
239     for (; i > 0; i--)
240         if ((_bpath[i].code == NR_MOVETO) ||
241             (_bpath[i].code == NR_MOVETO_OPEN))
242             break;
243     _substart = i;
244     _closed = sp_bpath_closed(_bpath);
246     debug_check("SPCurve::set_pathv", this);
249 /**
250  * Get pointer to bpath data. Don't keep this reference too long, because the path might change by another function.
251  */
252 NArtBpath const *
253 SPCurve::get_bpath() const
255     debug_check("SPCurve::get_bpath", this);
256     return _bpath;
257 };
259 Geom::PathVector const &
260 SPCurve::get_pathvector() const
262     debug_check("SPCurve::get_pathvector", this);
263     return _pathv;
266 /**
267  *Returns index in bpath[] of NR_END element.
268  * remove for 2geom
269  */
270 guint
271 SPCurve::get_length() const
273 //    g_message("SPCurve::get_length must be removed");
275     return _end;
278 /**
279  * Increase _refcount of curve.
280  *
281  * \todo should this be shared with other refcounting code?
282  * 2GEOMproof
283  */
284 SPCurve *
285 SPCurve::ref()
287     g_return_val_if_fail(this != NULL, NULL);
289     _refcount += 1;
291     return this;
294 /**
295  * Decrease refcount of curve, with possible destruction.
296  *
297  * \todo should this be shared with other refcounting code?
298  * 2GEOMproof
299  */
300 SPCurve *
301 SPCurve::unref()
303     g_return_val_if_fail(this != NULL, NULL);
305     _refcount -= 1;
307     if (_refcount < 1) {
308         delete this;
309     }
311     return NULL;
314 /**
315  * Add space for more paths in curve.
316  * This function has no meaning for 2geom representation, other than maybe for optimization issues (enlargening the vector for what is to come)
317  * 2GEOMproof
318  */
319 void
320 SPCurve::ensure_space(guint space)
322     g_return_if_fail(this != NULL);
323     g_return_if_fail(space > 0);
325     if (_end + space < _length)
326         return;
328     if (space < SP_CURVE_LENSTEP)
329         space = SP_CURVE_LENSTEP;
331     _bpath = g_renew(NArtBpath, _bpath, _length + space);
333     _length += space;
336 /**
337  * Create new curve from its own bpath array.
338  * 2GEOMproof
339  */
340 SPCurve *
341 SPCurve::copy() const
343     g_return_val_if_fail(this != NULL, NULL);
345     return SPCurve::new_from_foreign_bpath(_bpath);
348 /**
349  * Return new curve that is the concatenation of all curves in list.
350  * 2GEOMified
351  */
352 SPCurve *
353 SPCurve::concat(GSList const *list)
355     g_return_val_if_fail(list != NULL, NULL);
357     gint length = 0;
359     for (GSList const *l = list; l != NULL; l = l->next) {
360         SPCurve *c = (SPCurve *) l->data;
361         length += c->_end;
362     }
364     SPCurve *new_curve = new SPCurve(length + 1);
366     NArtBpath *bp = new_curve->_bpath;
368     for (GSList const *l = list; l != NULL; l = l->next) {
369         SPCurve *c = (SPCurve *) l->data;
370         memcpy(bp, c->_bpath, c->_end * sizeof(NArtBpath));
371         bp += c->_end;
372     }
374     bp->code = NR_END;
376     new_curve->_end = length;
377     gint i;
378     for (i = new_curve->_end; i > 0; i--) {
379         if ((new_curve->_bpath[i].code == NR_MOVETO)     ||
380             (new_curve->_bpath[i].code == NR_MOVETO_OPEN)  )
381             break;
382     }
384     new_curve->_substart = i;
386     for (GSList const *l = list; l != NULL; l = l->next) {
387         SPCurve *c = (SPCurve *) l->data;
388         new_curve->_pathv.insert( new_curve->_pathv.end(), c->get_pathvector().begin(), c->get_pathvector().end() );
389     }
391     debug_check("SPCurve::concat", new_curve);
393     return new_curve;
396 /**
397  * Returns a list of new curves corresponding to the subpaths in \a curve.
398  * 2geomified
399  */
400 GSList *
401 SPCurve::split() const
403     g_return_val_if_fail(this != NULL, NULL);
405     guint p = 0;
406     GSList *l = NULL;
408     gint pathnr = 0;
409     while (p < _end) {
410         gint i = 1;
411         while ((_bpath[p + i].code == NR_LINETO) ||
412                (_bpath[p + i].code == NR_CURVETO))
413             i++;
414         SPCurve *new_curve = new SPCurve(i + 1);
415         memcpy(new_curve->_bpath, _bpath + p, i * sizeof(NArtBpath));
416         new_curve->_end = i;
417         new_curve->_bpath[i].code = NR_END;
418         new_curve->_substart = 0;
419         new_curve->_closed = (new_curve->_bpath->code == NR_MOVETO);
420         new_curve->_hascpt = (new_curve->_bpath->code == NR_MOVETO_OPEN);
421         new_curve->_pathv = Geom::PathVector(1, _pathv[pathnr]);
422         l = g_slist_prepend(l, new_curve);
423         p += i;
424         pathnr++;
425     }
427     return l;
430 /**
431  * Transform all paths in curve, template helper.
432  */
433 template<class M>
434 static void
435 tmpl_curve_transform(SPCurve * curve, M const &m)
437     g_return_if_fail(curve != NULL);
439     for (guint i = 0; i < curve->_end; i++) {
440         NArtBpath *p = curve->_bpath + i;
441         switch (p->code) {
442             case NR_MOVETO:
443             case NR_MOVETO_OPEN:
444             case NR_LINETO: {
445                 p->setC(3, p->c(3) * m);
446                 break;
447             }
448             case NR_CURVETO:
449                 for (unsigned i = 1; i <= 3; ++i) {
450                     p->setC(i, p->c(i) * m);
451                 }
452                 break;
453             default:
454                 g_warning("Illegal pathcode %d", p->code);
455                 break;
456         }
457     }
460 /**
461  * Transform all paths in curve using matrix.
462  * 2GEOMified, can be deleted when completely 2geom
463  */
464 void
465 SPCurve::transform(NR::Matrix const &m)
467     tmpl_curve_transform<NR::Matrix>(this, m);
469     _pathv = _pathv * to_2geom(m);
471     debug_check("SPCurve::transform(NR::Matrix const &m)", this);
474 /**
475  * Transform all paths in curve using matrix.
476  */
477 void
478 SPCurve::transform(Geom::Matrix const &m)
480     tmpl_curve_transform<NR::Matrix>(this, from_2geom(m));
482     _pathv = _pathv * m;
484     debug_check("SPCurve::transform(Geom::Matrix const &m)", this);
487 /**
488  * Transform all paths in curve using NR::translate.
489  * 2GEOMified, can be deleted when completely 2geom
490  */
491 void
492 SPCurve::transform(NR::translate const &m)
494     tmpl_curve_transform<NR::translate>(this, m);
496     _pathv = _pathv * to_2geom(m);
498     debug_check("SPCurve::transform(NR::translate const &m)", this);
501 /**
502  * Set curve to empty curve.
503  * 2GEOMified
504  */
505 void
506 SPCurve::reset()
508     g_return_if_fail(this != NULL);
510     _bpath->code = NR_END;
511     _end = 0;
512     _substart = 0;
513     _hascpt = false;
514     _posSet = false;
515     _moving = false;
516     _closed = false;
518     _pathv.clear();
520     debug_check("SPCurve::reset", this);
523 /* Several consecutive movetos are ALLOWED */
525 /**
526  * Calls SPCurve::moveto() with point made of given coordinates.
527  */
528 void
529 SPCurve::moveto(gdouble x, gdouble y)
531     moveto(NR::Point(x, y));
533 /**
534  * Calls SPCurve::moveto() with point made of given coordinates.
535  */
536 void
537 SPCurve::moveto(Geom::Point const &p)
539     moveto(from_2geom(p));
541 /**
542  * Perform a moveto to a point, thus starting a new subpath.
543  * 2GEOMified
544  */
545 void
546 SPCurve::moveto(NR::Point const &p)
548     g_return_if_fail(this != NULL);
549     g_return_if_fail(!_moving);
551     _substart = _end;
552     _hascpt = true;
553     _posSet = true;
554     _movePos = p;
555     _pathv.push_back( Geom::Path() );  // for some reason Geom::Path(p) does not work...
556     _pathv.back().start(to_2geom(p));
558     // the output is not the same. This is because SPCurve *incorrectly* coaslesces multiple moveto's into one for NArtBpath.
559 //    debug_check("SPCurve::moveto", this);
562 /**
563  * Calls SPCurve::lineto() with a point's coordinates.
564  */
565 void
566 SPCurve::lineto(Geom::Point const &p)
568     lineto(p[Geom::X], p[Geom::Y]);
570 /**
571  * Calls SPCurve::lineto() with a point's coordinates.
572  */
573 void
574 SPCurve::lineto(NR::Point const &p)
576     lineto(p[NR::X], p[NR::Y]);
578 /**
579  * Adds a line to the current subpath.
580  * 2GEOMified
581  */
582 void
583 SPCurve::lineto(gdouble x, gdouble y)
585     g_return_if_fail(this != NULL);
586     g_return_if_fail(_hascpt);
588     if (_moving) {
589         /* fix endpoint */
590         g_return_if_fail(!_posSet);
591         g_return_if_fail(_end > 1);
592         NArtBpath *bp = _bpath + _end - 1;
593         g_return_if_fail(bp->code == NR_LINETO);
594         bp->x3 = x;
595         bp->y3 = y;
596         _moving = false;
598         Geom::Path::iterator it = _pathv.back().end();
599         if ( Geom::LineSegment const *last_line_segment = dynamic_cast<Geom::LineSegment const *>( &(*it) )) {
600             Geom::LineSegment new_seg( *last_line_segment );
601             new_seg.setFinal( Geom::Point(x,y) );
602             _pathv.back().replace(it, new_seg);
603         }
604     } else if (_posSet) {
605         /* start a new segment */
606         ensure_space(2);
607         NArtBpath *bp = _bpath + _end;
608         bp->code = NR_MOVETO_OPEN;
609         bp->setC(3, _movePos);
610         bp++;
611         bp->code = NR_LINETO;
612         bp->x3 = x;
613         bp->y3 = y;
614         bp++;
615         bp->code = NR_END;
616         _end += 2;
617         _posSet = false;
618         _closed = false;
620         _pathv.back().appendNew<Geom::LineSegment>( Geom::Point(x,y) );
621         return;
622     } else {
623         /* add line */
625         g_return_if_fail(_end > 1);
626         ensure_space(1);
627         NArtBpath *bp = _bpath + _end;
628         bp->code = NR_LINETO;
629         bp->x3 = x;
630         bp->y3 = y;
631         bp++;
632         bp->code = NR_END;
633         _end++;
634         _pathv.back().appendNew<Geom::LineSegment>( Geom::Point(x,y) );
635     }
637     debug_check("SPCurve::lineto", this);
640 /**
641  * Calls SPCurve::curveto() with coordinates of three points.
642  */
643 void
644 SPCurve::curveto(Geom::Point const &p0, Geom::Point const &p1, Geom::Point const &p2)
646     using Geom::X;
647     using Geom::Y;
648     curveto( p0[X], p0[Y],
649              p1[X], p1[Y],
650              p2[X], p2[Y] );
652 /**
653  * Calls SPCurve::curveto() with coordinates of three points.
654  */
655 void
656 SPCurve::curveto(NR::Point const &p0, NR::Point const &p1, NR::Point const &p2)
658     using NR::X;
659     using NR::Y;
660     curveto( p0[X], p0[Y],
661              p1[X], p1[Y],
662              p2[X], p2[Y] );
664 /**
665  * Adds a bezier segment to the current subpath.
666  * 2GEOMified
667  */
668 void
669 SPCurve::curveto(gdouble x0, gdouble y0, gdouble x1, gdouble y1, gdouble x2, gdouble y2)
671     g_return_if_fail(this != NULL);
672     g_return_if_fail(_hascpt);
673     g_return_if_fail(!_moving);
675     if (_posSet) {
676         /* start a new segment */
677         ensure_space(2);
678         NArtBpath *bp = _bpath + _end;
679         bp->code = NR_MOVETO_OPEN;
680         bp->setC(3, _movePos);
681         bp++;
682         bp->code = NR_CURVETO;
683         bp->x1 = x0;
684         bp->y1 = y0;
685         bp->x2 = x1;
686         bp->y2 = y1;
687         bp->x3 = x2;
688         bp->y3 = y2;
689         bp++;
690         bp->code = NR_END;
691         _end += 2;
692         _posSet = false;
693         _closed = false;
694         _pathv.back().appendNew<Geom::CubicBezier>( Geom::Point(x0,y0), Geom::Point(x1,y1), Geom::Point(x2,y2) );
695     } else {
696         /* add curve */
698         g_return_if_fail(_end > 1);
699         ensure_space(1);
700         NArtBpath *bp = _bpath + _end;
701         bp->code = NR_CURVETO;
702         bp->x1 = x0;
703         bp->y1 = y0;
704         bp->x2 = x1;
705         bp->y2 = y1;
706         bp->x3 = x2;
707         bp->y3 = y2;
708         bp++;
709         bp->code = NR_END;
710         _end++;
711         if (_pathv.empty())  g_message("leeg");
712         else _pathv.back().appendNew<Geom::CubicBezier>( Geom::Point(x0,y0), Geom::Point(x1,y1), Geom::Point(x2,y2) );
713     }
715     debug_check("SPCurve::curveto", this);
718 /**
719  * Close current subpath by possibly adding a line between start and end.
720   * 2GEOMified
721  */
722 void
723 SPCurve::closepath()
725     g_return_if_fail(this != NULL);
726     g_return_if_fail(_hascpt);
727     g_return_if_fail(!_posSet);
728     g_return_if_fail(!_moving);
729     g_return_if_fail(!_closed);
730     /* We need at least moveto, curveto, end. */
731     g_return_if_fail(_end - _substart > 1);
733     {
734         NArtBpath *bs = _bpath + _substart;
735         NArtBpath *be = _bpath + _end - 1;
737         if (bs->c(3) != be->c(3)) {
738             lineto(bs->c(3));
739             bs = _bpath + _substart;
740         }
742         bs->code = NR_MOVETO;
743     }
744     // Inkscape always manually adds the closing line segment to SPCurve with a lineto.
745     // This lineto is removed in the writing function for NArtBpath, 
746     // so when path is closed and the last segment is a lineto, the closing line segment must really be removed first!
747     // TODO: fix behavior in Inkscape!
748     if ( /*Geom::LineSegment const *line_segment = */ dynamic_cast<Geom::LineSegment const  *>(&_pathv.back().back())) {
749         _pathv.back().erase_last();
750     }
751     _pathv.back().close(true);
752     _closed = true;
754     for (Geom::PathVector::const_iterator it = _pathv.begin(); it != _pathv.end(); it++) {
755          if ( ! it->closed() ) {
756             _closed = false;
757             break;
758         }
759     }
761     for (NArtBpath const *bp = _bpath; bp->code != NR_END; bp++) {
762         /** \todo
763          * effic: Maintain a count of NR_MOVETO_OPEN's (e.g. instead of
764          * the closed boolean).
765          */
766         if (bp->code == NR_MOVETO_OPEN) {
767             _closed = false;
768             break;
769         }
770     }
772     _hascpt = false;
774     debug_check("SPCurve::closepath", this);
777 /** Like SPCurve::closepath() but sets the end point of the current
778     command to the subpath start point instead of adding a new lineto.
780     Used for freehand drawing when the user draws back to the start point.
781   
782   2GEOMified
783 **/
784 void
785 SPCurve::closepath_current()
787     g_return_if_fail(this != NULL);
788     g_return_if_fail(_hascpt);
789     g_return_if_fail(!_posSet);
790     g_return_if_fail(!_closed);
791     /* We need at least moveto, curveto, end. */
792     g_return_if_fail(_end - _substart > 1);
794     {
795         NArtBpath *bs = _bpath + _substart;
796         NArtBpath *be = _bpath + _end - 1;
798         be->x3 = bs->x3;
799         be->y3 = bs->y3;
801         bs->code = NR_MOVETO;
802     }
803     // Inkscape always manually adds the closing line segment to SPCurve with a lineto.
804     // This lineto is removed in the writing function for NArtBpath, 
805     // so when path is closed and the last segment is a lineto, the closing line segment must really be removed first!
806     // TODO: fix behavior in Inkscape!
807     if ( /*Geom::LineSegment const *line_segment = */ dynamic_cast<Geom::LineSegment const  *>(&_pathv.back().back())) {
808         _pathv.back().erase_last();
809     }
810     _pathv.back().close(true);
811     _closed = true;
813     for (Geom::PathVector::const_iterator it = _pathv.begin(); it != _pathv.end(); it++) {
814          if ( ! it->closed() ) {
815             _closed = false;
816             break;
817         }
818     }
820     for (NArtBpath const *bp = _bpath; bp->code != NR_END; bp++) {
821         /** \todo
822          * effic: Maintain a count of NR_MOVETO_OPEN's (e.g. instead of
823          * the closed boolean).
824          */
825         if (bp->code == NR_MOVETO_OPEN) {
826             _closed = false;
827             break;
828         }
829     }
831     _hascpt = false;
832     _moving = false;
834     debug_check("SPCurve::closepath_current", this);
837 /**
838  * True if no paths are in curve.
839  * 2GEOMproof
840  */
841 bool
842 SPCurve::is_empty() const
844     g_return_val_if_fail(this != NULL, TRUE);
846     if (!_bpath)
847         return true;
849     bool empty = _pathv.empty(); /* || _pathv.front().empty(); */
850     debug_check("SPCurve::is_empty", (_bpath->code == NR_END)  ==  empty );
852     return (_bpath->code == NR_END);
855 /**
856  * True iff all subpaths are closed.
857  * 2GEOMproof
858  */
859 bool
860 SPCurve::is_closed() const
862     bool closed = true;
863     for (Geom::PathVector::const_iterator it = _pathv.begin(); it != _pathv.end(); it++) {
864          if ( ! it->closed() ) {
865             closed = false;
866             break;
867         }
868     }
869     debug_check("SPCurve::is_closed", (closed)  ==  (_closed) );
871     return _closed;
874 /**
875  * Return last subpath or NULL.
876  */
877 NArtBpath const *
878 SPCurve::last_bpath() const
880     g_return_val_if_fail(this != NULL, NULL);
882     if (_end == 0) {
883         return NULL;
884     }
886     return _bpath + _end - 1;
889 /**
890  * Return first subpath or NULL.
891  */
892 NArtBpath const *
893 SPCurve::first_bpath() const
895     g_return_val_if_fail(this != NULL, NULL);
897     if (_end == 0) {
898         return NULL;
899     }
901     return _bpath;
904 /**
905  * 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) ?
906  */
907 NR::Point
908 SPCurve::first_point() const
910     NArtBpath const * bpath = first_bpath();
911     g_return_val_if_fail(bpath != NULL, NR::Point(0, 0));
912     if (is_empty())
913         return NR::Point(0, 0);
915     debug_check("SPCurve::first_point", bpath->c(3) == _pathv.front().initialPoint() );
917     return bpath->c(3);
918     // return from_2geom( _pathv.front().initialPoint() );
921 /**
922  * Return the second point of first subpath or _movePos if curve too short.
923  */
924 NR::Point
925 SPCurve::second_point() const
927     g_return_val_if_fail(this != NULL, NR::Point(0, 0));
929     if (_end < 1) {
930         return _movePos;
931     }
933     NArtBpath *bpath = NULL;
934     if (_end < 2) {
935         bpath = _bpath;
936     } else {
937         bpath = _bpath + 1;
938     }
939     g_return_val_if_fail(bpath != NULL, NR::Point(0, 0));
941     debug_check("SPCurve::second_point", bpath->c(3) == _pathv.front()[0].finalPoint() );
943     return bpath->c(3);
946 /**
947  * Return the second-last point of last subpath or _movePos if curve too short.
948  */
949 NR::Point
950 SPCurve::penultimate_point() const
952     g_return_val_if_fail(this != NULL, NR::Point(0, 0));
954     if (_end < 2) {
955         return _movePos;
956     }
958     NArtBpath *const bpath = _bpath + _end - 2;
959     g_return_val_if_fail(bpath != NULL, NR::Point(0, 0));
960     
961     Geom::Point p(NR_HUGE, NR_HUGE);
962     Geom::Curve const& back = _pathv.back().back();
963     if (_pathv.back().closed()) {
964         p = back.finalPoint();
965     } else {
966         p = back.initialPoint();
967     }
969     debug_check("SPCurve::penultimate_point", bpath->c(3) == p );
970     return bpath->c(3);
973 /**
974  * 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) ?
975  */
976 NR::Point
977 SPCurve::last_point() const
979     NArtBpath const * bpath = last_bpath();
980     g_return_val_if_fail(bpath != NULL, NR::Point(0, 0));
981     if (is_empty())
982         return NR::Point(0, 0);
984     debug_check("SPCurve::last_point", bpath->c(3) == _pathv.back().finalPoint() );
985     return bpath->c(3);
986     // return from_2geom( _pathv.back().finalPoint() );
989 inline static bool
990 is_moveto(NRPathcode const c)
992     return c == NR_MOVETO || c == NR_MOVETO_OPEN;
995 /**
996  * Returns a *new* \a curve but drawn in the opposite direction.
997  * Should result in the same shape, but
998  * with all its markers drawn facing the other direction.
999  * Reverses the order of subpaths as well
1000  * 2GEOMified
1001  **/
1002 SPCurve *
1003 SPCurve::create_reverse() const
1005     /* We need at least moveto, curveto, end. */
1006     g_return_val_if_fail(_end - _substart > 1, NULL);
1008     NArtBpath const *be = _bpath + _end - 1;
1010     g_assert(is_moveto(_bpath[_substart].code));
1011     g_assert(is_moveto(_bpath[0].code));
1012     g_assert((be+1)->code == NR_END);
1014     SPCurve  *new_curve = new SPCurve(_length);
1015     new_curve->moveto(be->c(3));
1017     for (NArtBpath const *bp = be; ; --bp) {
1018         switch (bp->code) {
1019             case NR_MOVETO:
1020                 g_assert(new_curve->_bpath[new_curve->_substart].code == NR_MOVETO_OPEN);
1021                 new_curve->_bpath[new_curve->_substart].code = NR_MOVETO;
1022                 /* FALL-THROUGH */
1023             case NR_MOVETO_OPEN:
1024                 if (bp == _bpath) {
1025                     return new_curve;
1026                 }
1027                 new_curve->moveto((bp-1)->c(3));
1028                 break;
1030             case NR_LINETO:
1031                 new_curve->lineto((bp-1)->c(3));
1032                 break;
1034             case NR_CURVETO:
1035                 new_curve->curveto(bp->c(2), bp->c(1), (bp-1)->c(3));
1036                 break;
1038             default:
1039                 g_assert_not_reached();
1040         }
1041     }
1043     new_curve->_pathv = Geom::reverse_paths_and_order(_pathv);
1045     debug_check("SPCurve::create_reverse", new_curve);
1048 /**
1049  * Append \a curve2 to \a this.
1050  * If \a use_lineto is false, simply add all paths in \a curve2 to \a this;
1051  * if \a use_lineto is true, combine \a this's last path and \a curve2's first path and add the rest of the paths in \a curve2 to \a this.
1052  * 2GEOMified
1053  */
1054 void
1055 SPCurve::append(SPCurve const *curve2,
1056                 bool use_lineto)
1058     g_return_if_fail(this != NULL);
1059     g_return_if_fail(curve2 != NULL);
1061     if (curve2->is_empty())
1062         return;
1063     if (curve2->_end < 1)
1064         return;
1066     NArtBpath const *bs = curve2->_bpath;
1068     bool closed = this->_closed;
1070     for (NArtBpath const *bp = bs; bp->code != NR_END; bp++) {
1071         switch (bp->code) {
1072             case NR_MOVETO_OPEN:
1073                 if (use_lineto && _hascpt) {
1074                     lineto(bp->x3, bp->y3);
1075                     use_lineto = FALSE;
1076                 } else {
1077                     if (closed) closepath();
1078                     moveto(bp->x3, bp->y3);
1079                 }
1080                 closed = false;
1081                 break;
1083             case NR_MOVETO:
1084                 if (use_lineto && _hascpt) {
1085                     lineto(bp->x3, bp->y3);
1086                     use_lineto = FALSE;
1087                 } else {
1088                     if (closed) closepath();
1089                     moveto(bp->x3, bp->y3);
1090                 }
1091                 closed = true;
1092                 break;
1094             case NR_LINETO:
1095                 lineto(bp->x3, bp->y3);
1096                 break;
1098             case NR_CURVETO:
1099                 curveto(bp->x1, bp->y1, bp->x2, bp->y2, bp->x3, bp->y3);
1100                 break;
1102             case NR_END:
1103                 g_assert_not_reached();
1104         }
1105     }
1107     if (closed) {
1108         closepath();
1109     }
1111     debug_check("SPCurve::append", this);
1113     /* 2GEOM code when code above is removed:
1114     if (use_lineto) {
1115         Geom::PathVector::const_iterator it = curve2->_pathv.begin();
1116         if ( ! _pathv.empty() ) {
1117             Geom::Path & lastpath = _pathv.back();
1118             lastpath.appendNew<Geom::LineSegment>( (*it).initialPoint() );
1119             lastpath.append( (*it) );
1120         } else {
1121             _pathv.push_back( (*it) );
1122         }
1124         for (it++; it != curve2->_pathv.end(); it++) {
1125             _pathv.push_back( (*it) );
1126         }
1127     } else {
1128         for (Geom::PathVector::const_iterator it = curve2->_pathv.begin(); it != curve2->_pathv.end(); it++) {
1129             _pathv.push_back( (*it) );
1130         }
1131     }
1132     */
1135 /**
1136  * Append \a c1 to \a this with possible fusing of close endpoints.
1137  * 2GEOMproof. Needs to be recoded when NArtBpath is no longer there. Right now, it applies the same changes to bpath and pathv depending on bpath
1138  */
1139 SPCurve *
1140 SPCurve::append_continuous(SPCurve const *c1, gdouble tolerance)
1142     g_return_val_if_fail(this != NULL, NULL);
1143     g_return_val_if_fail(c1 != NULL, NULL);
1144     g_return_val_if_fail(!_closed, NULL);
1145     g_return_val_if_fail(!c1->_closed, NULL);
1147     if (c1->_end < 1) {
1148         return this;
1149     }
1151     debug_check("SPCurve::append_continuous 11", this);
1153     NArtBpath const *be = last_bpath();
1154     if (be) {
1155         NArtBpath const *bs = c1->first_bpath();
1156         if ( bs
1157              && ( fabs( bs->x3 - be->x3 ) <= tolerance )
1158              && ( fabs( bs->y3 - be->y3 ) <= tolerance ) )
1159         {
1160             /** \todo
1161              * fixme: Strictly we mess in case of multisegment mixed
1162              * open/close curves
1163              */
1164             bool closed = false;
1165             for (bs = bs + 1; bs->code != NR_END; bs++) {
1166                 switch (bs->code) {
1167                     case NR_MOVETO_OPEN:
1168                         if (closed) closepath();
1169                         moveto(bs->x3, bs->y3);
1170                         closed = false;
1171                         break;
1172                     case NR_MOVETO:
1173                         if (closed) closepath();
1174                         moveto(bs->x3, bs->y3);
1175                         closed = true;
1176                         break;
1177                     case NR_LINETO:
1178                         lineto(bs->x3, bs->y3);
1179                         break;
1180                     case NR_CURVETO:
1181                         curveto(bs->x1, bs->y1, bs->x2, bs->y2, bs->x3, bs->y3);
1182                         break;
1183                     case NR_END:
1184                         g_assert_not_reached();
1185                 }
1186             }
1187         } else {
1188             append(c1, TRUE);
1189         }
1190     } else {
1191         append(c1, TRUE);
1192     }
1194     debug_check("SPCurve::append_continuous", this);
1196     return this;
1199 /**
1200  * Remove last segment of curve.
1201  * (Only used once in /src/pen-context.cpp)
1202  * 2GEOMified
1203  */
1204 void
1205 SPCurve::backspace()
1207     g_return_if_fail(this != NULL);
1209     if ( is_empty() )
1210         return;
1212     if (_end > 0) {
1213         _end -= 1;
1214         if (_end > 0) {
1215             NArtBpath *bp = _bpath + _end - 1;
1216             if ((bp->code == NR_MOVETO)     ||
1217                 (bp->code == NR_MOVETO_OPEN)  )
1218             {
1219                 _hascpt = true;
1220                 _posSet = true;
1221                 _closed = false;
1222                 _movePos = bp->c(3);
1223                 _end -= 1;
1224             }
1225         }
1226         _bpath[_end].code = NR_END;
1227     }
1229     if ( !_pathv.back().empty() ) {
1230         _pathv.back().erase_last();
1231         _pathv.back().close(false);
1232     }
1234     debug_check("SPCurve::backspace", this);
1237 /* Private methods */
1239 /**
1240  * Returns index of first NR_END bpath in array.
1241  */
1242 static unsigned sp_bpath_length(NArtBpath const bpath[])
1244     g_return_val_if_fail(bpath != NULL, FALSE);
1246     unsigned ret = 0;
1247     while ( bpath[ret].code != NR_END ) {
1248         ++ret;
1249     }
1250     ++ret;
1252     return ret;
1255 /**
1256  * \brief
1257  *
1258  * \todo
1259  * fixme: this is bogus -- it doesn't check for nr_moveto, which will indicate
1260  * a closing of the subpath it's nonsense to talk about a path as a whole
1261  * being closed, although maybe someone would want that for some other reason?
1262  * Oh, also, if the bpath just ends, then it's *open*.  I hope nobody is using
1263  * this code for anything.
1264  */
1265 static bool sp_bpath_closed(NArtBpath const bpath[])
1267     g_return_val_if_fail(bpath != NULL, FALSE);
1269     for (NArtBpath const *bp = bpath; bp->code != NR_END; bp++) {
1270         if (bp->code == NR_MOVETO_OPEN) {
1271             return false;
1272         }
1273     }
1275     return true;
1278 /**
1279  * Returns length of bezier segment.
1280  */
1281 static double
1282 bezier_len(NR::Point const &c0,
1283            NR::Point const &c1,
1284            NR::Point const &c2,
1285            NR::Point const &c3,
1286            double const threshold)
1288     /** \todo
1289      * The SVG spec claims that a closed form exists, but for the moment I'll
1290      * use a stupid algorithm.
1291      */
1292     double const lbound = L2( c3 - c0 );
1293     double const ubound = L2( c1 - c0 ) + L2( c2 - c1 ) + L2( c3 - c2 );
1294     double ret;
1295     if ( ubound - lbound <= threshold ) {
1296         ret = .5 * ( lbound + ubound );
1297     } else {
1298         NR::Point const a1( .5 * ( c0 + c1 ) );
1299         NR::Point const b2( .5 * ( c2 + c3 ) );
1300         NR::Point const c12( .5 * ( c1 + c2 ) );
1301         NR::Point const a2( .5 * ( a1 + c12 ) );
1302         NR::Point const b1( .5 * ( c12 + b2 ) );
1303         NR::Point const midpoint( .5 * ( a2 + b1 ) );
1304         double const rec_threshold = .625 * threshold;
1305         ret = bezier_len(c0, a1, a2, midpoint, rec_threshold) + bezier_len(midpoint, b1, b2, c3, rec_threshold);
1306         if (!(lbound - 1e-2 <= ret && ret <= ubound + 1e-2)) {
1307             using NR::X; using NR::Y;
1308             g_warning("ret=%f outside of expected bounds [%f, %f] for {(%.0f %.0f) (%.0f %.0f) (%.0f %.0f) (%.0f %.0f)}",
1309                       ret, lbound, ubound, c0[X], c0[Y], c1[X], c1[Y], c2[X], c2[Y], c3[X], c3[Y]);
1310         }
1311     }
1312     return ret;
1315 /**
1316  * Returns total length of curve, excluding length of closepath segments.
1317  */
1318 double
1319 sp_curve_distance_including_space(SPCurve const *const curve, double seg2len[])
1321     g_return_val_if_fail(curve != NULL, 0.);
1323     double ret = 0.0;
1325     if ( curve->_bpath->code == NR_END ) {
1326         return ret;
1327     }
1329     NR::Point prev(curve->_bpath->c(3));
1330     for (guint i = 1; i < curve->_end; ++i) {
1331         NArtBpath &p = curve->_bpath[i];
1332         double seg_len = 0;
1333         switch (p.code) {
1334             case NR_MOVETO_OPEN:
1335             case NR_MOVETO:
1336             case NR_LINETO:
1337                 seg_len = L2(p.c(3) - prev);
1338                 break;
1340             case NR_CURVETO:
1341                 seg_len = bezier_len(prev, p.c(1), p.c(2), p.c(3), 1.);
1342                 break;
1344             case NR_END:
1345                 return ret;
1346         }
1347         seg2len[i - 1] = seg_len;
1348         ret += seg_len;
1349         prev = p.c(3);
1350     }
1351     g_assert(!(ret < 0));
1352     return ret;
1355 /**
1356  * Like sp_curve_distance_including_space(), but ensures that the
1357  * result >= 1e-18:  uses 1 per segment if necessary.
1358  */
1359 double
1360 sp_curve_nonzero_distance_including_space(SPCurve const *const curve, double seg2len[])
1362     double const real_dist(sp_curve_distance_including_space(curve, seg2len));
1363     if (real_dist >= 1e-18) {
1364         return real_dist;
1365     } else {
1366         unsigned const nSegs = SP_CURVE_LENGTH(curve) - 1;
1367         for (unsigned i = 0; i < nSegs; ++i) {
1368             seg2len[i] = 1.;
1369         }
1370         return (double) nSegs;
1371     }
1374 /**
1375  * 2GEOMified
1376  */
1377 void
1378 SPCurve::stretch_endpoints(NR::Point const &new_p0, NR::Point const &new_p1)
1380     if (is_empty()) {
1381         return;
1382     }
1383     g_assert(unsigned(SP_CURVE_LENGTH(this)) + 1 == sp_bpath_length(_bpath));
1384     unsigned const nSegs = SP_CURVE_LENGTH(this) - 1;
1385     g_assert(nSegs != 0);
1386     double *const seg2len = new double[nSegs];
1387     double const tot_len = sp_curve_nonzero_distance_including_space(this, seg2len);
1388     NR::Point const offset0( new_p0 - first_point() );
1389     NR::Point const offset1( new_p1 - last_point() );
1390     _bpath->setC(3, new_p0);
1391     double begin_dist = 0.;
1392     for (unsigned si = 0; si < nSegs; ++si) {
1393         double const end_dist = begin_dist + seg2len[si];
1394         NArtBpath &p = _bpath[1 + si];
1395         switch (p.code) {
1396             case NR_LINETO:
1397             case NR_MOVETO:
1398             case NR_MOVETO_OPEN:
1399                 p.setC(3, p.c(3) + NR::Lerp(end_dist / tot_len, offset0, offset1));
1400                 break;
1402             case NR_CURVETO:
1403                 for (unsigned ci = 1; ci <= 3; ++ci) {
1404                     p.setC(ci, p.c(ci) + Lerp((begin_dist + ci * seg2len[si] / 3.) / tot_len, offset0, offset1));
1405                 }
1406                 break;
1408             default:
1409                 g_assert_not_reached();
1410         }
1412         begin_dist = end_dist;
1413     }
1414     g_assert(L1(_bpath[nSegs].c(3) - new_p1) < 1.);
1415     /* Explicit set for better numerical properties. */
1416     _bpath[nSegs].setC(3, new_p1);
1417     delete [] seg2len;
1419     Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2 = _pathv.front().toPwSb();
1420     Geom::Piecewise<Geom::SBasis> arclength = Geom::arcLengthSb(pwd2);
1421     if ( arclength.lastValue() <= 0 ) {
1422         g_error("SPCurve::stretch_endpoints - arclength <= 0");
1423         throw;
1424     }
1425     arclength *= 1./arclength.lastValue();
1426     Geom::Point const A( to_2geom(offset0) );
1427     Geom::Point const B( to_2geom(offset1) );
1428     Geom::Piecewise<Geom::SBasis> offsetx = (arclength*-1.+1)*A[0] + arclength*B[0];
1429     Geom::Piecewise<Geom::SBasis> offsety = (arclength*-1.+1)*A[1] + arclength*B[1];
1430     Geom::Piecewise<Geom::D2<Geom::SBasis> > offsetpath = Geom::sectionize( Geom::D2<Geom::Piecewise<Geom::SBasis> >(offsetx, offsety) );
1431     pwd2 += offsetpath;
1432     _pathv = Geom::path_from_piecewise( pwd2, 0.001 );
1434     debug_check("SPCurve::stretch_endpoints", this);
1437 /**
1438  *  sets start of first path to new_p0, and end of first path to  new_p1
1439  * 2GEOMified
1440  */
1441 void
1442 SPCurve::move_endpoints(NR::Point const &new_p0, NR::Point const &new_p1)
1444     if (is_empty()) {
1445         return;
1446     }
1447     unsigned const nSegs = SP_CURVE_LENGTH(this) - 1;
1448     g_assert(nSegs != 0);
1450     _bpath->setC(3, new_p0);
1451     _bpath[nSegs].setC(3, new_p1);
1453     _pathv.front().setInitial(to_2geom(new_p0));
1454     _pathv.front().setFinal(to_2geom(new_p1));
1456     debug_check("SPCurve::move_endpoints", this);
1459 /**
1460  * returns the number of nodes in a path, used for statusbar text when selecting an spcurve.
1461  * 2GEOMified
1462  */
1463 guint
1464 SPCurve::nodes_in_path() const
1466     gint r = _end;
1467     gint i = _length - 1;
1468     if (i > r) i = r; // sometimes after switching from node editor length is wrong, e.g. f6 - draw - f2 - tab - f1, this fixes it
1469     for (; i >= 0; i --)
1470         if (_bpath[i].code == NR_MOVETO)
1471             r --;
1473     guint nr = 0;
1474     for(Geom::PathVector::const_iterator it = _pathv.begin(); it != _pathv.end(); ++it) {
1475         nr += (*it).size();
1477         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
1478     }
1480     debug_check("SPCurve::nodes_in_path", r == (gint)nr);
1482     return r;
1485 /**
1486  *  Adds p to the last point (and last handle if present) of the last path
1487  * 2GEOMified
1488  */
1489 void
1490 SPCurve::last_point_additive_move(Geom::Point const & p)
1492     if (is_empty()) {
1493         return;
1494     }
1495     if (_end == 0) {
1496         return;
1497     }
1498     NArtBpath * path = _bpath + _end - 1;
1500     if (path->code == NR_CURVETO) {
1501         path->x2 += p[Geom::X];
1502         path->y2 += p[Geom::Y];
1503     }
1504     path->x3 += p[Geom::X];
1505     path->y3 += p[Geom::Y];
1507     _pathv.back().setFinal( _pathv.back().finalPoint() + p );
1509     // Move handle as well when the last segment is a cubic bezier segment:
1510     // TODO: what to do for quadratic beziers?
1511     if ( Geom::CubicBezier const *lastcube = dynamic_cast<Geom::CubicBezier const *>(&_pathv.back().back()) ) {
1512         Geom::CubicBezier newcube( *lastcube );
1513         newcube.setPoint(2, newcube[2] + p);
1514         _pathv.back().replace( --_pathv.back().end(), newcube );
1515     }
1517     debug_check("SPCurve::last_point_additive_move", this);
1520 /*
1521   Local Variables:
1522   mode:c++
1523   c-file-style:"stroustrup"
1524   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1525   indent-tabs-mode:nil
1526   fill-column:99
1527   End:
1528 */
1529 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :