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);
108 }
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)
121 {
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);
136 }
138 // * 2GEOMproof
139 SPCurve *
140 SPCurve::new_from_foreign_bpath(NArtBpath const *bpath)
141 {
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;
167 }
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)
177 {
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;
186 }
188 // * 2GEOMproof
189 SPCurve *
190 SPCurve::new_from_rect(NR::Maybe<NR::Rect> const &rect)
191 {
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;
207 }
209 // * 2GEOMproof
210 SPCurve::~SPCurve()
211 {
212 if (_bpath) {
213 g_free(_bpath);
214 _bpath = NULL;
215 }
216 }
218 /* Methods */
220 void
221 SPCurve::set_pathv(Geom::PathVector const & new_pathv)
222 {
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);
247 }
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
254 {
255 debug_check("SPCurve::get_bpath", this);
256 return _bpath;
257 };
259 Geom::PathVector const &
260 SPCurve::get_pathvector() const
261 {
262 debug_check("SPCurve::get_pathvector", this);
263 return _pathv;
264 }
266 /**
267 *Returns index in bpath[] of NR_END element.
268 * remove for 2geom
269 */
270 guint
271 SPCurve::get_length() const
272 {
273 // g_message("SPCurve::get_length must be removed");
275 return _end;
276 }
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()
286 {
287 g_return_val_if_fail(this != NULL, NULL);
289 _refcount += 1;
291 return this;
292 }
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()
302 {
303 g_return_val_if_fail(this != NULL, NULL);
305 _refcount -= 1;
307 if (_refcount < 1) {
308 delete this;
309 }
311 return NULL;
312 }
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)
321 {
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;
334 }
336 /**
337 * Create new curve from its own bpath array.
338 * 2GEOMproof
339 */
340 SPCurve *
341 SPCurve::copy() const
342 {
343 g_return_val_if_fail(this != NULL, NULL);
345 return SPCurve::new_from_foreign_bpath(_bpath);
346 }
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)
354 {
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;
394 }
396 /**
397 * Returns a list of new curves corresponding to the subpaths in \a curve.
398 * 2geomified
399 */
400 GSList *
401 SPCurve::split() const
402 {
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;
428 }
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)
436 {
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 }
458 }
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)
466 {
467 tmpl_curve_transform<NR::Matrix>(this, m);
469 _pathv = _pathv * to_2geom(m);
471 debug_check("SPCurve::transform(NR::Matrix const &m)", this);
472 }
474 /**
475 * Transform all paths in curve using matrix.
476 */
477 void
478 SPCurve::transform(Geom::Matrix const &m)
479 {
480 tmpl_curve_transform<NR::Matrix>(this, from_2geom(m));
482 _pathv = _pathv * m;
484 debug_check("SPCurve::transform(Geom::Matrix const &m)", this);
485 }
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)
493 {
494 tmpl_curve_transform<NR::translate>(this, m);
496 _pathv = _pathv * to_2geom(m);
498 debug_check("SPCurve::transform(NR::translate const &m)", this);
499 }
501 /**
502 * Set curve to empty curve.
503 * 2GEOMified
504 */
505 void
506 SPCurve::reset()
507 {
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);
521 }
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)
530 {
531 moveto(NR::Point(x, y));
532 }
533 /**
534 * Calls SPCurve::moveto() with point made of given coordinates.
535 */
536 void
537 SPCurve::moveto(Geom::Point const &p)
538 {
539 moveto(from_2geom(p));
540 }
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)
547 {
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);
560 }
562 /**
563 * Calls SPCurve::lineto() with a point's coordinates.
564 */
565 void
566 SPCurve::lineto(Geom::Point const &p)
567 {
568 lineto(p[Geom::X], p[Geom::Y]);
569 }
570 /**
571 * Calls SPCurve::lineto() with a point's coordinates.
572 */
573 void
574 SPCurve::lineto(NR::Point const &p)
575 {
576 lineto(p[NR::X], p[NR::Y]);
577 }
578 /**
579 * Adds a line to the current subpath.
580 * 2GEOMified
581 */
582 void
583 SPCurve::lineto(gdouble x, gdouble y)
584 {
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);
638 }
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)
645 {
646 using Geom::X;
647 using Geom::Y;
648 curveto( p0[X], p0[Y],
649 p1[X], p1[Y],
650 p2[X], p2[Y] );
651 }
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)
657 {
658 using NR::X;
659 using NR::Y;
660 curveto( p0[X], p0[Y],
661 p1[X], p1[Y],
662 p2[X], p2[Y] );
663 }
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)
670 {
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);
716 }
718 /**
719 * Close current subpath by possibly adding a line between start and end.
720 * 2GEOMified
721 */
722 void
723 SPCurve::closepath()
724 {
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);
775 }
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.
782 2GEOMified
783 **/
784 void
785 SPCurve::closepath_current()
786 {
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);
835 }
837 /**
838 * True if no paths are in curve.
839 * 2GEOMproof
840 */
841 bool
842 SPCurve::is_empty() const
843 {
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);
853 }
855 /**
856 * True iff all subpaths are closed.
857 * 2GEOMproof
858 */
859 bool
860 SPCurve::is_closed() const
861 {
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;
872 }
874 /**
875 * Return last subpath or NULL.
876 */
877 NArtBpath const *
878 SPCurve::last_bpath() const
879 {
880 g_return_val_if_fail(this != NULL, NULL);
882 if (_end == 0) {
883 return NULL;
884 }
886 return _bpath + _end - 1;
887 }
889 /**
890 * Return first subpath or NULL.
891 */
892 NArtBpath const *
893 SPCurve::first_bpath() const
894 {
895 g_return_val_if_fail(this != NULL, NULL);
897 if (_end == 0) {
898 return NULL;
899 }
901 return _bpath;
902 }
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
909 {
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() );
919 }
921 /**
922 * Return the second point of first subpath or _movePos if curve too short.
923 */
924 NR::Point
925 SPCurve::second_point() const
926 {
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);
944 }
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
951 {
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));
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);
971 }
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
978 {
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() );
987 }
989 inline static bool
990 is_moveto(NRPathcode const c)
991 {
992 return c == NR_MOVETO || c == NR_MOVETO_OPEN;
993 }
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
1004 {
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);
1046 }
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)
1057 {
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 */
1133 }
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)
1141 {
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;
1197 }
1199 /**
1200 * Remove last segment of curve.
1201 * (Only used once in /src/pen-context.cpp)
1202 * 2GEOMified
1203 */
1204 void
1205 SPCurve::backspace()
1206 {
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);
1235 }
1237 /* Private methods */
1239 /**
1240 * Returns index of first NR_END bpath in array.
1241 */
1242 static unsigned sp_bpath_length(NArtBpath const bpath[])
1243 {
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;
1253 }
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[])
1266 {
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;
1276 }
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)
1287 {
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;
1313 }
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[])
1320 {
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;
1353 }
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[])
1361 {
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 }
1372 }
1374 /**
1375 * 2GEOMified
1376 */
1377 void
1378 SPCurve::stretch_endpoints(NR::Point const &new_p0, NR::Point const &new_p1)
1379 {
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);
1435 }
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)
1443 {
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);
1457 }
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
1465 {
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;
1483 }
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)
1491 {
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);
1518 }
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 :