1 #define __CURVE_C__
3 /** \file
4 * Routines for SPCurve and for its Geom::PathVector
5 */
7 /*
8 * Authors:
9 * Lauris Kaplinski <lauris@kaplinski.com>
10 * Johan Engelen
11 *
12 * Copyright (C) 2000 Lauris Kaplinski
13 * Copyright (C) 2000-2001 Ximian, Inc.
14 * Copyright (C) 2002 Lauris Kaplinski
15 * Copyright (C) 2008 Johan Engelen
16 *
17 * Released under GNU GPL
18 */
20 #include "display/curve.h"
22 #include <string.h>
23 #include "libnr/nr-point.h"
24 #include "libnr/nr-rect.h"
25 #include <libnr/nr-point-matrix-ops.h>
26 #include <libnr/nr-convert2geom.h>
27 #include <cstring>
28 #include <string>
29 #include <2geom/pathvector.h>
30 #include <2geom/sbasis-geometric.h>
31 #include <2geom/sbasis-to-bezier.h>
32 #include "svg/svg.h"
33 #include <2geom/point.h>
35 /* Constructors */
37 /**
38 * The returned curve's state is as if SPCurve::reset has just been called on it.
39 * \param length Initial number of NArtBpath elements allocated for bpath (including NR_END
40 * element).
41 * 2GEOMproof
42 */
43 SPCurve::SPCurve(guint length)
44 : _refcount(1),
45 _pathv()
46 {
47 if (length <= 0) {
48 g_error("SPCurve::SPCurve called with invalid length parameter");
49 throw;
50 }
52 _pathv.clear();
53 }
55 SPCurve::SPCurve(Geom::PathVector const& pathv)
56 : _refcount(1),
57 _pathv(pathv)
58 {
59 }
61 SPCurve *
62 SPCurve::new_from_rect(Geom::Rect const &rect)
63 {
64 SPCurve *c = new SPCurve();
66 NR::Point p = rect.corner(0);
67 c->moveto(p);
69 for (int i=3; i>=0; i--) {
70 c->lineto(rect.corner(i));
71 }
72 c->closepath_current();
74 return c;
75 }
77 SPCurve::~SPCurve()
78 {
79 }
81 /* Methods */
83 void
84 SPCurve::set_pathvector(Geom::PathVector const & new_pathv)
85 {
86 _pathv = new_pathv;
87 }
89 Geom::PathVector const &
90 SPCurve::get_pathvector() const
91 {
92 return _pathv;
93 }
95 /*
96 * Returns the number of segments of all paths summed
97 * This count includes the closing line segment of a closed path.
98 */
99 guint
100 SPCurve::get_segment_count() const
101 {
102 guint nr = 0;
103 for(Geom::PathVector::const_iterator it = _pathv.begin(); it != _pathv.end(); ++it) {
104 nr += (*it).size();
106 if (it->closed()) nr += 1;
107 }
108 return nr;
109 }
111 /**
112 * Increase _refcount of curve.
113 *
114 * \todo should this be shared with other refcounting code?
115 */
116 SPCurve *
117 SPCurve::ref()
118 {
119 _refcount += 1;
121 return this;
122 }
124 /**
125 * Decrease refcount of curve, with possible destruction.
126 *
127 * \todo should this be shared with other refcounting code?
128 */
129 SPCurve *
130 SPCurve::unref()
131 {
132 _refcount -= 1;
134 if (_refcount < 1) {
135 delete this;
136 }
138 return NULL;
139 }
141 /**
142 * Create new curve from this curve's pathvector array.
143 */
144 SPCurve *
145 SPCurve::copy() const
146 {
147 return new SPCurve(_pathv);
148 }
150 /**
151 * Return new curve that is the concatenation of all curves in list.
152 */
153 SPCurve *
154 SPCurve::concat(GSList const *list)
155 {
156 SPCurve *new_curve = new SPCurve();
158 for (GSList const *l = list; l != NULL; l = l->next) {
159 SPCurve *c = (SPCurve *) l->data;
160 new_curve->_pathv.insert( new_curve->_pathv.end(), c->get_pathvector().begin(), c->get_pathvector().end() );
161 }
163 return new_curve;
164 }
166 /**
167 * Returns a list of new curves corresponding to the subpaths in \a curve.
168 * 2geomified
169 */
170 GSList *
171 SPCurve::split() const
172 {
173 GSList *l = NULL;
175 for (Geom::PathVector::const_iterator path_it = _pathv.begin(); path_it != _pathv.end(); ++path_it) {
176 Geom::PathVector newpathv;
177 newpathv.push_back(*path_it);
178 SPCurve * newcurve = new SPCurve(newpathv);
179 l = g_slist_prepend(l, newcurve);
180 }
182 return l;
183 }
186 void
187 SPCurve::transform(NR::Matrix const &m)
188 {
189 transform(to_2geom(m));
190 };
193 /**
194 * Transform all paths in curve using matrix.
195 */
196 void
197 SPCurve::transform(Geom::Matrix const &m)
198 {
199 _pathv = _pathv * m;
200 }
202 /**
203 * Set curve to empty curve.
204 */
205 void
206 SPCurve::reset()
207 {
208 _pathv.clear();
209 }
211 /** Several consecutive movetos are ALLOWED
212 * Ref: http://www.w3.org/TR/SVG11/implnote.html#PathElementImplementationNotes
213 * (first subitem of the item about zero-length path segments) */
215 /**
216 * Calls SPCurve::moveto() with point made of given coordinates.
217 */
218 void
219 SPCurve::moveto(gdouble x, gdouble y)
220 {
221 moveto(NR::Point(x, y));
222 }
223 /**
224 * Calls SPCurve::moveto() with point made of given coordinates.
225 */
226 void
227 SPCurve::moveto(Geom::Point const &p)
228 {
229 moveto(from_2geom(p));
230 }
231 /**
232 * Perform a moveto to a point, thus starting a new subpath.
233 */
234 void
235 SPCurve::moveto(NR::Point const &p)
236 {
237 _pathv.push_back( Geom::Path() ); // for some reason Geom::Path(p) does not work...
238 _pathv.back().start(to_2geom(p));
239 }
241 /**
242 * Calls SPCurve::lineto() with a point's coordinates.
243 */
244 void
245 SPCurve::lineto(Geom::Point const &p)
246 {
247 lineto(p[Geom::X], p[Geom::Y]);
248 }
249 /**
250 * Calls SPCurve::lineto() with a point's coordinates.
251 */
252 void
253 SPCurve::lineto(NR::Point const &p)
254 {
255 lineto(p[NR::X], p[NR::Y]);
256 }
257 /**
258 * Adds a line to the current subpath.
259 */
260 void
261 SPCurve::lineto(gdouble x, gdouble y)
262 {
263 if (_pathv.empty()) g_message("leeg");
264 else _pathv.back().appendNew<Geom::LineSegment>( Geom::Point(x,y) );
265 }
267 /**
268 * Calls SPCurve::curveto() with coordinates of three points.
269 */
270 void
271 SPCurve::curveto(Geom::Point const &p0, Geom::Point const &p1, Geom::Point const &p2)
272 {
273 using Geom::X;
274 using Geom::Y;
275 curveto( p0[X], p0[Y],
276 p1[X], p1[Y],
277 p2[X], p2[Y] );
278 }
279 /**
280 * Calls SPCurve::curveto() with coordinates of three points.
281 */
282 void
283 SPCurve::curveto(NR::Point const &p0, NR::Point const &p1, NR::Point const &p2)
284 {
285 using NR::X;
286 using NR::Y;
287 curveto( p0[X], p0[Y],
288 p1[X], p1[Y],
289 p2[X], p2[Y] );
290 }
291 /**
292 * Adds a bezier segment to the current subpath.
293 * 2GEOMified
294 */
295 void
296 SPCurve::curveto(gdouble x0, gdouble y0, gdouble x1, gdouble y1, gdouble x2, gdouble y2)
297 {
298 if (_pathv.empty()) g_message("leeg");
299 else _pathv.back().appendNew<Geom::CubicBezier>( Geom::Point(x0,y0), Geom::Point(x1,y1), Geom::Point(x2,y2) );
300 }
302 /**
303 * Close current subpath by possibly adding a line between start and end.
304 */
305 void
306 SPCurve::closepath()
307 {
308 _pathv.back().close(true);
309 }
311 /** Like SPCurve::closepath() but sets the end point of the last subpath
312 to the subpath start point instead of adding a new lineto.
314 Used for freehand drawing when the user draws back to the start point.
315 **/
316 void
317 SPCurve::closepath_current()
318 {
319 _pathv.back().setFinal(_pathv.back().initialPoint());
320 _pathv.back().close(true);
321 }
323 /**
324 * True if no paths are in curve. If it only contains a path with only a moveto, the path is considered NON-empty
325 */
326 bool
327 SPCurve::is_empty() const
328 {
329 return _pathv.empty();
330 }
332 /**
333 * True iff all subpaths are closed.
334 * Returns false if the curve is empty.
335 */
336 bool
337 SPCurve::is_closed() const
338 {
339 if (is_empty()) {
340 return false;
341 } else {
342 bool closed = true;
343 for (Geom::PathVector::const_iterator it = _pathv.begin(); it != _pathv.end(); it++) {
344 if ( ! it->closed() ) {
345 closed = false;
346 break;
347 }
348 }
349 return closed;
350 }
351 }
353 /**
354 * Return last pathsegment (possibly the closing path segment) of the last path in PathVector or NULL.
355 * If the last path is empty (contains only a moveto), the function returns NULL
356 */
357 Geom::Curve const *
358 SPCurve::last_segment() const
359 {
360 if (is_empty()) {
361 return NULL;
362 }
363 if (_pathv.back().empty()) {
364 return NULL;
365 }
367 return &_pathv.back().back_default();
368 }
370 /**
371 * Return last path in PathVector or NULL.
372 */
373 Geom::Path const *
374 SPCurve::last_path() const
375 {
376 if (is_empty()) {
377 return NULL;
378 }
380 return &_pathv.back();
381 }
383 /**
384 * Return first pathsegment in PathVector or NULL.
385 * equal in functionality to SPCurve::first_bpath()
386 */
387 Geom::Curve const *
388 SPCurve::first_segment() const
389 {
390 if (is_empty()) {
391 return NULL;
392 }
393 if (_pathv.front().empty()) {
394 return NULL;
395 }
397 return &_pathv.front().front();
398 }
400 /**
401 * Return first path in PathVector or NULL.
402 */
403 Geom::Path const *
404 SPCurve::first_path() const
405 {
406 if (is_empty()) {
407 return NULL;
408 }
410 return &_pathv.front();
411 }
413 /**
414 * 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) ?
415 */
416 NR::Point
417 SPCurve::first_point() const
418 {
419 if (is_empty())
420 return NR::Point(0, 0);
422 return from_2geom( _pathv.front().initialPoint() );
423 }
425 /**
426 * Return the second point of first subpath or _movePos if curve too short.
427 * If the pathvector is empty, this returns (0,0). If the first path is only a moveto, this method
428 * returns the first point of the second path, if it exists. If there is no 2nd path, it returns the
429 * first point of the first path.
430 *
431 * FIXME: for empty paths shouldn't this return (NR_HUGE,NR_HUGE)
432 */
433 NR::Point
434 SPCurve::second_point() const
435 {
436 if (is_empty()) {
437 return NR::Point(0,0);
438 }
439 else if (_pathv.front().empty()) {
440 // first path is only a moveto
441 // check if there is second path
442 if (_pathv.size() > 1) {
443 return _pathv[1].initialPoint();
444 } else {
445 return _pathv[0].initialPoint();
446 }
447 }
448 else
449 return _pathv.front()[0].finalPoint();
450 }
452 /**
453 * TODO: fix comment: Return the second-last point of last subpath or _movePos if curve too short.
454 */
455 NR::Point
456 SPCurve::penultimate_point() const
457 {
458 Geom::Curve const& back = _pathv.back().back_default();
459 Geom::Point p = back.initialPoint();
460 return from_2geom(p);
461 }
463 /**
464 * 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) ?
465 * If the last path is only a moveto, then return that point.
466 */
467 NR::Point
468 SPCurve::last_point() const
469 {
470 if (is_empty())
471 return NR::Point(0, 0);
473 return from_2geom( _pathv.back().finalPoint() );
474 }
476 /**
477 * Returns a *new* \a curve but drawn in the opposite direction.
478 * Should result in the same shape, but
479 * with all its markers drawn facing the other direction.
480 * Reverses the order of subpaths as well
481 **/
482 SPCurve *
483 SPCurve::create_reverse() const
484 {
485 SPCurve *new_curve = new SPCurve(Geom::reverse_paths_and_order(_pathv));
487 return new_curve;
488 }
490 /**
491 * Append \a curve2 to \a this.
492 * If \a use_lineto is false, simply add all paths in \a curve2 to \a this;
493 * 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.
494 */
495 void
496 SPCurve::append(SPCurve const *curve2,
497 bool use_lineto)
498 {
499 if (curve2->is_empty())
500 return;
502 if (use_lineto) {
503 Geom::PathVector::const_iterator it = curve2->_pathv.begin();
504 if ( ! _pathv.empty() ) {
505 Geom::Path & lastpath = _pathv.back();
506 lastpath.appendNew<Geom::LineSegment>( (*it).initialPoint() );
507 lastpath.append( (*it) );
508 } else {
509 _pathv.push_back( (*it) );
510 }
512 for (it++; it != curve2->_pathv.end(); it++) {
513 _pathv.push_back( (*it) );
514 }
515 } else {
516 for (Geom::PathVector::const_iterator it = curve2->_pathv.begin(); it != curve2->_pathv.end(); it++) {
517 _pathv.push_back( (*it) );
518 }
519 }
520 }
522 /**
523 * Append \a c1 to \a this with possible fusing of close endpoints. If the end of this curve and the start of c1 are within tolerance distance,
524 * then the startpoint of c1 is moved to the end of this curve and the first subpath of c1 is appended to the last subpath of this curve.
525 * When one of the curves (this curve or the argument curve) is closed, the returned value is NULL; otherwise the returned value is this curve.
526 * When one of the curves is empty, this curves path becomes the non-empty path.
527 */
528 SPCurve *
529 SPCurve::append_continuous(SPCurve const *c1, gdouble tolerance)
530 {
531 using Geom::X;
532 using Geom::Y;
534 g_return_val_if_fail(c1 != NULL, NULL);
535 if ( this->is_closed() || c1->is_closed() ) {
536 return NULL;
537 }
539 if (c1->is_empty()) {
540 return this;
541 }
543 if (this->is_empty()) {
544 _pathv = c1->_pathv;
545 return this;
546 }
548 if ( (fabs(this->last_point()[X] - c1->first_point()[X]) <= tolerance)
549 && (fabs(this->last_point()[Y] - c1->first_point()[Y]) <= tolerance) )
550 {
551 // c1's first subpath can be appended to this curve's last subpath
552 Geom::PathVector::const_iterator path_it = c1->_pathv.begin();
553 Geom::Path & lastpath = _pathv.back();
555 Geom::Path newfirstpath(*path_it);
556 newfirstpath.setInitial(lastpath.finalPoint());
557 lastpath.append( newfirstpath );
559 for (path_it++; path_it != c1->_pathv.end(); path_it++) {
560 _pathv.push_back( (*path_it) );
561 }
563 } else {
564 append(c1, false);
565 }
567 return this;
568 }
570 /**
571 * Remove last segment of curve.
572 * (Only used once in /src/pen-context.cpp)
573 */
574 void
575 SPCurve::backspace()
576 {
577 if ( is_empty() )
578 return;
580 if ( !_pathv.back().empty() ) {
581 _pathv.back().erase_last();
582 _pathv.back().close(false);
583 }
584 }
586 /**
587 * TODO: add comments about what this method does and what assumptions are made and requirements are put on SPCurve
588 */
589 void
590 SPCurve::stretch_endpoints(NR::Point const &new_p0, NR::Point const &new_p1)
591 {
592 if (is_empty()) {
593 return;
594 }
596 NR::Point const offset0( new_p0 - first_point() );
597 NR::Point const offset1( new_p1 - last_point() );
599 Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2 = _pathv.front().toPwSb();
600 Geom::Piecewise<Geom::SBasis> arclength = Geom::arcLengthSb(pwd2);
601 if ( arclength.lastValue() <= 0 ) {
602 g_error("SPCurve::stretch_endpoints - arclength <= 0");
603 throw;
604 }
605 arclength *= 1./arclength.lastValue();
606 Geom::Point const A( to_2geom(offset0) );
607 Geom::Point const B( to_2geom(offset1) );
608 Geom::Piecewise<Geom::SBasis> offsetx = (arclength*-1.+1)*A[0] + arclength*B[0];
609 Geom::Piecewise<Geom::SBasis> offsety = (arclength*-1.+1)*A[1] + arclength*B[1];
610 Geom::Piecewise<Geom::D2<Geom::SBasis> > offsetpath = Geom::sectionize( Geom::D2<Geom::Piecewise<Geom::SBasis> >(offsetx, offsety) );
611 pwd2 += offsetpath;
612 _pathv = Geom::path_from_piecewise( pwd2, 0.001 );
613 }
615 /**
616 * sets start of first path to new_p0, and end of first path to new_p1
617 */
618 void
619 SPCurve::move_endpoints(NR::Point const &new_p0, NR::Point const &new_p1)
620 {
621 if (is_empty()) {
622 return;
623 }
624 _pathv.front().setInitial(to_2geom(new_p0));
625 _pathv.front().setFinal(to_2geom(new_p1));
626 }
628 /**
629 * returns the number of nodes in a path, used for statusbar text when selecting an spcurve.
630 */
631 guint
632 SPCurve::nodes_in_path() const
633 {
634 guint nr = 0;
635 for(Geom::PathVector::const_iterator it = _pathv.begin(); it != _pathv.end(); ++it) {
636 nr += (*it).size();
638 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
639 }
641 return nr;
642 }
644 /**
645 * Adds p to the last point (and last handle if present) of the last path
646 */
647 void
648 SPCurve::last_point_additive_move(Geom::Point const & p)
649 {
650 if (is_empty()) {
651 return;
652 }
654 _pathv.back().setFinal( _pathv.back().finalPoint() + p );
656 // Move handle as well when the last segment is a cubic bezier segment:
657 // TODO: what to do for quadratic beziers?
658 if ( Geom::CubicBezier const *lastcube = dynamic_cast<Geom::CubicBezier const *>(&_pathv.back().back()) ) {
659 Geom::CubicBezier newcube( *lastcube );
660 newcube.setPoint(2, newcube[2] + p);
661 _pathv.back().replace( --_pathv.back().end(), newcube );
662 }
663 }
665 /*
666 Local Variables:
667 mode:c++
668 c-file-style:"stroustrup"
669 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
670 indent-tabs-mode:nil
671 fill-column:99
672 End:
673 */
674 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :