Code

8868107bc770d0bae52c11fd9d5b31bccb8393d0
[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  *
15  * Released under GNU GPL
16  */
18 #include <string.h>
19 #include <glib/gmem.h>
20 #include <display/curve.h>
21 #include <libnr/n-art-bpath.h>
22 #include <libnr/nr-point-matrix-ops.h>
23 #include <libnr/nr-translate-ops.h>
24 #include <cstring>
25 #include <string>
27 #define SP_CURVE_LENSTEP 32
29 static unsigned sp_bpath_length(NArtBpath const bpath[]);
30 static bool sp_bpath_closed(NArtBpath const bpath[]);
32 /* Constructors */
34 /**
35  * The returned curve's state is as if sp_curve_reset has just been called on it.
36  */
37 SPCurve *
38 sp_curve_new()
39 {
40     return sp_curve_new_sized(SP_CURVE_LENSTEP);
41 }
43 /**
44  * Like sp_curve_new, but overriding the default initial capacity.
45  *
46  * The returned curve's state is as if sp_curve_reset has just been called on it.
47  *
48  * \param length Initial number of NArtBpath elements allocated for bpath (including NR_END
49  *    element).
50  */
51 SPCurve *
52 sp_curve_new_sized(gint length)
53 {
54     g_return_val_if_fail(length > 0, NULL);
56     SPCurve *curve = g_new(SPCurve, 1);
58     curve->refcount = 1;
59     curve->_bpath = g_new(NArtBpath, length);
60     curve->_bpath->code = NR_END;
61     curve->end = 0;
62     curve->length = length;
63     curve->substart = 0;
64     curve->hascpt = false;
65     curve->posSet = false;
66     curve->moving = false;
67     curve->closed = false;
69     return curve;
70 }
72 /**
73  * Convert NArtBpath object to SPCurve object.
74  *
75  * \return new SPCurve, or NULL if the curve was not created for some reason.
76  */
77 SPCurve *
78 sp_curve_new_from_bpath(NArtBpath *bpath)
79 {
80     g_return_val_if_fail(bpath != NULL, NULL);
82     SPCurve *curve = sp_curve_new_from_foreign_bpath(bpath);
83     g_free(bpath);
84     return curve;
85 }
87 /**
88  * Convert const NArtBpath array to SPCurve.
89  *
90  * \return new SPCurve, or NULL if the curve was not created for some reason.
91  */
92 SPCurve *sp_curve_new_from_foreign_bpath(NArtBpath const bpath[])
93 {
94     g_return_val_if_fail(bpath != NULL, NULL);
96     NArtBpath *new_bpath;
97     unsigned const len = sp_bpath_length(bpath);
98     new_bpath = g_new(NArtBpath, len);
99     memcpy(new_bpath, bpath, len * sizeof(NArtBpath));
101     SPCurve *curve = g_new(SPCurve, 1);
103     curve->refcount = 1;
104     curve->_bpath = new_bpath;
105     curve->length = len;
106     curve->end = curve->length - 1;
107     gint i = curve->end;
108     for (; i > 0; i--)
109         if ((curve->_bpath[i].code == NR_MOVETO) ||
110             (curve->_bpath[i].code == NR_MOVETO_OPEN))
111             break;
112     curve->substart = i;
113     curve->hascpt = false;
114     curve->posSet = false;
115     curve->moving = false;
116     curve->closed = sp_bpath_closed(new_bpath);
118     return curve;
121 SPCurve *sp_curve_new_from_rect(NR::Maybe<NR::Rect> const &rect)
123     g_return_val_if_fail(rect, NULL);
125     SPCurve *c = sp_curve_new();
127     NR::Point p = rect->corner(0);
128     sp_curve_moveto(c, p);
130     for (int i=3; i>=0; i--) {
131         sp_curve_lineto(c, rect->corner(i));
132     }
133     sp_curve_closepath_current(c);
135     return c;
138 /**
139  * Increase refcount of curve.
140  *
141  * \todo should this be shared with other refcounting code?
142  */
143 SPCurve *
144 sp_curve_ref(SPCurve *curve)
146     g_return_val_if_fail(curve != NULL, NULL);
148     curve->refcount += 1;
150     return curve;
153 /**
154  * Decrease refcount of curve, with possible destruction.
155  *
156  * \todo should this be shared with other refcounting code?
157  */
158 SPCurve *
159 sp_curve_unref(SPCurve *curve)
161     g_return_val_if_fail(curve != NULL, NULL);
163     curve->refcount -= 1;
165     if (curve->refcount < 1) {
166         if (curve->_bpath) {
167             g_free(curve->_bpath);
168         }
169         g_free(curve);
170     }
172     return NULL;
175 /**
176  * Add space for more paths in curve.
177  */
178 static void
179 sp_curve_ensure_space(SPCurve *curve, gint space)
181     g_return_if_fail(curve != NULL);
182     g_return_if_fail(space > 0);
184     if (curve->end + space < curve->length)
185         return;
187     if (space < SP_CURVE_LENSTEP)
188         space = SP_CURVE_LENSTEP;
190     curve->_bpath = g_renew(NArtBpath, curve->_bpath, curve->length + space);
192     curve->length += space;
195 /**
196  * Create new curve from its own bpath array.
197  */
198 SPCurve *
199 sp_curve_copy(SPCurve *curve)
201     g_return_val_if_fail(curve != NULL, NULL);
203     return sp_curve_new_from_foreign_bpath(curve->_bpath);
206 /**
207  * Return new curve that is the concatenation of all curves in list.
208  */
209 SPCurve *
210 sp_curve_concat(GSList const *list)
212     g_return_val_if_fail(list != NULL, NULL);
214     gint length = 0;
216     for (GSList const *l = list; l != NULL; l = l->next) {
217         SPCurve *c = (SPCurve *) l->data;
218         length += c->end;
219     }
221     SPCurve *new_curve = sp_curve_new_sized(length + 1);
223     NArtBpath *bp = new_curve->_bpath;
225     for (GSList const *l = list; l != NULL; l = l->next) {
226         SPCurve *c = (SPCurve *) l->data;
227         memcpy(bp, c->_bpath, c->end * sizeof(NArtBpath));
228         bp += c->end;
229     }
231     bp->code = NR_END;
233     new_curve->end = length;
234     gint i;
235     for (i = new_curve->end; i > 0; i--) {
236         if ((new_curve->_bpath[i].code == NR_MOVETO)     ||
237             (new_curve->_bpath[i].code == NR_MOVETO_OPEN)  )
238             break;
239     }
241     new_curve->substart = i;
243     return new_curve;
246 /**
247  * Returns a list of new curves corresponding to the subpaths in \a curve.
248  */
249 GSList *
250 sp_curve_split(SPCurve const *curve)
252     g_return_val_if_fail(curve != NULL, NULL);
254     gint p = 0;
255     GSList *l = NULL;
257     while (p < curve->end) {
258         gint i = 1;
259         while ((curve->_bpath[p + i].code == NR_LINETO) ||
260                (curve->_bpath[p + i].code == NR_CURVETO))
261             i++;
262         SPCurve *new_curve = sp_curve_new_sized(i + 1);
263         memcpy(new_curve->_bpath, curve->_bpath + p, i * sizeof(NArtBpath));
264         new_curve->end = i;
265         new_curve->_bpath[i].code = NR_END;
266         new_curve->substart = 0;
267         new_curve->closed = (new_curve->_bpath->code == NR_MOVETO);
268         new_curve->hascpt = (new_curve->_bpath->code == NR_MOVETO_OPEN);
269         l = g_slist_prepend(l, new_curve);
270         p += i;
271     }
273     return l;
276 /**
277  * Transform all paths in curve, template helper.
278  */
279 template<class M>
280 static void
281 tmpl_curve_transform(SPCurve *const curve, M const &m)
283     g_return_if_fail(curve != NULL);
285     for (gint i = 0; i < curve->end; i++) {
286         NArtBpath *p = curve->_bpath + i;
287         switch (p->code) {
288             case NR_MOVETO:
289             case NR_MOVETO_OPEN:
290             case NR_LINETO: {
291                 p->setC(3, p->c(3) * m);
292                 break;
293             }
294             case NR_CURVETO:
295                 for (unsigned i = 1; i <= 3; ++i) {
296                     p->setC(i, p->c(i) * m);
297                 }
298                 break;
299             default:
300                 g_warning("Illegal pathcode %d", p->code);
301                 break;
302         }
303     }
306 /**
307  * Transform all paths in curve using matrix.
308  */
309 void
310 sp_curve_transform(SPCurve *const curve, NR::Matrix const &m)
312     tmpl_curve_transform<NR::Matrix>(curve, m);
315 /**
316  * Transform all paths in curve using NR::translate.
317  */
318 void
319 sp_curve_transform(SPCurve *const curve, NR::translate const &m)
321     tmpl_curve_transform<NR::translate>(curve, m);
325 /* Methods */
327 /**
328  * Set curve to empty curve.
329  */
330 void
331 sp_curve_reset(SPCurve *curve)
333     g_return_if_fail(curve != NULL);
335     curve->_bpath->code = NR_END;
336     curve->end = 0;
337     curve->substart = 0;
338     curve->hascpt = false;
339     curve->posSet = false;
340     curve->moving = false;
341     curve->closed = false;
344 /* Several consecutive movetos are ALLOWED */
346 /**
347  * Calls sp_curve_moveto() with point made of given coordinates.
348  */
349 void
350 sp_curve_moveto(SPCurve *curve, gdouble x, gdouble y)
352     sp_curve_moveto(curve, NR::Point(x, y));
355 /**
356  * Perform a moveto to a point, thus starting a new subpath.
357  */
358 void
359 sp_curve_moveto(SPCurve *curve, NR::Point const &p)
361     g_return_if_fail(curve != NULL);
362     g_return_if_fail(!curve->moving);
364     curve->substart = curve->end;
365     curve->hascpt = true;
366     curve->posSet = true;
367     curve->movePos = p;
370 /**
371  * Calls sp_curve_lineto() with a point's coordinates.
372  */
373 void
374 sp_curve_lineto(SPCurve *curve, NR::Point const &p)
376     sp_curve_lineto(curve, p[NR::X], p[NR::Y]);
379 /**
380  * Adds a line to the current subpath.
381  */
382 void
383 sp_curve_lineto(SPCurve *curve, gdouble x, gdouble y)
385     g_return_if_fail(curve != NULL);
386     g_return_if_fail(curve->hascpt);
388     if (curve->moving) {
389         /* fix endpoint */
390         g_return_if_fail(!curve->posSet);
391         g_return_if_fail(curve->end > 1);
392         NArtBpath *bp = curve->_bpath + curve->end - 1;
393         g_return_if_fail(bp->code == NR_LINETO);
394         bp->x3 = x;
395         bp->y3 = y;
396         curve->moving = false;
397         return;
398     }
400     if (curve->posSet) {
401         /* start a new segment */
402         sp_curve_ensure_space(curve, 2);
403         NArtBpath *bp = curve->_bpath + curve->end;
404         bp->code = NR_MOVETO_OPEN;
405         bp->setC(3, curve->movePos);
406         bp++;
407         bp->code = NR_LINETO;
408         bp->x3 = x;
409         bp->y3 = y;
410         bp++;
411         bp->code = NR_END;
412         curve->end += 2;
413         curve->posSet = false;
414         curve->closed = false;
415         return;
416     }
418     /* add line */
420     g_return_if_fail(curve->end > 1);
421     sp_curve_ensure_space(curve, 1);
422     NArtBpath *bp = curve->_bpath + curve->end;
423     bp->code = NR_LINETO;
424     bp->x3 = x;
425     bp->y3 = y;
426     bp++;
427     bp->code = NR_END;
428     curve->end++;
431 /// Unused
432 void
433 sp_curve_lineto_moving(SPCurve *curve, gdouble x, gdouble y)
435     g_return_if_fail(curve != NULL);
436     g_return_if_fail(curve->hascpt);
438     if (curve->moving) {
439         /* change endpoint */
440         g_return_if_fail(!curve->posSet);
441         g_return_if_fail(curve->end > 1);
442         NArtBpath *bp = curve->_bpath + curve->end - 1;
443         g_return_if_fail(bp->code == NR_LINETO);
444         bp->x3 = x;
445         bp->y3 = y;
446         return;
447     }
449     if (curve->posSet) {
450         /* start a new segment */
451         sp_curve_ensure_space(curve, 2);
452         NArtBpath *bp = curve->_bpath + curve->end;
453         bp->code = NR_MOVETO_OPEN;
454         bp->setC(3, curve->movePos);
455         bp++;
456         bp->code = NR_LINETO;
457         bp->x3 = x;
458         bp->y3 = y;
459         bp++;
460         bp->code = NR_END;
461         curve->end += 2;
462         curve->posSet = false;
463         curve->moving = true;
464         curve->closed = false;
465         return;
466     }
468     /* add line */
470     g_return_if_fail(curve->end > 1);
471     sp_curve_ensure_space(curve, 1);
472     NArtBpath *bp = curve->_bpath + curve->end;
473     bp->code = NR_LINETO;
474     bp->x3 = x;
475     bp->y3 = y;
476     bp++;
477     bp->code = NR_END;
478     curve->end++;
479     curve->moving = true;
482 /**
483  * Calls sp_curve_curveto() with coordinates of three points.
484  */
485 void
486 sp_curve_curveto(SPCurve *curve, NR::Point const &p0, NR::Point const &p1, NR::Point const &p2)
488     using NR::X;
489     using NR::Y;
490     sp_curve_curveto(curve,
491                      p0[X], p0[Y],
492                      p1[X], p1[Y],
493                      p2[X], p2[Y]);
496 /**
497  * Adds a bezier segment to the current subpath.
498  */
499 void
500 sp_curve_curveto(SPCurve *curve, gdouble x0, gdouble y0, gdouble x1, gdouble y1, gdouble x2, gdouble y2)
502     g_return_if_fail(curve != NULL);
503     g_return_if_fail(curve->hascpt);
504     g_return_if_fail(!curve->moving);
506     if (curve->posSet) {
507         /* start a new segment */
508         sp_curve_ensure_space(curve, 2);
509         NArtBpath *bp = curve->_bpath + curve->end;
510         bp->code = NR_MOVETO_OPEN;
511         bp->setC(3, curve->movePos);
512         bp++;
513         bp->code = NR_CURVETO;
514         bp->x1 = x0;
515         bp->y1 = y0;
516         bp->x2 = x1;
517         bp->y2 = y1;
518         bp->x3 = x2;
519         bp->y3 = y2;
520         bp++;
521         bp->code = NR_END;
522         curve->end += 2;
523         curve->posSet = false;
524         curve->closed = false;
525         return;
526     }
528     /* add curve */
530     g_return_if_fail(curve->end > 1);
531     sp_curve_ensure_space(curve, 1);
532     NArtBpath *bp = curve->_bpath + curve->end;
533     bp->code = NR_CURVETO;
534     bp->x1 = x0;
535     bp->y1 = y0;
536     bp->x2 = x1;
537     bp->y2 = y1;
538     bp->x3 = x2;
539     bp->y3 = y2;
540     bp++;
541     bp->code = NR_END;
542     curve->end++;
545 /**
546  * Close current subpath by possibly adding a line between start and end.
547  */
548 void
549 sp_curve_closepath(SPCurve *curve)
551     g_return_if_fail(curve != NULL);
552     g_return_if_fail(curve->hascpt);
553     g_return_if_fail(!curve->posSet);
554     g_return_if_fail(!curve->moving);
555     g_return_if_fail(!curve->closed);
556     /* We need at least moveto, curveto, end. */
557     g_return_if_fail(curve->end - curve->substart > 1);
559     {
560         NArtBpath *bs = curve->_bpath + curve->substart;
561         NArtBpath *be = curve->_bpath + curve->end - 1;
563         if (bs->c(3) != be->c(3)) {
564             sp_curve_lineto(curve, bs->c(3));
565             bs = curve->_bpath + curve->substart;
566         }
568         bs->code = NR_MOVETO;
569     }
570     curve->closed = true;
572     for (NArtBpath const *bp = curve->_bpath; bp->code != NR_END; bp++) {
573         /** \todo
574          * effic: Maintain a count of NR_MOVETO_OPEN's (e.g. instead of
575          * the closed boolean).
576          */
577         if (bp->code == NR_MOVETO_OPEN) {
578             curve->closed = false;
579             break;
580         }
581     }
583     curve->hascpt = false;
586 /** Like sp_curve_closepath() but sets the end point of the current
587     command to the subpath start point instead of adding a new lineto.
589     Used for freehand drawing when the user draws back to the start point.
590 **/
591 void
592 sp_curve_closepath_current(SPCurve *curve)
594     g_return_if_fail(curve != NULL);
595     g_return_if_fail(curve->hascpt);
596     g_return_if_fail(!curve->posSet);
597     g_return_if_fail(!curve->closed);
598     /* We need at least moveto, curveto, end. */
599     g_return_if_fail(curve->end - curve->substart > 1);
601     {
602         NArtBpath *bs = curve->_bpath + curve->substart;
603         NArtBpath *be = curve->_bpath + curve->end - 1;
605         be->x3 = bs->x3;
606         be->y3 = bs->y3;
608         bs->code = NR_MOVETO;
609     }
610     curve->closed = true;
612     for (NArtBpath const *bp = curve->_bpath; bp->code != NR_END; bp++) {
613         /** \todo
614          * effic: Maintain a count of NR_MOVETO_OPEN's (e.g. instead of
615          * the closed boolean).
616          */
617         if (bp->code == NR_MOVETO_OPEN) {
618             curve->closed = false;
619             break;
620         }
621     }
623     curve->hascpt = false;
624     curve->moving = false;
627 /**
628  * True if no paths are in curve.
629  */
630 bool
631 sp_curve_empty(SPCurve *curve)
633     g_return_val_if_fail(curve != NULL, TRUE);
635     return (curve->_bpath->code == NR_END);
638 /**
639  * Return last subpath or NULL.
640  */
641 NArtBpath *
642 sp_curve_last_bpath(SPCurve const *curve)
644     g_return_val_if_fail(curve != NULL, NULL);
646     if (curve->end == 0) {
647         return NULL;
648     }
650     return curve->_bpath + curve->end - 1;
653 /**
654  * Return first subpath or NULL.
655  */
656 NArtBpath *
657 sp_curve_first_bpath(SPCurve const *curve)
659     g_return_val_if_fail(curve != NULL, NULL);
661     if (curve->end == 0) {
662         return NULL;
663     }
665     return curve->_bpath;
668 /**
669  * Return first point of first subpath or (0,0).
670  */
671 NR::Point
672 sp_curve_first_point(SPCurve const *const curve)
674     NArtBpath *const bpath = sp_curve_first_bpath(curve);
675     g_return_val_if_fail(bpath != NULL, NR::Point(0, 0));
676     return bpath->c(3);
679 /**
680  * Return the second point of first subpath or curve->movePos if curve too short.
681  */
682 NR::Point
683 sp_curve_second_point(SPCurve const *const curve)
685     g_return_val_if_fail(curve != NULL, NR::Point(0, 0));
687     if (curve->end < 1) {
688         return curve->movePos;
689     }
691     NArtBpath *bpath = NULL;
692     if (curve->end < 2) {
693         bpath = curve->_bpath;
694     } else {
695         bpath = curve->_bpath + 1;
696     }
697     g_return_val_if_fail(bpath != NULL, NR::Point(0, 0));
698     return bpath->c(3);
701 /**
702  * Return the second-last point of last subpath or curve->movePos if curve too short.
703  */
704 NR::Point
705 sp_curve_penultimate_point(SPCurve const *const curve)
707     g_return_val_if_fail(curve != NULL, NR::Point(0, 0));
709     if (curve->end < 2) {
710         return curve->movePos;
711     }
713     NArtBpath *const bpath = curve->_bpath + curve->end - 2;
714     g_return_val_if_fail(bpath != NULL, NR::Point(0, 0));
715     return bpath->c(3);
718 /**
719  * Return last point of last subpath or (0,0).
720  */
721 NR::Point
722 sp_curve_last_point(SPCurve const *const curve)
724     NArtBpath *const bpath = sp_curve_last_bpath(curve);
725     g_return_val_if_fail(bpath != NULL, NR::Point(0, 0));
726     return bpath->c(3);
729 inline static bool
730 is_moveto(NRPathcode const c)
732     return c == NR_MOVETO || c == NR_MOVETO_OPEN;
735 /**
736  * Returns \a curve but drawn in the opposite direction.
737  * Should result in the same shape, but
738  * with all its markers drawn facing the other direction.
739  **/
740 SPCurve *
741 sp_curve_reverse(SPCurve const *curve)
743     /* We need at least moveto, curveto, end. */
744     g_return_val_if_fail(curve->end - curve->substart > 1, NULL);
746     NArtBpath const *be = curve->_bpath + curve->end - 1;
748     g_assert(is_moveto(curve->_bpath[curve->substart].code));
749     g_assert(is_moveto(curve->_bpath[0].code));
750     g_assert((be+1)->code == NR_END);
752     SPCurve  *new_curve = sp_curve_new_sized(curve->length);
753     sp_curve_moveto(new_curve, be->c(3));
755     for (NArtBpath const *bp = be; ; --bp) {
756         switch (bp->code) {
757             case NR_MOVETO:
758                 g_assert(new_curve->_bpath[new_curve->substart].code == NR_MOVETO_OPEN);
759                 new_curve->_bpath[new_curve->substart].code = NR_MOVETO;
760                 /* FALL-THROUGH */
761             case NR_MOVETO_OPEN:
762                 if (bp == curve->_bpath) {
763                     return new_curve;
764                 }
765                 sp_curve_moveto(new_curve, (bp-1)->c(3));
766                 break;
768             case NR_LINETO:
769                 sp_curve_lineto(new_curve, (bp-1)->c(3));
770                 break;
772             case NR_CURVETO:
773                 sp_curve_curveto(new_curve, bp->c(2), bp->c(1), (bp-1)->c(3));
774                 break;
776             default:
777                 g_assert_not_reached();
778         }
779     }
782 /**
783  * Append \a curve2 to \a curve.
784  */
785 void
786 sp_curve_append(SPCurve *curve,
787                 SPCurve const *curve2,
788                 bool use_lineto)
790     g_return_if_fail(curve != NULL);
791     g_return_if_fail(curve2 != NULL);
793     if (curve2->end < 1)
794         return;
796     NArtBpath const *bs = curve2->_bpath;
798     bool closed = curve->closed;
800     for (NArtBpath const *bp = bs; bp->code != NR_END; bp++) {
801         switch (bp->code) {
802             case NR_MOVETO_OPEN:
803                 if (use_lineto && curve->hascpt) {
804                     sp_curve_lineto(curve, bp->x3, bp->y3);
805                     use_lineto = FALSE;
806                 } else {
807                     if (closed) sp_curve_closepath(curve);
808                     sp_curve_moveto(curve, bp->x3, bp->y3);
809                 }
810                 closed = false;
811                 break;
813             case NR_MOVETO:
814                 if (use_lineto && curve->hascpt) {
815                     sp_curve_lineto(curve, bp->x3, bp->y3);
816                     use_lineto = FALSE;
817                 } else {
818                     if (closed) sp_curve_closepath(curve);
819                     sp_curve_moveto(curve, bp->x3, bp->y3);
820                 }
821                 closed = true;
822                 break;
824             case NR_LINETO:
825                 sp_curve_lineto(curve, bp->x3, bp->y3);
826                 break;
828             case NR_CURVETO:
829                 sp_curve_curveto(curve, bp->x1, bp->y1, bp->x2, bp->y2, bp->x3, bp->y3);
830                 break;
832             case NR_END:
833                 g_assert_not_reached();
834         }
835     }
837     if (closed) {
838         sp_curve_closepath(curve);
839     }
842 /**
843  * Append \a c1 to \a c0 with possible fusing of close endpoints.
844  */
845 SPCurve *
846 sp_curve_append_continuous(SPCurve *c0, SPCurve const *c1, gdouble tolerance)
848     g_return_val_if_fail(c0 != NULL, NULL);
849     g_return_val_if_fail(c1 != NULL, NULL);
850     g_return_val_if_fail(!c0->closed, NULL);
851     g_return_val_if_fail(!c1->closed, NULL);
853     if (c1->end < 1) {
854         return c0;
855     }
857     NArtBpath *be = sp_curve_last_bpath(c0);
858     if (be) {
859         NArtBpath const *bs = sp_curve_first_bpath(c1);
860         if ( bs
861              && ( fabs( bs->x3 - be->x3 ) <= tolerance )
862              && ( fabs( bs->y3 - be->y3 ) <= tolerance ) )
863         {
864             /** \todo
865              * fixme: Strictly we mess in case of multisegment mixed
866              * open/close curves
867              */
868             bool closed = false;
869             for (bs = bs + 1; bs->code != NR_END; bs++) {
870                 switch (bs->code) {
871                     case NR_MOVETO_OPEN:
872                         if (closed) sp_curve_closepath(c0);
873                         sp_curve_moveto(c0, bs->x3, bs->y3);
874                         closed = false;
875                         break;
876                     case NR_MOVETO:
877                         if (closed) sp_curve_closepath(c0);
878                         sp_curve_moveto(c0, bs->x3, bs->y3);
879                         closed = true;
880                         break;
881                     case NR_LINETO:
882                         sp_curve_lineto(c0, bs->x3, bs->y3);
883                         break;
884                     case NR_CURVETO:
885                         sp_curve_curveto(c0, bs->x1, bs->y1, bs->x2, bs->y2, bs->x3, bs->y3);
886                         break;
887                     case NR_END:
888                         g_assert_not_reached();
889                 }
890             }
891         } else {
892             sp_curve_append(c0, c1, TRUE);
893         }
894     } else {
895         sp_curve_append(c0, c1, TRUE);
896     }
898     return c0;
901 /**
902  * Remove last segment of curve.
903  */
904 void
905 sp_curve_backspace(SPCurve *curve)
907     g_return_if_fail(curve != NULL);
909     if (curve->end > 0) {
910         curve->end -= 1;
911         if (curve->end > 0) {
912             NArtBpath *bp = curve->_bpath + curve->end - 1;
913             if ((bp->code == NR_MOVETO)     ||
914                 (bp->code == NR_MOVETO_OPEN)  )
915             {
916                 curve->hascpt = true;
917                 curve->posSet = true;
918                 curve->closed = false;
919                 curve->movePos = bp->c(3);
920                 curve->end -= 1;
921             }
922         }
923         curve->_bpath[curve->end].code = NR_END;
924     }
927 /* Private methods */
929 /**
930  * Returns index of first NR_END bpath in array.
931  */
932 static unsigned sp_bpath_length(NArtBpath const bpath[])
934     g_return_val_if_fail(bpath != NULL, FALSE);
936     unsigned ret = 0;
937     while ( bpath[ret].code != NR_END ) {
938         ++ret;
939     }
940     ++ret;
942     return ret;
945 /**
946  * \brief
947  *
948  * \todo
949  * fixme: this is bogus -- it doesn't check for nr_moveto, which will indicate
950  * a closing of the subpath it's nonsense to talk about a path as a whole
951  * being closed, although maybe someone would want that for some other reason?
952  * Oh, also, if the bpath just ends, then it's *open*.  I hope nobody is using
953  * this code for anything.
954  */
955 static bool sp_bpath_closed(NArtBpath const bpath[])
957     g_return_val_if_fail(bpath != NULL, FALSE);
959     for (NArtBpath const *bp = bpath; bp->code != NR_END; bp++) {
960         if (bp->code == NR_MOVETO_OPEN) {
961             return false;
962         }
963     }
965     return true;
968 /**
969  * Returns length of bezier segment.
970  */
971 static double
972 bezier_len(NR::Point const &c0,
973            NR::Point const &c1,
974            NR::Point const &c2,
975            NR::Point const &c3,
976            double const threshold)
978     /** \todo
979      * The SVG spec claims that a closed form exists, but for the moment I'll
980      * use a stupid algorithm.
981      */
982     double const lbound = L2( c3 - c0 );
983     double const ubound = L2( c1 - c0 ) + L2( c2 - c1 ) + L2( c3 - c2 );
984     double ret;
985     if ( ubound - lbound <= threshold ) {
986         ret = .5 * ( lbound + ubound );
987     } else {
988         NR::Point const a1( .5 * ( c0 + c1 ) );
989         NR::Point const b2( .5 * ( c2 + c3 ) );
990         NR::Point const c12( .5 * ( c1 + c2 ) );
991         NR::Point const a2( .5 * ( a1 + c12 ) );
992         NR::Point const b1( .5 * ( c12 + b2 ) );
993         NR::Point const midpoint( .5 * ( a2 + b1 ) );
994         double const rec_threshold = .625 * threshold;
995         ret = bezier_len(c0, a1, a2, midpoint, rec_threshold) + bezier_len(midpoint, b1, b2, c3, rec_threshold);
996         if (!(lbound - 1e-2 <= ret && ret <= ubound + 1e-2)) {
997             using NR::X; using NR::Y;
998             g_warning("ret=%f outside of expected bounds [%f, %f] for {(%.0f %.0f) (%.0f %.0f) (%.0f %.0f) (%.0f %.0f)}",
999                       ret, lbound, ubound, c0[X], c0[Y], c1[X], c1[Y], c2[X], c2[Y], c3[X], c3[Y]);
1000         }
1001     }
1002     return ret;
1005 /**
1006  * Returns total length of curve, excluding length of closepath segments.
1007  */
1008 static double
1009 sp_curve_distance_including_space(SPCurve const *const curve, double seg2len[])
1011     g_return_val_if_fail(curve != NULL, 0.);
1013     double ret = 0.0;
1015     if ( curve->_bpath->code == NR_END ) {
1016         return ret;
1017     }
1019     NR::Point prev(curve->_bpath->c(3));
1020     for (gint i = 1; i < curve->end; ++i) {
1021         NArtBpath &p = curve->_bpath[i];
1022         double seg_len = 0;
1023         switch (p.code) {
1024             case NR_MOVETO_OPEN:
1025             case NR_MOVETO:
1026             case NR_LINETO:
1027                 seg_len = L2(p.c(3) - prev);
1028                 break;
1030             case NR_CURVETO:
1031                 seg_len = bezier_len(prev, p.c(1), p.c(2), p.c(3), 1.);
1032                 break;
1034             case NR_END:
1035                 return ret;
1036         }
1037         seg2len[i - 1] = seg_len;
1038         ret += seg_len;
1039         prev = p.c(3);
1040     }
1041     g_assert(!(ret < 0));
1042     return ret;
1045 /**
1046  * Like sp_curve_distance_including_space(), but ensures that the
1047  * result >= 1e-18:  uses 1 per segment if necessary.
1048  */
1049 static double
1050 sp_curve_nonzero_distance_including_space(SPCurve const *const curve, double seg2len[])
1052     double const real_dist(sp_curve_distance_including_space(curve, seg2len));
1053     if (real_dist >= 1e-18) {
1054         return real_dist;
1055     } else {
1056         unsigned const nSegs = SP_CURVE_LENGTH(curve) - 1;
1057         for (unsigned i = 0; i < nSegs; ++i) {
1058             seg2len[i] = 1.;
1059         }
1060         return (double) nSegs;
1061     }
1064 void
1065 sp_curve_stretch_endpoints(SPCurve *curve, NR::Point const &new_p0, NR::Point const &new_p1)
1067     if (sp_curve_empty(curve)) {
1068         return;
1069     }
1070     g_assert(unsigned(SP_CURVE_LENGTH(curve)) + 1 == sp_bpath_length(curve->_bpath));
1071     unsigned const nSegs = SP_CURVE_LENGTH(curve) - 1;
1072     g_assert(nSegs != 0);
1073     double *const seg2len = new double[nSegs];
1074     double const tot_len = sp_curve_nonzero_distance_including_space(curve, seg2len);
1075     NR::Point const offset0( new_p0 - sp_curve_first_point(curve) );
1076     NR::Point const offset1( new_p1 - sp_curve_last_point(curve) );
1077     curve->_bpath->setC(3, new_p0);
1078     double begin_dist = 0.;
1079     for (unsigned si = 0; si < nSegs; ++si) {
1080         double const end_dist = begin_dist + seg2len[si];
1081         NArtBpath &p = curve->_bpath[1 + si];
1082         switch (p.code) {
1083             case NR_LINETO:
1084             case NR_MOVETO:
1085             case NR_MOVETO_OPEN:
1086                 p.setC(3, p.c(3) + NR::Lerp(end_dist / tot_len, offset0, offset1));
1087                 break;
1089             case NR_CURVETO:
1090                 for (unsigned ci = 1; ci <= 3; ++ci) {
1091                     p.setC(ci, p.c(ci) + Lerp((begin_dist + ci * seg2len[si] / 3.) / tot_len, offset0, offset1));
1092                 }
1093                 break;
1095             default:
1096                 g_assert_not_reached();
1097         }
1099         begin_dist = end_dist;
1100     }
1101     g_assert(L1(curve->_bpath[nSegs].c(3) - new_p1) < 1.);
1102     /* Explicit set for better numerical properties. */
1103     curve->_bpath[nSegs].setC(3, new_p1);
1104     delete [] seg2len;
1107 void
1108 sp_curve_move_endpoints(SPCurve *curve, NR::Point const &new_p0,
1109         NR::Point const &new_p1)
1111     if (sp_curve_empty(curve)) {
1112         return;
1113     }
1114     unsigned const nSegs = SP_CURVE_LENGTH(curve) - 1;
1115     g_assert(nSegs != 0);
1117     curve->_bpath->setC(3, new_p0);
1118     curve->_bpath[nSegs].setC(3, new_p1);
1122 /*
1123   Local Variables:
1124   mode:c++
1125   c-file-style:"stroustrup"
1126   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1127   indent-tabs-mode:nil
1128   fill-column:99
1129   End:
1130 */
1131 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :