Code

struct SPCurve => class SPCurve
[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 <cstring>
29 #include <string>
31 static unsigned sp_bpath_length(NArtBpath const bpath[]);
32 static bool sp_bpath_closed(NArtBpath const bpath[]);
34 /* Constructors */
36 /**
37  * The returned curve's state is as if SPCurve::reset has just been called on it.
38  * \param length Initial number of NArtBpath elements allocated for bpath (including NR_END
39  *    element).
40  */
41 SPCurve::SPCurve(gint length)
42   : refcount(1),
43     _bpath(NULL),
44     end(0),
45     length(length),
46     substart(0),
47     hascpt(false),
48     posSet(false),
49     moving(false),
50     closed(false)
51 {
52     if (length <= 0) {
53         g_error("SPCurve::SPCurve called with invalid length parameter");
54         throw;
55     }
57     _bpath = g_new(NArtBpath, length);
58     _bpath->code = NR_END;
59 }
61 SPCurve *
62 SPCurve::new_from_foreign_bpath(NArtBpath const *bpath)
63 {
64     g_return_val_if_fail(bpath != NULL, NULL);
66     NArtBpath *new_bpath;
67     unsigned const len = sp_bpath_length(bpath);
68     new_bpath = g_new(NArtBpath, len);
69     memcpy(new_bpath, bpath, len * sizeof(NArtBpath));
71     SPCurve *curve = new SPCurve();
73     curve->_bpath = new_bpath;
74     curve->length = len;
75     curve->end = curve->length - 1;
76     gint i = curve->end;
77     for (; i > 0; i--)
78         if ((curve->_bpath[i].code == NR_MOVETO) ||
79             (curve->_bpath[i].code == NR_MOVETO_OPEN))
80             break;
81     curve->substart = i;
82     curve->closed = sp_bpath_closed(new_bpath);
84     return curve;
85 }
87 /**
88  * Convert NArtBpath object to SPCurve object.
89  *
90  * \return new SPCurve, or NULL if the curve was not created for some reason.
91  */
92 SPCurve *
93 SPCurve::new_from_bpath(NArtBpath *bpath)
94 {
95     g_return_val_if_fail(bpath != NULL, NULL);
97     SPCurve *curve = SPCurve::new_from_foreign_bpath(bpath);
98     g_free(bpath);
99     return curve;
102 SPCurve *
103 SPCurve::new_from_rect(NR::Maybe<NR::Rect> const &rect)
105     g_return_val_if_fail(rect, NULL);
107     SPCurve *c =  new SPCurve();
109     NR::Point p = rect->corner(0);
110     c->moveto(p);
112     for (int i=3; i>=0; i--) {
113         c->lineto(rect->corner(i));
114     }
115     c->closepath_current();
117     return c;
120 SPCurve::~SPCurve()
122     if (_bpath) {
123         g_free(_bpath);
124         _bpath = NULL;
125     }
128 /* Methods */
130 /**
131  * Increase refcount of curve.
132  *
133  * \todo should this be shared with other refcounting code?
134  */
135 SPCurve *
136 SPCurve::ref()
138     g_return_val_if_fail(this != NULL, NULL);
140     refcount += 1;
142     return this;
145 /**
146  * Decrease refcount of curve, with possible destruction.
147  *
148  * \todo should this be shared with other refcounting code?
149  */
150 SPCurve *
151 SPCurve::unref()
153     g_return_val_if_fail(this != NULL, NULL);
155     refcount -= 1;
157     if (refcount < 1) {
158         if (_bpath) {
159             g_free(_bpath);
160             _bpath = NULL;
161         }
162         delete this;
163     }
165     return NULL;
168 /**
169  * Add space for more paths in curve.
170  */
171 static void
172 sp_curve_ensure_space(SPCurve *curve, gint space)
174     g_return_if_fail(curve != NULL);
175     g_return_if_fail(space > 0);
177     if (curve->end + space < curve->length)
178         return;
180     if (space < SP_CURVE_LENSTEP)
181         space = SP_CURVE_LENSTEP;
183     curve->_bpath = g_renew(NArtBpath, curve->_bpath, curve->length + space);
185     curve->length += space;
188 /**
189  * Create new curve from its own bpath array.
190  */
191 SPCurve *
192 SPCurve::copy() const
194     g_return_val_if_fail(this != NULL, NULL);
196     return SPCurve::new_from_foreign_bpath(_bpath);
199 /**
200  * Return new curve that is the concatenation of all curves in list.
201  */
202 SPCurve *
203 SPCurve::concat(GSList const *list)
205     g_return_val_if_fail(list != NULL, NULL);
207     gint length = 0;
209     for (GSList const *l = list; l != NULL; l = l->next) {
210         SPCurve *c = (SPCurve *) l->data;
211         length += c->end;
212     }
214     SPCurve *new_curve = new SPCurve(length + 1);
216     NArtBpath *bp = new_curve->_bpath;
218     for (GSList const *l = list; l != NULL; l = l->next) {
219         SPCurve *c = (SPCurve *) l->data;
220         memcpy(bp, c->_bpath, c->end * sizeof(NArtBpath));
221         bp += c->end;
222     }
224     bp->code = NR_END;
226     new_curve->end = length;
227     gint i;
228     for (i = new_curve->end; i > 0; i--) {
229         if ((new_curve->_bpath[i].code == NR_MOVETO)     ||
230             (new_curve->_bpath[i].code == NR_MOVETO_OPEN)  )
231             break;
232     }
234     new_curve->substart = i;
236     return new_curve;
239 /**
240  * Returns a list of new curves corresponding to the subpaths in \a curve.
241  */
242 GSList *
243 SPCurve::split() const
245     g_return_val_if_fail(this != NULL, NULL);
247     gint p = 0;
248     GSList *l = NULL;
250     while (p < end) {
251         gint i = 1;
252         while ((_bpath[p + i].code == NR_LINETO) ||
253                (_bpath[p + i].code == NR_CURVETO))
254             i++;
255         SPCurve *new_curve = new SPCurve(i + 1);
256         memcpy(new_curve->_bpath, _bpath + p, i * sizeof(NArtBpath));
257         new_curve->end = i;
258         new_curve->_bpath[i].code = NR_END;
259         new_curve->substart = 0;
260         new_curve->closed = (new_curve->_bpath->code == NR_MOVETO);
261         new_curve->hascpt = (new_curve->_bpath->code == NR_MOVETO_OPEN);
262         l = g_slist_prepend(l, new_curve);
263         p += i;
264     }
266     return l;
269 /**
270  * Transform all paths in curve, template helper.
271  */
272 template<class M>
273 static void
274 tmpl_curve_transform(SPCurve *const curve, M const &m)
276     g_return_if_fail(curve != NULL);
278     for (gint i = 0; i < curve->end; i++) {
279         NArtBpath *p = curve->_bpath + i;
280         switch (p->code) {
281             case NR_MOVETO:
282             case NR_MOVETO_OPEN:
283             case NR_LINETO: {
284                 p->setC(3, p->c(3) * m);
285                 break;
286             }
287             case NR_CURVETO:
288                 for (unsigned i = 1; i <= 3; ++i) {
289                     p->setC(i, p->c(i) * m);
290                 }
291                 break;
292             default:
293                 g_warning("Illegal pathcode %d", p->code);
294                 break;
295         }
296     }
299 /**
300  * Transform all paths in curve using matrix.
301  */
302 void
303 SPCurve::transform(NR::Matrix const &m)
305     tmpl_curve_transform<NR::Matrix>(this, m);
308 /**
309  * Transform all paths in curve using NR::translate.
310  */
311 void
312 SPCurve::transform(NR::translate const &m)
314     tmpl_curve_transform<NR::translate>(this, m);
317 /**
318  * Set curve to empty curve.
319  */
320 void
321 SPCurve::reset()
323     g_return_if_fail(this != NULL);
325     _bpath->code = NR_END;
326     end = 0;
327     substart = 0;
328     hascpt = false;
329     posSet = false;
330     moving = false;
331     closed = false;
334 /* Several consecutive movetos are ALLOWED */
336 /**
337  * Calls SPCurve::moveto() with point made of given coordinates.
338  */
339 void
340 SPCurve::moveto(gdouble x, gdouble y)
342     moveto(NR::Point(x, y));
345 /**
346  * Perform a moveto to a point, thus starting a new subpath.
347  */
348 void
349 SPCurve::moveto(NR::Point const &p)
351     g_return_if_fail(this != NULL);
352     g_return_if_fail(!moving);
354     substart = end;
355     hascpt = true;
356     posSet = true;
357     movePos = p;
360 /**
361  * Calls SPCurve::lineto() with a point's coordinates.
362  */
363 void
364 SPCurve::lineto(NR::Point const &p)
366     lineto(p[NR::X], p[NR::Y]);
369 /**
370  * Adds a line to the current subpath.
371  */
372 void
373 SPCurve::lineto(gdouble x, gdouble y)
375     g_return_if_fail(this != NULL);
376     g_return_if_fail(hascpt);
378     if (moving) {
379         /* fix endpoint */
380         g_return_if_fail(!posSet);
381         g_return_if_fail(end > 1);
382         NArtBpath *bp = _bpath + end - 1;
383         g_return_if_fail(bp->code == NR_LINETO);
384         bp->x3 = x;
385         bp->y3 = y;
386         moving = false;
387         return;
388     }
390     if (posSet) {
391         /* start a new segment */
392         sp_curve_ensure_space(this, 2);
393         NArtBpath *bp = _bpath + end;
394         bp->code = NR_MOVETO_OPEN;
395         bp->setC(3, movePos);
396         bp++;
397         bp->code = NR_LINETO;
398         bp->x3 = x;
399         bp->y3 = y;
400         bp++;
401         bp->code = NR_END;
402         end += 2;
403         posSet = false;
404         closed = false;
405         return;
406     }
408     /* add line */
410     g_return_if_fail(end > 1);
411     sp_curve_ensure_space(this, 1);
412     NArtBpath *bp = _bpath + end;
413     bp->code = NR_LINETO;
414     bp->x3 = x;
415     bp->y3 = y;
416     bp++;
417     bp->code = NR_END;
418     end++;
421 /// Unused
422 void
423 SPCurve::lineto_moving(gdouble x, gdouble y)
425     g_return_if_fail(this != NULL);
426     g_return_if_fail(hascpt);
428     if (moving) {
429         /* change endpoint */
430         g_return_if_fail(!posSet);
431         g_return_if_fail(end > 1);
432         NArtBpath *bp = _bpath + end - 1;
433         g_return_if_fail(bp->code == NR_LINETO);
434         bp->x3 = x;
435         bp->y3 = y;
436         return;
437     }
439     if (posSet) {
440         /* start a new segment */
441         sp_curve_ensure_space(this, 2);
442         NArtBpath *bp = _bpath + end;
443         bp->code = NR_MOVETO_OPEN;
444         bp->setC(3, movePos);
445         bp++;
446         bp->code = NR_LINETO;
447         bp->x3 = x;
448         bp->y3 = y;
449         bp++;
450         bp->code = NR_END;
451         end += 2;
452         posSet = false;
453         moving = true;
454         closed = false;
455         return;
456     }
458     /* add line */
460     g_return_if_fail(end > 1);
461     sp_curve_ensure_space(this, 1);
462     NArtBpath *bp = _bpath + end;
463     bp->code = NR_LINETO;
464     bp->x3 = x;
465     bp->y3 = y;
466     bp++;
467     bp->code = NR_END;
468     end++;
469     moving = true;
472 /**
473  * Calls SPCurve::curveto() with coordinates of three points.
474  */
475 void
476 SPCurve::curveto(NR::Point const &p0, NR::Point const &p1, NR::Point const &p2)
478     using NR::X;
479     using NR::Y;
480     curveto( p0[X], p0[Y],
481              p1[X], p1[Y],
482              p2[X], p2[Y] );
485 /**
486  * Adds a bezier segment to the current subpath.
487  */
488 void
489 SPCurve::curveto(gdouble x0, gdouble y0, gdouble x1, gdouble y1, gdouble x2, gdouble y2)
491     g_return_if_fail(this != NULL);
492     g_return_if_fail(hascpt);
493     g_return_if_fail(!moving);
495     if (posSet) {
496         /* start a new segment */
497         sp_curve_ensure_space(this, 2);
498         NArtBpath *bp = _bpath + end;
499         bp->code = NR_MOVETO_OPEN;
500         bp->setC(3, movePos);
501         bp++;
502         bp->code = NR_CURVETO;
503         bp->x1 = x0;
504         bp->y1 = y0;
505         bp->x2 = x1;
506         bp->y2 = y1;
507         bp->x3 = x2;
508         bp->y3 = y2;
509         bp++;
510         bp->code = NR_END;
511         end += 2;
512         posSet = false;
513         closed = false;
514         return;
515     }
517     /* add curve */
519     g_return_if_fail(end > 1);
520     sp_curve_ensure_space(this, 1);
521     NArtBpath *bp = _bpath + end;
522     bp->code = NR_CURVETO;
523     bp->x1 = x0;
524     bp->y1 = y0;
525     bp->x2 = x1;
526     bp->y2 = y1;
527     bp->x3 = x2;
528     bp->y3 = y2;
529     bp++;
530     bp->code = NR_END;
531     end++;
534 /**
535  * Close current subpath by possibly adding a line between start and end.
536  */
537 void
538 SPCurve::closepath()
540     g_return_if_fail(this != NULL);
541     g_return_if_fail(hascpt);
542     g_return_if_fail(!posSet);
543     g_return_if_fail(!moving);
544     g_return_if_fail(!closed);
545     /* We need at least moveto, curveto, end. */
546     g_return_if_fail(end - substart > 1);
548     {
549         NArtBpath *bs = _bpath + substart;
550         NArtBpath *be = _bpath + end - 1;
552         if (bs->c(3) != be->c(3)) {
553             lineto(bs->c(3));
554             bs = _bpath + substart;
555         }
557         bs->code = NR_MOVETO;
558     }
559     closed = true;
561     for (NArtBpath const *bp = _bpath; bp->code != NR_END; bp++) {
562         /** \todo
563          * effic: Maintain a count of NR_MOVETO_OPEN's (e.g. instead of
564          * the closed boolean).
565          */
566         if (bp->code == NR_MOVETO_OPEN) {
567             closed = false;
568             break;
569         }
570     }
572     hascpt = false;
575 /** Like SPCurve::closepath() but sets the end point of the current
576     command to the subpath start point instead of adding a new lineto.
578     Used for freehand drawing when the user draws back to the start point.
579 **/
580 void
581 SPCurve::closepath_current()
583     g_return_if_fail(this != NULL);
584     g_return_if_fail(hascpt);
585     g_return_if_fail(!posSet);
586     g_return_if_fail(!closed);
587     /* We need at least moveto, curveto, end. */
588     g_return_if_fail(end - substart > 1);
590     {
591         NArtBpath *bs = _bpath + substart;
592         NArtBpath *be = _bpath + end - 1;
594         be->x3 = bs->x3;
595         be->y3 = bs->y3;
597         bs->code = NR_MOVETO;
598     }
599     closed = true;
601     for (NArtBpath const *bp = _bpath; bp->code != NR_END; bp++) {
602         /** \todo
603          * effic: Maintain a count of NR_MOVETO_OPEN's (e.g. instead of
604          * the closed boolean).
605          */
606         if (bp->code == NR_MOVETO_OPEN) {
607             closed = false;
608             break;
609         }
610     }
612     hascpt = false;
613     moving = false;
616 /**
617  * True if no paths are in curve.
618  */
619 bool
620 SPCurve::is_empty() const
622     g_return_val_if_fail(this != NULL, TRUE);
624     return (_bpath->code == NR_END);
627 /**
628  * Return last subpath or NULL.
629  */
630 NArtBpath *
631 SPCurve::last_bpath() const
633     g_return_val_if_fail(this != NULL, NULL);
635     if (end == 0) {
636         return NULL;
637     }
639     return _bpath + end - 1;
642 /**
643  * Return first subpath or NULL.
644  */
645 NArtBpath *
646 SPCurve::first_bpath() const
648     g_return_val_if_fail(this != NULL, NULL);
650     if (end == 0) {
651         return NULL;
652     }
654     return _bpath;
657 /**
658  * Return first point of first subpath or (0,0).
659  */
660 NR::Point
661 SPCurve::first_point() const
663     NArtBpath *const bpath = first_bpath();
664     g_return_val_if_fail(bpath != NULL, NR::Point(0, 0));
665     return bpath->c(3);
668 /**
669  * Return the second point of first subpath or movePos if curve too short.
670  */
671 NR::Point
672 SPCurve::second_point() const
674     g_return_val_if_fail(this != NULL, NR::Point(0, 0));
676     if (end < 1) {
677         return movePos;
678     }
680     NArtBpath *bpath = NULL;
681     if (end < 2) {
682         bpath = _bpath;
683     } else {
684         bpath = _bpath + 1;
685     }
686     g_return_val_if_fail(bpath != NULL, NR::Point(0, 0));
687     return bpath->c(3);
690 /**
691  * Return the second-last point of last subpath or movePos if curve too short.
692  */
693 NR::Point
694 SPCurve::penultimate_point() const
696     g_return_val_if_fail(this != NULL, NR::Point(0, 0));
698     if (end < 2) {
699         return movePos;
700     }
702     NArtBpath *const bpath = _bpath + end - 2;
703     g_return_val_if_fail(bpath != NULL, NR::Point(0, 0));
704     return bpath->c(3);
707 /**
708  * Return last point of last subpath or (0,0).
709  */
710 NR::Point
711 SPCurve::last_point() const
713     NArtBpath *const bpath = last_bpath();
714     g_return_val_if_fail(bpath != NULL, NR::Point(0, 0));
715     return bpath->c(3);
718 inline static bool
719 is_moveto(NRPathcode const c)
721     return c == NR_MOVETO || c == NR_MOVETO_OPEN;
724 /**
725  * Returns \a curve but drawn in the opposite direction.
726  * Should result in the same shape, but
727  * with all its markers drawn facing the other direction.
728  **/
729 SPCurve *
730 SPCurve::reverse() const
732     /* We need at least moveto, curveto, end. */
733     g_return_val_if_fail(end - substart > 1, NULL);
735     NArtBpath const *be = _bpath + end - 1;
737     g_assert(is_moveto(_bpath[substart].code));
738     g_assert(is_moveto(_bpath[0].code));
739     g_assert((be+1)->code == NR_END);
741     SPCurve  *new_curve = new SPCurve(length);
742     new_curve->moveto(be->c(3));
744     for (NArtBpath const *bp = be; ; --bp) {
745         switch (bp->code) {
746             case NR_MOVETO:
747                 g_assert(new_curve->_bpath[new_curve->substart].code == NR_MOVETO_OPEN);
748                 new_curve->_bpath[new_curve->substart].code = NR_MOVETO;
749                 /* FALL-THROUGH */
750             case NR_MOVETO_OPEN:
751                 if (bp == _bpath) {
752                     return new_curve;
753                 }
754                 new_curve->moveto((bp-1)->c(3));
755                 break;
757             case NR_LINETO:
758                 new_curve->lineto((bp-1)->c(3));
759                 break;
761             case NR_CURVETO:
762                 new_curve->curveto(bp->c(2), bp->c(1), (bp-1)->c(3));
763                 break;
765             default:
766                 g_assert_not_reached();
767         }
768     }
771 /**
772  * Append \a curve2 to \a curve.
773  */
774 void
775 SPCurve::append(SPCurve const *curve2,
776                 bool use_lineto)
778     g_return_if_fail(this != NULL);
779     g_return_if_fail(curve2 != NULL);
781     if (curve2->end < 1)
782         return;
784     NArtBpath const *bs = curve2->_bpath;
786     bool _closed = this->closed;
788     for (NArtBpath const *bp = bs; bp->code != NR_END; bp++) {
789         switch (bp->code) {
790             case NR_MOVETO_OPEN:
791                 if (use_lineto && hascpt) {
792                     lineto(bp->x3, bp->y3);
793                     use_lineto = FALSE;
794                 } else {
795                     if (_closed) closepath();
796                     moveto(bp->x3, bp->y3);
797                 }
798                 _closed = false;
799                 break;
801             case NR_MOVETO:
802                 if (use_lineto && hascpt) {
803                     lineto(bp->x3, bp->y3);
804                     use_lineto = FALSE;
805                 } else {
806                     if (_closed) closepath();
807                     moveto(bp->x3, bp->y3);
808                 }
809                 _closed = true;
810                 break;
812             case NR_LINETO:
813                 lineto(bp->x3, bp->y3);
814                 break;
816             case NR_CURVETO:
817                 curveto(bp->x1, bp->y1, bp->x2, bp->y2, bp->x3, bp->y3);
818                 break;
820             case NR_END:
821                 g_assert_not_reached();
822         }
823     }
825     if (_closed) {
826         closepath();
827     }
830 /**
831  * Append \a c1 to \a this with possible fusing of close endpoints.
832  */
833 SPCurve *
834 SPCurve::append_continuous(SPCurve const *c1, gdouble tolerance)
836     g_return_val_if_fail(this != NULL, NULL);
837     g_return_val_if_fail(c1 != NULL, NULL);
838     g_return_val_if_fail(!closed, NULL);
839     g_return_val_if_fail(!c1->closed, NULL);
841     if (c1->end < 1) {
842         return this;
843     }
845     NArtBpath *be = last_bpath();
846     if (be) {
847         NArtBpath const *bs = c1->first_bpath();
848         if ( bs
849              && ( fabs( bs->x3 - be->x3 ) <= tolerance )
850              && ( fabs( bs->y3 - be->y3 ) <= tolerance ) )
851         {
852             /** \todo
853              * fixme: Strictly we mess in case of multisegment mixed
854              * open/close curves
855              */
856             bool _closed = false;
857             for (bs = bs + 1; bs->code != NR_END; bs++) {
858                 switch (bs->code) {
859                     case NR_MOVETO_OPEN:
860                         if (_closed) closepath();
861                         moveto(bs->x3, bs->y3);
862                         _closed = false;
863                         break;
864                     case NR_MOVETO:
865                         if (_closed) closepath();
866                         moveto(bs->x3, bs->y3);
867                         _closed = true;
868                         break;
869                     case NR_LINETO:
870                         lineto(bs->x3, bs->y3);
871                         break;
872                     case NR_CURVETO:
873                         curveto(bs->x1, bs->y1, bs->x2, bs->y2, bs->x3, bs->y3);
874                         break;
875                     case NR_END:
876                         g_assert_not_reached();
877                 }
878             }
879         } else {
880             append(c1, TRUE);
881         }
882     } else {
883         append(c1, TRUE);
884     }
886     return this;
889 /**
890  * Remove last segment of curve.
891  */
892 void
893 SPCurve::backspace()
895     g_return_if_fail(this != NULL);
897     if (end > 0) {
898         end -= 1;
899         if (end > 0) {
900             NArtBpath *bp = _bpath + end - 1;
901             if ((bp->code == NR_MOVETO)     ||
902                 (bp->code == NR_MOVETO_OPEN)  )
903             {
904                 hascpt = true;
905                 posSet = true;
906                 closed = false;
907                 movePos = bp->c(3);
908                 end -= 1;
909             }
910         }
911         _bpath[end].code = NR_END;
912     }
915 /* Private methods */
917 /**
918  * Returns index of first NR_END bpath in array.
919  */
920 static unsigned sp_bpath_length(NArtBpath const bpath[])
922     g_return_val_if_fail(bpath != NULL, FALSE);
924     unsigned ret = 0;
925     while ( bpath[ret].code != NR_END ) {
926         ++ret;
927     }
928     ++ret;
930     return ret;
933 /**
934  * \brief
935  *
936  * \todo
937  * fixme: this is bogus -- it doesn't check for nr_moveto, which will indicate
938  * a closing of the subpath it's nonsense to talk about a path as a whole
939  * being closed, although maybe someone would want that for some other reason?
940  * Oh, also, if the bpath just ends, then it's *open*.  I hope nobody is using
941  * this code for anything.
942  */
943 static bool sp_bpath_closed(NArtBpath const bpath[])
945     g_return_val_if_fail(bpath != NULL, FALSE);
947     for (NArtBpath const *bp = bpath; bp->code != NR_END; bp++) {
948         if (bp->code == NR_MOVETO_OPEN) {
949             return false;
950         }
951     }
953     return true;
956 /**
957  * Returns length of bezier segment.
958  */
959 static double
960 bezier_len(NR::Point const &c0,
961            NR::Point const &c1,
962            NR::Point const &c2,
963            NR::Point const &c3,
964            double const threshold)
966     /** \todo
967      * The SVG spec claims that a closed form exists, but for the moment I'll
968      * use a stupid algorithm.
969      */
970     double const lbound = L2( c3 - c0 );
971     double const ubound = L2( c1 - c0 ) + L2( c2 - c1 ) + L2( c3 - c2 );
972     double ret;
973     if ( ubound - lbound <= threshold ) {
974         ret = .5 * ( lbound + ubound );
975     } else {
976         NR::Point const a1( .5 * ( c0 + c1 ) );
977         NR::Point const b2( .5 * ( c2 + c3 ) );
978         NR::Point const c12( .5 * ( c1 + c2 ) );
979         NR::Point const a2( .5 * ( a1 + c12 ) );
980         NR::Point const b1( .5 * ( c12 + b2 ) );
981         NR::Point const midpoint( .5 * ( a2 + b1 ) );
982         double const rec_threshold = .625 * threshold;
983         ret = bezier_len(c0, a1, a2, midpoint, rec_threshold) + bezier_len(midpoint, b1, b2, c3, rec_threshold);
984         if (!(lbound - 1e-2 <= ret && ret <= ubound + 1e-2)) {
985             using NR::X; using NR::Y;
986             g_warning("ret=%f outside of expected bounds [%f, %f] for {(%.0f %.0f) (%.0f %.0f) (%.0f %.0f) (%.0f %.0f)}",
987                       ret, lbound, ubound, c0[X], c0[Y], c1[X], c1[Y], c2[X], c2[Y], c3[X], c3[Y]);
988         }
989     }
990     return ret;
993 /**
994  * Returns total length of curve, excluding length of closepath segments.
995  */
996 static double
997 sp_curve_distance_including_space(SPCurve const *const curve, double seg2len[])
999     g_return_val_if_fail(curve != NULL, 0.);
1001     double ret = 0.0;
1003     if ( curve->_bpath->code == NR_END ) {
1004         return ret;
1005     }
1007     NR::Point prev(curve->_bpath->c(3));
1008     for (gint i = 1; i < curve->end; ++i) {
1009         NArtBpath &p = curve->_bpath[i];
1010         double seg_len = 0;
1011         switch (p.code) {
1012             case NR_MOVETO_OPEN:
1013             case NR_MOVETO:
1014             case NR_LINETO:
1015                 seg_len = L2(p.c(3) - prev);
1016                 break;
1018             case NR_CURVETO:
1019                 seg_len = bezier_len(prev, p.c(1), p.c(2), p.c(3), 1.);
1020                 break;
1022             case NR_END:
1023                 return ret;
1024         }
1025         seg2len[i - 1] = seg_len;
1026         ret += seg_len;
1027         prev = p.c(3);
1028     }
1029     g_assert(!(ret < 0));
1030     return ret;
1033 /**
1034  * Like sp_curve_distance_including_space(), but ensures that the
1035  * result >= 1e-18:  uses 1 per segment if necessary.
1036  */
1037 static double
1038 sp_curve_nonzero_distance_including_space(SPCurve const *const curve, double seg2len[])
1040     double const real_dist(sp_curve_distance_including_space(curve, seg2len));
1041     if (real_dist >= 1e-18) {
1042         return real_dist;
1043     } else {
1044         unsigned const nSegs = SP_CURVE_LENGTH(curve) - 1;
1045         for (unsigned i = 0; i < nSegs; ++i) {
1046             seg2len[i] = 1.;
1047         }
1048         return (double) nSegs;
1049     }
1052 void
1053 SPCurve::stretch_endpoints(NR::Point const &new_p0, NR::Point const &new_p1)
1055     if (is_empty()) {
1056         return;
1057     }
1058     g_assert(unsigned(SP_CURVE_LENGTH(this)) + 1 == sp_bpath_length(_bpath));
1059     unsigned const nSegs = SP_CURVE_LENGTH(this) - 1;
1060     g_assert(nSegs != 0);
1061     double *const seg2len = new double[nSegs];
1062     double const tot_len = sp_curve_nonzero_distance_including_space(this, seg2len);
1063     NR::Point const offset0( new_p0 - first_point() );
1064     NR::Point const offset1( new_p1 - last_point() );
1065     _bpath->setC(3, new_p0);
1066     double begin_dist = 0.;
1067     for (unsigned si = 0; si < nSegs; ++si) {
1068         double const end_dist = begin_dist + seg2len[si];
1069         NArtBpath &p = _bpath[1 + si];
1070         switch (p.code) {
1071             case NR_LINETO:
1072             case NR_MOVETO:
1073             case NR_MOVETO_OPEN:
1074                 p.setC(3, p.c(3) + NR::Lerp(end_dist / tot_len, offset0, offset1));
1075                 break;
1077             case NR_CURVETO:
1078                 for (unsigned ci = 1; ci <= 3; ++ci) {
1079                     p.setC(ci, p.c(ci) + Lerp((begin_dist + ci * seg2len[si] / 3.) / tot_len, offset0, offset1));
1080                 }
1081                 break;
1083             default:
1084                 g_assert_not_reached();
1085         }
1087         begin_dist = end_dist;
1088     }
1089     g_assert(L1(_bpath[nSegs].c(3) - new_p1) < 1.);
1090     /* Explicit set for better numerical properties. */
1091     _bpath[nSegs].setC(3, new_p1);
1092     delete [] seg2len;
1095 void
1096 SPCurve::move_endpoints(NR::Point const &new_p0, NR::Point const &new_p1)
1098     if (is_empty()) {
1099         return;
1100     }
1101     unsigned const nSegs = SP_CURVE_LENGTH(this) - 1;
1102     g_assert(nSegs != 0);
1104     _bpath->setC(3, new_p0);
1105     _bpath[nSegs].setC(3, new_p1);
1109 /*
1110   Local Variables:
1111   mode:c++
1112   c-file-style:"stroustrup"
1113   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1114   indent-tabs-mode:nil
1115   fill-column:99
1116   End:
1117 */
1118 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :