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;
119 }
121 SPCurve *sp_curve_new_from_rect(NR::Maybe<NR::Rect> const &rect)
122 {
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;
136 }
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)
145 {
146 g_return_val_if_fail(curve != NULL, NULL);
148 curve->refcount += 1;
150 return curve;
151 }
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)
160 {
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;
173 }
175 /**
176 * Add space for more paths in curve.
177 */
178 static void
179 sp_curve_ensure_space(SPCurve *curve, gint space)
180 {
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;
193 }
195 /**
196 * Create new curve from its own bpath array.
197 */
198 SPCurve *
199 sp_curve_copy(SPCurve *curve)
200 {
201 g_return_val_if_fail(curve != NULL, NULL);
203 return sp_curve_new_from_foreign_bpath(curve->_bpath);
204 }
206 /**
207 * Return new curve that is the concatenation of all curves in list.
208 */
209 SPCurve *
210 sp_curve_concat(GSList const *list)
211 {
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;
244 }
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)
251 {
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;
274 }
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)
282 {
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 }
304 }
306 /**
307 * Transform all paths in curve using matrix.
308 */
309 void
310 sp_curve_transform(SPCurve *const curve, NR::Matrix const &m)
311 {
312 tmpl_curve_transform<NR::Matrix>(curve, m);
313 }
315 /**
316 * Transform all paths in curve using NR::translate.
317 */
318 void
319 sp_curve_transform(SPCurve *const curve, NR::translate const &m)
320 {
321 tmpl_curve_transform<NR::translate>(curve, m);
322 }
325 /* Methods */
327 /**
328 * Set curve to empty curve.
329 */
330 void
331 sp_curve_reset(SPCurve *curve)
332 {
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;
342 }
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)
351 {
352 sp_curve_moveto(curve, NR::Point(x, y));
353 }
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)
360 {
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;
368 }
370 /**
371 * Calls sp_curve_lineto() with a point's coordinates.
372 */
373 void
374 sp_curve_lineto(SPCurve *curve, NR::Point const &p)
375 {
376 sp_curve_lineto(curve, p[NR::X], p[NR::Y]);
377 }
379 /**
380 * Adds a line to the current subpath.
381 */
382 void
383 sp_curve_lineto(SPCurve *curve, gdouble x, gdouble y)
384 {
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++;
429 }
431 /// Unused
432 void
433 sp_curve_lineto_moving(SPCurve *curve, gdouble x, gdouble y)
434 {
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;
480 }
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)
487 {
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]);
494 }
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)
501 {
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++;
543 }
545 /**
546 * Close current subpath by possibly adding a line between start and end.
547 */
548 void
549 sp_curve_closepath(SPCurve *curve)
550 {
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;
584 }
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)
593 {
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;
625 }
627 /**
628 * True if no paths are in curve.
629 */
630 bool
631 sp_curve_empty(SPCurve *curve)
632 {
633 g_return_val_if_fail(curve != NULL, TRUE);
635 return (curve->_bpath->code == NR_END);
636 }
638 /**
639 * Return last subpath or NULL.
640 */
641 NArtBpath *
642 sp_curve_last_bpath(SPCurve const *curve)
643 {
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;
651 }
653 /**
654 * Return first subpath or NULL.
655 */
656 NArtBpath *
657 sp_curve_first_bpath(SPCurve const *curve)
658 {
659 g_return_val_if_fail(curve != NULL, NULL);
661 if (curve->end == 0) {
662 return NULL;
663 }
665 return curve->_bpath;
666 }
668 /**
669 * Return first point of first subpath or (0,0).
670 */
671 NR::Point
672 sp_curve_first_point(SPCurve const *const curve)
673 {
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);
677 }
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)
684 {
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);
699 }
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)
706 {
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);
716 }
718 /**
719 * Return last point of last subpath or (0,0).
720 */
721 NR::Point
722 sp_curve_last_point(SPCurve const *const curve)
723 {
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);
727 }
729 inline static bool
730 is_moveto(NRPathcode const c)
731 {
732 return c == NR_MOVETO || c == NR_MOVETO_OPEN;
733 }
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)
742 {
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 }
780 }
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)
789 {
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 }
840 }
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)
847 {
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;
899 }
901 /**
902 * Remove last segment of curve.
903 */
904 void
905 sp_curve_backspace(SPCurve *curve)
906 {
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 }
925 }
927 /* Private methods */
929 /**
930 * Returns index of first NR_END bpath in array.
931 */
932 static unsigned sp_bpath_length(NArtBpath const bpath[])
933 {
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;
943 }
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[])
956 {
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;
966 }
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)
977 {
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;
1003 }
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[])
1010 {
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;
1043 }
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[])
1051 {
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 }
1062 }
1064 void
1065 sp_curve_stretch_endpoints(SPCurve *curve, NR::Point const &new_p0, NR::Point const &new_p1)
1066 {
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;
1105 }
1107 void
1108 sp_curve_move_endpoints(SPCurve *curve, NR::Point const &new_p0,
1109 NR::Point const &new_p1)
1110 {
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);
1119 }
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 :