1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Base class for shapes, including <path> element
4  *
5  * Author:
6  *   Lauris Kaplinski <lauris@kaplinski.com>
7  *   Abhishek Sharma
8  *
9  * Copyright (C) 1999-2002 Lauris Kaplinski
10  * Copyright (C) 2000-2001 Ximian, Inc.
11  * Copyright (C) 2004 John Cliff
12  * Copyright (C) 2007-2008 Johan Engelen
13  * Copyright (C) 2010      Jon A. Cruz <jon@joncruz.org>
14  *
15  * Released under GNU GPL v2+, read the file 'COPYING' for more information.
16  */
17 
18 #include <2geom/rect.h>
19 #include <2geom/transforms.h>
20 #include <2geom/pathvector.h>
21 #include <2geom/path-intersection.h>
22 #include "helper/geom.h"
23 #include "helper/geom-nodetype.h"
24 
25 #include <sigc++/functors/ptr_fun.h>
26 #include <sigc++/adaptors/bind.h>
27 
28 #include "display/drawing-shape.h"
29 #include "display/curve.h"
30 #include "print.h"
31 #include "document.h"
32 #include "style.h"
33 #include "sp-marker.h"
34 #include "sp-root.h"
35 #include "sp-path.h"
36 #include "preferences.h"
37 #include "attributes.h"
38 #include "path/path-outline.h" // For bound box calculation
39 
40 #include "svg/svg.h"
41 #include "svg/path-string.h"
42 #include "live_effects/lpeobject.h"
43 
44 #include "helper/mathfns.h" // for triangle_area()
45 
46 #define noSHAPE_VERBOSE
47 
48 static void sp_shape_update_marker_view (SPShape *shape, Inkscape::DrawingItem *ai);
49 
SPShape()50 SPShape::SPShape() : SPLPEItem() {
51     for (auto & i : this->_marker) {
52         i = nullptr;
53     }
54 
55     this->_curve = nullptr;
56     this->_curve_before_lpe = nullptr;
57 }
58 
~SPShape()59 SPShape::~SPShape() {
60     for ( int i = 0 ; i < SP_MARKER_LOC_QTY ; i++ ) {
61         this->_release_connect[i].disconnect();
62         this->_modified_connect[i].disconnect();
63     }
64 }
65 
build(SPDocument * document,Inkscape::XML::Node * repr)66 void SPShape::build(SPDocument *document, Inkscape::XML::Node *repr) {
67     SPLPEItem::build(document, repr);
68 
69     for (int i = 0 ; i < SP_MARKER_LOC_QTY ; i++) {
70         sp_shape_set_marker (this, i, this->style->marker_ptrs[i]->value());
71     }
72 }
73 
74 
75 /**
76  * Removes, releases and unrefs all children of object
77  *
78  * This is the inverse of sp_shape_build().  It must be invoked as soon
79  * as the shape is removed from the tree, even if it is still referenced
80  * by other objects.  This routine also disconnects/unrefs markers and
81  * curves attached to it.
82  *
83  * \see SPObject::release()
84  */
release()85 void SPShape::release() {
86     for (int i = 0; i < SP_MARKER_LOC_QTY; i++) {
87         if (this->_marker[i]) {
88 
89             for (SPItemView *v = this->display; v != nullptr; v = v->next) {
90                 sp_marker_hide(_marker[i], v->arenaitem->key() + i);
91             }
92 
93             this->_release_connect[i].disconnect();
94             this->_modified_connect[i].disconnect();
95             _marker[i]->unhrefObject(this);
96             _marker[i] = nullptr;
97         }
98     }
99 
100     _curve.reset();
101 
102     _curve_before_lpe.reset();
103 
104     SPLPEItem::release();
105 }
106 
set(SPAttr key,const gchar * value)107 void SPShape::set(SPAttr key, const gchar* value) {
108 	SPLPEItem::set(key, value);
109 }
110 
111 
write(Inkscape::XML::Document * xml_doc,Inkscape::XML::Node * repr,guint flags)112 Inkscape::XML::Node* SPShape::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) {
113 	SPLPEItem::write(xml_doc, repr, flags);
114 	return repr;
115 }
116 
update(SPCtx * ctx,guint flags)117 void SPShape::update(SPCtx* ctx, guint flags) {
118     // Any update can change the bounding box,
119     // so the cached version can no longer be used.
120     // But the idle checker usually is just moving the objects around.
121     bbox_vis_cache_is_valid = false;
122     bbox_geom_cache_is_valid = false;
123 
124     // std::cout << "SPShape::update(): " << (getId()?getId():"null") << std::endl;
125     SPLPEItem::update(ctx, flags);
126 
127     /* This stanza checks that an object's marker style agrees with
128      * the marker objects it has allocated.  sp_shape_set_marker ensures
129      * that the appropriate marker objects are present (or absent) to
130      * match the style.
131      */
132     for (int i = 0 ; i < SP_MARKER_LOC_QTY ; i++) {
133         sp_shape_set_marker (this, i, this->style->marker_ptrs[i]->value());
134     }
135 
136     if (flags & (SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) {
137         if (this->style->stroke_width.unit == SP_CSS_UNIT_PERCENT) {
138             SPItemCtx *ictx = (SPItemCtx *) ctx;
139             double const aw = 1.0 / ictx->i2vp.descrim();
140             this->style->stroke_width.computed = this->style->stroke_width.value * aw;
141 
142             for (SPItemView *v = ((SPItem *) (this))->display; v != nullptr; v = v->next) {
143                 Inkscape::DrawingShape *sh = dynamic_cast<Inkscape::DrawingShape *>(v->arenaitem);
144                 if (hasMarkers()) {
145                     this->context_style = this->style;
146                     sh->setStyle(this->style, this->context_style);
147                     // Done at end:
148                     // sh->setChildrenStyle(this->context_style); //Resolve 'context-xxx' in children.
149                 } else if (this->parent) {
150                     this->context_style = this->parent->context_style;
151                     sh->setStyle(this->style, this->context_style);
152                 }
153             }
154         }
155     }
156 
157     if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_PARENT_MODIFIED_FLAG)) {
158         /* This is suboptimal, because changing parent style schedules recalculation */
159         /* But on the other hand - how can we know that parent does not tie style and transform */
160         for (SPItemView *v = this->display; v != nullptr; v = v->next) {
161             Inkscape::DrawingShape *sh = dynamic_cast<Inkscape::DrawingShape *>(v->arenaitem);
162 
163             if (flags & SP_OBJECT_MODIFIED_FLAG) {
164                 sh->setPath(this->_curve.get());
165             }
166         }
167     }
168 
169     if (this->hasMarkers ()) {
170 
171         /* Dimension marker views */
172         for (SPItemView *v = this->display; v != nullptr; v = v->next) {
173             if (!v->arenaitem->key()) {
174                 v->arenaitem->setKey(SPItem::display_key_new (SP_MARKER_LOC_QTY));
175             }
176 
177             for (int i = 0 ; i < SP_MARKER_LOC_QTY ; i++) {
178                 if (_marker[i]) {
179                     sp_marker_show_dimension(_marker[i],
180                                              v->arenaitem->key() + i,
181                                              numberOfMarkers(i));
182                 }
183             }
184         }
185 
186         /* Update marker views */
187         for (SPItemView *v = this->display; v != nullptr; v = v->next) {
188             sp_shape_update_marker_view (this, v->arenaitem);
189         }
190 
191         // Marker selector needs this here or marker previews are not rendered.
192         for (SPItemView *v = this->display; v != nullptr; v = v->next) {
193             Inkscape::DrawingShape *sh = dynamic_cast<Inkscape::DrawingShape *>(v->arenaitem);
194 
195             sh->setChildrenStyle(this->context_style); // Resolve 'context-xxx' in children.
196         }
197     }
198 
199     /* Update stroke/dashes for relative units. */
200     if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) {
201 
202         SPItemCtx const *ictx = reinterpret_cast<SPItemCtx const *>(ctx);
203 
204         double const w = ictx->viewport.width();
205         double const h = ictx->viewport.height();
206         double const d = sqrt(w*w + h*h) * M_SQRT1_2; // diagonal per SVG spec
207         double const em = style->font_size.computed;
208         double const ex = 0.5 * em;  // fixme: get x height from pango or libnrtype.
209 
210         if (style->stroke_width.unit == SP_CSS_UNIT_EM) {
211             style->stroke_width.computed = style->stroke_width.value * em;
212         }
213         else if (style->stroke_width.unit == SP_CSS_UNIT_EX) {
214             style->stroke_width.computed = style->stroke_width.value * ex;
215         }
216         else if (style->stroke_width.unit == SP_CSS_UNIT_PERCENT) {
217             style->stroke_width.computed = style->stroke_width.value * d;
218         }
219 
220         if (style->stroke_dasharray.values.size() != 0) {
221             for (auto&& i: style->stroke_dasharray.values) {
222                 if      (i.unit == SP_CSS_UNIT_EM)      i.computed = i.value * em;
223                 else if (i.unit == SP_CSS_UNIT_EX)      i.computed = i.value * ex;
224                 else if (i.unit == SP_CSS_UNIT_PERCENT) i.computed = i.value * d;
225             }
226         }
227 
228         if (style->stroke_dashoffset.unit == SP_CSS_UNIT_EM) {
229             style->stroke_dashoffset.computed = style->stroke_dashoffset.value * em;
230         }
231         else if (style->stroke_dashoffset.unit == SP_CSS_UNIT_EX) {
232             style->stroke_dashoffset.computed = style->stroke_dashoffset.value * ex;
233         }
234         else if (style->stroke_dashoffset.unit == SP_CSS_UNIT_PERCENT) {
235             style->stroke_dashoffset.computed = style->stroke_dashoffset.value * d;
236         }
237     }
238 }
239 
240 /**
241  * Calculate the transform required to get a marker's path object in the
242  * right place for particular path segment on a shape.
243  *
244  * \see sp_shape_marker_update_marker_view.
245  *
246  * From SVG spec:
247  * The axes of the temporary new user coordinate system are aligned according to the orient attribute on the 'marker'
248  * element and the slope of the curve at the given vertex. (Note: if there is a discontinuity at a vertex, the slope
249  * is the average of the slopes of the two segments of the curve that join at the given vertex. If a slope cannot be
250  * determined, the slope is assumed to be zero.)
251  *
252  * Reference: http://www.w3.org/TR/SVG11/painting.html#MarkerElement, the `orient' attribute.
253  * Reference for behaviour of zero-length segments:
254  * http://www.w3.org/TR/SVG11/implnote.html#PathElementImplementationNotes
255  */
sp_shape_marker_get_transform(Geom::Curve const & c1,Geom::Curve const & c2)256 Geom::Affine sp_shape_marker_get_transform(Geom::Curve const & c1, Geom::Curve const & c2)
257 {
258     Geom::Point p = c1.pointAt(1);
259     Geom::Curve * c1_reverse = c1.reverse();
260     Geom::Point tang1 = - c1_reverse->unitTangentAt(0);
261     delete c1_reverse;
262     Geom::Point tang2 = c2.unitTangentAt(0);
263 
264     double const angle1 = Geom::atan2(tang1);
265     double const angle2 = Geom::atan2(tang2);
266 
267     double ret_angle = .5 * (angle1 + angle2);
268 
269     if ( fabs( angle2 - angle1 ) > M_PI ) {
270         /* ret_angle is in the middle of the larger of the two sectors between angle1 and
271          * angle2, so flip it by 180degrees to force it to the middle of the smaller sector.
272          *
273          * (Imagine a circle with rays drawn at angle1 and angle2 from the centre of the
274          * circle.  Those two rays divide the circle into two sectors.)
275          */
276         ret_angle += M_PI;
277     }
278 
279     return Geom::Rotate(ret_angle) * Geom::Translate(p);
280 }
281 
sp_shape_marker_get_transform_at_start(Geom::Curve const & c)282 Geom::Affine sp_shape_marker_get_transform_at_start(Geom::Curve const & c)
283 {
284     Geom::Point p = c.pointAt(0);
285     Geom::Affine ret = Geom::Translate(p);
286 
287     if ( !c.isDegenerate() ) {
288         Geom::Point tang = c.unitTangentAt(0);
289         double const angle = Geom::atan2(tang);
290         ret = Geom::Rotate(angle) * Geom::Translate(p);
291     } else {
292         /* FIXME: the svg spec says to search for a better alternative than zero angle directionality:
293          * http://www.w3.org/TR/SVG11/implnote.html#PathElementImplementationNotes */
294     }
295 
296     return ret;
297 }
298 
sp_shape_marker_get_transform_at_end(Geom::Curve const & c)299 Geom::Affine sp_shape_marker_get_transform_at_end(Geom::Curve const & c)
300 {
301     Geom::Point p = c.pointAt(1);
302     Geom::Affine ret = Geom::Translate(p);
303 
304     if ( !c.isDegenerate() ) {
305         Geom::Curve * c_reverse = c.reverse();
306         Geom::Point tang = - c_reverse->unitTangentAt(0);
307         delete c_reverse;
308         double const angle = Geom::atan2(tang);
309         ret = Geom::Rotate(angle) * Geom::Translate(p);
310     } else {
311         /* FIXME: the svg spec says to search for a better alternative than zero angle directionality:
312          * http://www.w3.org/TR/SVG11/implnote.html#PathElementImplementationNotes */
313     }
314 
315     return ret;
316 }
317 
318 /**
319  * Updates the instances (views) of a given marker in a shape.
320  * Marker views have to be scaled already.  The transformation
321  * is retrieved and then shown by calling sp_marker_show_instance.
322  *
323  * @todo figure out what to do when both 'marker' and for instance 'marker-end' are set.
324  */
325 static void
sp_shape_update_marker_view(SPShape * shape,Inkscape::DrawingItem * ai)326 sp_shape_update_marker_view(SPShape *shape, Inkscape::DrawingItem *ai)
327 {
328     // position arguments to sp_marker_show_instance, basically counts the amount of markers.
329     int counter[4] = {0};
330 
331     if (!shape->curve())
332         return;
333 
334     Geom::PathVector const &pathv = shape->curve()->get_pathvector();
335     if (pathv.empty()) return;
336 
337     // the first vertex should get a start marker, the last an end marker, and all the others a mid marker
338     // see bug 456148
339 
340     // START marker
341     {
342         Geom::Affine const m (sp_shape_marker_get_transform_at_start(pathv.begin()->front()));
343         for (int i = 0; i < 2; i++) {  // SP_MARKER_LOC and SP_MARKER_LOC_START
344             if ( shape->_marker[i] ) {
345                 Geom::Affine m_auto = m;
346                 // Reverse start marker if necessary.
347                 if (shape->_marker[i]->orient_mode == MARKER_ORIENT_AUTO_START_REVERSE) {
348                     m_auto = Geom::Rotate::from_degrees( 180.0 ) * m;
349                 }
350                 sp_marker_show_instance(shape->_marker[i], ai,
351                                         ai->key() + i, counter[i], m_auto,
352                                         shape->style->stroke_width.computed);
353                  counter[i]++;
354             }
355         }
356     }
357 
358     // MID marker
359     if (shape->_marker[SP_MARKER_LOC_MID] || shape->_marker[SP_MARKER_LOC]) {
360         for(Geom::PathVector::const_iterator path_it = pathv.begin(); path_it != pathv.end(); ++path_it) {
361             // START position
362             if ( path_it != pathv.begin()
363                  && ! ((path_it == (pathv.end()-1)) && (path_it->size_default() == 0)) ) // if this is the last path and it is a moveto-only, don't draw mid marker there
364             {
365                 Geom::Affine const m (sp_shape_marker_get_transform_at_start(path_it->front()));
366                 for (int i = 0; i < 3; i += 2) {  // SP_MARKER_LOC and SP_MARKER_LOC_MID
367                     if ( shape->_marker[i] ) {
368                         sp_marker_show_instance(shape->_marker[i], ai,
369                                                 ai->key() + i, counter[i], m,
370                                                 shape->style->stroke_width.computed);
371                          counter[i]++;
372                     }
373                 }
374             }
375             // MID position
376             if ( path_it->size_default() > 1) {
377                 Geom::Path::const_iterator curve_it1 = path_it->begin();      // incoming curve
378                 Geom::Path::const_iterator curve_it2 = ++(path_it->begin());  // outgoing curve
379                 while (curve_it2 != path_it->end_default())
380                 {
381                     /* Put marker between curve_it1 and curve_it2.
382                      * Loop to end_default (so including closing segment), because when a path is closed,
383                      * there should be a midpoint marker between last segment and closing straight line segment
384                      */
385                     Geom::Affine const m (sp_shape_marker_get_transform(*curve_it1, *curve_it2));
386                     for (int i = 0; i < 3; i += 2) {  // SP_MARKER_LOC and SP_MARKER_LOC_MID
387                         if (shape->_marker[i]) {
388                             sp_marker_show_instance(shape->_marker[i], ai,
389                                                     ai->key() + i, counter[i], m,
390                                                     shape->style->stroke_width.computed);
391                             counter[i]++;
392                         }
393                     }
394 
395                     ++curve_it1;
396                     ++curve_it2;
397                 }
398             }
399             // END position
400             if ( path_it != (pathv.end()-1) && !path_it->empty()) {
401                 Geom::Curve const &lastcurve = path_it->back_default();
402                 Geom::Affine const m = sp_shape_marker_get_transform_at_end(lastcurve);
403                 for (int i = 0; i < 3; i += 2) {  // SP_MARKER_LOC and SP_MARKER_LOC_MID
404                     if (shape->_marker[i]) {
405                         sp_marker_show_instance(shape->_marker[i], ai,
406                                                 ai->key() + i, counter[i], m,
407                                                 shape->style->stroke_width.computed);
408                         counter[i]++;
409                     }
410                 }
411             }
412         }
413     }
414 
415     // END marker
416     if ( shape->_marker[SP_MARKER_LOC_END] || shape->_marker[SP_MARKER_LOC] ) {
417         /* Get reference to last curve in the path.
418          * For moveto-only path, this returns the "closing line segment". */
419         Geom::Path const &path_last = pathv.back();
420         unsigned int index = path_last.size_default();
421         if (index > 0) {
422             index--;
423         }
424         Geom::Curve const &lastcurve = path_last[index];
425         Geom::Affine const m = sp_shape_marker_get_transform_at_end(lastcurve);
426 
427         for (int i = 0; i < 4; i += 3) {  // SP_MARKER_LOC and SP_MARKER_LOC_END
428             if (shape->_marker[i]) {
429                 sp_marker_show_instance(shape->_marker[i], ai,
430                                         ai->key() + i, counter[i], m,
431                                         shape->style->stroke_width.computed);
432                 counter[i]++;
433             }
434         }
435     }
436 }
437 
modified(unsigned int flags)438 void SPShape::modified(unsigned int flags) {
439     // std::cout << "SPShape::modified(): " << (getId()?getId():"null") << std::endl;
440     SPLPEItem::modified(flags);
441 
442     if (flags & SP_OBJECT_STYLE_MODIFIED_FLAG) {
443         for (SPItemView *v = this->display; v != nullptr; v = v->next) {
444             Inkscape::DrawingShape *sh = dynamic_cast<Inkscape::DrawingShape *>(v->arenaitem);
445             if (hasMarkers()) {
446                 this->context_style = this->style;
447                 sh->setStyle(this->style, this->context_style);
448                 // Note: marker selector preview does not trigger SP_OBJECT_STYLE_MODIFIED_FLAG so
449                 // this is not called when marker previews are generated, however there is code in
450                 // SPShape::update() that calls this routine so we don't worry about it here.
451                 sh->setChildrenStyle(this->context_style); // Resolve 'context-xxx' in children.
452             } else if (this->parent) {
453                 this->context_style = this->parent->context_style;
454                 sh->setStyle(this->style, this->context_style);
455             }
456         }
457     }
458 
459     if (flags & SP_OBJECT_MODIFIED_FLAG && style->filter.set) {
460         if (auto filter = style->getFilter()) {
461             filter->update_filter_all_regions();
462         }
463     }
464 
465     if (!_curve) {
466         sp_lpe_item_update_patheffect(this, true, false);
467     }
468 }
469 
bbox(Geom::Affine const & transform,SPItem::BBoxType bboxtype) const470 Geom::OptRect SPShape::bbox(Geom::Affine const &transform, SPItem::BBoxType bboxtype) const {
471     // If the object is clipped, the update funcation that invalidates
472     // the cache doesn't get called if the object is moved, so we need
473     // to compare the transformations as well.
474 
475     if (bboxtype == SPItem::VISUAL_BBOX) {
476         bbox_vis_cache =
477             either_bbox(transform, bboxtype, bbox_vis_cache_is_valid, bbox_vis_cache, bbox_vis_cache_transform);
478         if (bbox_vis_cache) {
479             bbox_vis_cache_transform = transform;
480             bbox_vis_cache_is_valid = true;
481         }
482         return bbox_vis_cache;
483     } else {
484         bbox_geom_cache =
485             either_bbox(transform, bboxtype, bbox_geom_cache_is_valid, bbox_geom_cache, bbox_geom_cache_transform);
486         if (bbox_geom_cache) {
487             bbox_geom_cache_transform = transform;
488             bbox_geom_cache_is_valid = true;
489         }
490         return bbox_geom_cache;
491     }
492 }
493 
either_bbox(Geom::Affine const & transform,SPItem::BBoxType bboxtype,bool cache_is_valid,Geom::OptRect bbox_cache,Geom::Affine const & transform_cache) const494 Geom::OptRect SPShape::either_bbox(Geom::Affine const &transform, SPItem::BBoxType bboxtype, bool cache_is_valid,
495                                    Geom::OptRect bbox_cache, Geom::Affine const &transform_cache) const
496 {
497 
498     Geom::OptRect bbox;
499 
500     // Return the cache if possible.
501     auto delta = transform_cache.inverse() * transform;
502     if (cache_is_valid && bbox_cache && delta.isTranslation()) {
503 
504         // Don't re-adjust the cache if we haven't moved
505         if (!delta.isNonzeroTranslation()) {
506             return bbox_cache;
507         }
508         // delta is pure translation so it's safe to use it as is
509         return *bbox_cache * delta;
510     }
511 
512     if (!this->_curve || this->_curve->get_pathvector().empty()) {
513     	return bbox;
514     }
515 
516     bbox = bounds_exact_transformed(this->_curve->get_pathvector(), transform);
517 
518     if (!bbox) {
519     	return bbox;
520     }
521 
522     if (bboxtype == SPItem::VISUAL_BBOX) {
523         // convert the stroke to a path and calculate that path's geometric bbox
524 
525         if (!this->style->stroke.isNone() && !this->style->stroke_extensions.hairline) {
526             Geom::PathVector *pathv = item_to_outline(this, true);  // calculate bbox_only
527 
528             if (pathv) {
529                 bbox |= bounds_exact_transformed(*pathv, transform);
530                 delete pathv;
531             }
532         }
533 
534         // Union with bboxes of the markers, if any
535         if ( this->hasMarkers()  && !this->_curve->get_pathvector().empty() ) {
536             /** \todo make code prettier! */
537             Geom::PathVector const & pathv = this->_curve->get_pathvector();
538             // START marker
539             for (unsigned i = 0; i < 2; i++) { // SP_MARKER_LOC and SP_MARKER_LOC_START
540                 if ( this->_marker[i] ) {
541                     SPItem* marker_item = sp_item_first_item_child( _marker[i] );
542 
543                     if (marker_item) {
544                         Geom::Affine tr(sp_shape_marker_get_transform_at_start(pathv.begin()->front()));
545 
546                         if (_marker[i]->orient_mode == MARKER_ORIENT_AUTO_START_REVERSE) {
547                             // Reverse start marker if necessary
548                             tr = Geom::Rotate::from_degrees( 180.0 ) * tr;
549                         } else if (_marker[i]->orient_mode == MARKER_ORIENT_ANGLE) {
550                             Geom::Point transl = tr.translation();
551                             tr = Geom::Rotate::from_degrees(_marker[i]->orient.computed) * Geom::Translate(transl);
552                         }
553 
554                         if (_marker[i]->markerUnits == SP_MARKER_UNITS_STROKEWIDTH) {
555                             tr = Geom::Scale(this->style->stroke_width.computed) * tr;
556                         }
557 
558                         // total marker transform
559                         tr = marker_item->transform * _marker[i]->c2p * tr * transform;
560 
561                         // get bbox of the marker with that transform
562                         bbox |= marker_item->visualBounds(tr);
563                     }
564                 }
565             }
566 
567             // MID marker
568             for (unsigned i = 0; i < 3; i += 2) { // SP_MARKER_LOC and SP_MARKER_LOC_MID
569                 if ( !this->_marker[i] ) {
570                 	continue;
571                 }
572 
573                 SPMarker* marker = _marker[i];
574                 SPItem* marker_item = sp_item_first_item_child( marker );
575 
576                 if ( !marker_item ) {
577                 	continue;
578                 }
579 
580                 for(Geom::PathVector::const_iterator path_it = pathv.begin(); path_it != pathv.end(); ++path_it) {
581                     // START position
582                     if ( path_it != pathv.begin()
583                          && ! ((path_it == (pathv.end()-1)) && (path_it->size_default() == 0)) ) // if this is the last path and it is a moveto-only, there is no mid marker there
584                     {
585                         Geom::Affine tr(sp_shape_marker_get_transform_at_start(path_it->front()));
586 
587                         if (marker->orient_mode == MARKER_ORIENT_ANGLE) {
588                             Geom::Point transl = tr.translation();
589                             tr = Geom::Rotate::from_degrees(marker->orient.computed) * Geom::Translate(transl);
590                         }
591 
592                         if (marker->markerUnits == SP_MARKER_UNITS_STROKEWIDTH) {
593                             tr = Geom::Scale(this->style->stroke_width.computed) * tr;
594                         }
595 
596                         tr = marker_item->transform * marker->c2p * tr * transform;
597                         bbox |= marker_item->visualBounds(tr);
598                     }
599 
600                     // MID position
601                     if ( path_it->size_default() > 1) {
602                         Geom::Path::const_iterator curve_it1 = path_it->begin();      // incoming curve
603                         Geom::Path::const_iterator curve_it2 = ++(path_it->begin());  // outgoing curve
604 
605                         while (curve_it2 != path_it->end_default())
606                         {
607                             /* Put marker between curve_it1 and curve_it2.
608                              * Loop to end_default (so including closing segment), because when a path is closed,
609                              * there should be a midpoint marker between last segment and closing straight line segment */
610 
611                             SPMarker* marker = _marker[i];
612                             SPItem* marker_item = sp_item_first_item_child( marker );
613 
614                             if (marker_item) {
615                                 Geom::Affine tr(sp_shape_marker_get_transform(*curve_it1, *curve_it2));
616 
617                                 if (marker->orient_mode == MARKER_ORIENT_ANGLE) {
618                                     Geom::Point transl = tr.translation();
619                                     tr = Geom::Rotate::from_degrees(marker->orient.computed) * Geom::Translate(transl);
620                                 }
621 
622                                 if (marker->markerUnits == SP_MARKER_UNITS_STROKEWIDTH) {
623                                     tr = Geom::Scale(this->style->stroke_width.computed) * tr;
624                                 }
625 
626                                 tr = marker_item->transform * marker->c2p * tr * transform;
627                                 bbox |= marker_item->visualBounds(tr);
628                             }
629 
630                             ++curve_it1;
631                             ++curve_it2;
632                         }
633                     }
634 
635                     // END position
636                     if ( path_it != (pathv.end()-1) && !path_it->empty()) {
637                         Geom::Curve const &lastcurve = path_it->back_default();
638                         Geom::Affine tr = sp_shape_marker_get_transform_at_end(lastcurve);
639 
640                         if (marker->orient_mode == MARKER_ORIENT_ANGLE) {
641                             Geom::Point transl = tr.translation();
642                             tr = Geom::Rotate::from_degrees(marker->orient.computed) * Geom::Translate(transl);
643                         }
644 
645                         if (marker->markerUnits == SP_MARKER_UNITS_STROKEWIDTH) {
646                             tr = Geom::Scale(this->style->stroke_width.computed) * tr;
647                         }
648 
649                         tr = marker_item->transform * marker->c2p * tr * transform;
650                         bbox |= marker_item->visualBounds(tr);
651                     }
652                 }
653             }
654 
655             // END marker
656             for (unsigned i = 0; i < 4; i += 3) { // SP_MARKER_LOC and SP_MARKER_LOC_END
657                 if ( _marker[i] ) {
658                     SPMarker* marker = _marker[i];
659                     SPItem* marker_item = sp_item_first_item_child( marker );
660 
661                     if (marker_item) {
662                         /* Get reference to last curve in the path.
663                          * For moveto-only path, this returns the "closing line segment". */
664                         Geom::Path const &path_last = pathv.back();
665                         unsigned int index = path_last.size_default();
666 
667                         if (index > 0) {
668                             index--;
669                         }
670 
671                         Geom::Curve const &lastcurve = path_last[index];
672 
673                         Geom::Affine tr = sp_shape_marker_get_transform_at_end(lastcurve);
674 
675                         if (marker->orient_mode == MARKER_ORIENT_ANGLE) {
676                             Geom::Point transl = tr.translation();
677                             tr = Geom::Rotate::from_degrees(marker->orient.computed) * Geom::Translate(transl);
678                         }
679 
680                         if (marker->markerUnits == SP_MARKER_UNITS_STROKEWIDTH) {
681                             tr = Geom::Scale(this->style->stroke_width.computed) * tr;
682                         }
683 
684                         // total marker transform
685                         tr = marker_item->transform * marker->c2p * tr * transform;
686 
687                         // get bbox of the marker with that transform
688                         bbox |= marker_item->visualBounds(tr);
689                     }
690                 }
691             }
692         }
693     }
694 
695 
696     return bbox;
697 }
698 
699 static void
sp_shape_print_invoke_marker_printing(SPObject * obj,Geom::Affine tr,SPStyle const * style,SPPrintContext * ctx)700 sp_shape_print_invoke_marker_printing(SPObject *obj, Geom::Affine tr, SPStyle const *style, SPPrintContext *ctx)
701 {
702     SPMarker *marker = SP_MARKER(obj);
703     if (marker->markerUnits == SP_MARKER_UNITS_STROKEWIDTH) {
704         tr = Geom::Scale(style->stroke_width.computed) * tr;
705     }
706 
707     SPItem* marker_item = sp_item_first_item_child( marker );
708     if (marker_item) {
709         tr = marker_item->transform * marker->c2p * tr;
710 
711         Geom::Affine old_tr = marker_item->transform;
712         marker_item->transform = tr;
713         marker_item->invoke_print (ctx);
714         marker_item->transform = old_tr;
715     }
716 }
717 
print(SPPrintContext * ctx)718 void SPShape::print(SPPrintContext* ctx) {
719 	if (!this->_curve) {
720     	return;
721     }
722 
723     Geom::PathVector const & pathv = this->_curve->get_pathvector();
724 
725     if (pathv.empty()) {
726     	return;
727     }
728 
729     /* fixme: Think (Lauris) */
730 	Geom::OptRect pbox, dbox, bbox;
731     pbox = this->geometricBounds();
732     bbox = this->desktopVisualBounds();
733     dbox = Geom::Rect::from_xywh(Geom::Point(0,0), this->document->getDimensions());
734 
735     Geom::Affine const i2dt(this->i2dt_affine());
736 
737     SPStyle* style = this->style;
738 
739     if (!style->fill.isNone()) {
740         ctx->fill (pathv, i2dt, style, pbox, dbox, bbox);
741     }
742 
743     if (!style->stroke.isNone()) {
744         ctx->stroke (pathv, i2dt, style, pbox, dbox, bbox);
745     }
746 
747     /** \todo make code prettier */
748     // START marker
749     for (int i = 0; i < 2; i++) {  // SP_MARKER_LOC and SP_MARKER_LOC_START
750         if ( this->_marker[i] ) {
751             Geom::Affine tr(sp_shape_marker_get_transform_at_start(pathv.begin()->front()));
752             sp_shape_print_invoke_marker_printing(this->_marker[i], tr, style, ctx);
753         }
754     }
755 
756     // MID marker
757     for (int i = 0; i < 3; i += 2) {  // SP_MARKER_LOC and SP_MARKER_LOC_MID
758         if (this->_marker[i]) {
759             for(Geom::PathVector::const_iterator path_it = pathv.begin(); path_it != pathv.end(); ++path_it) {
760                 // START position
761                 if ( path_it != pathv.begin()
762                      && ! ((path_it == (pathv.end()-1)) && (path_it->size_default() == 0)) ) // if this is the last path and it is a moveto-only, there is no mid marker there
763                 {
764                     Geom::Affine tr(sp_shape_marker_get_transform_at_start(path_it->front()));
765                     sp_shape_print_invoke_marker_printing(this->_marker[i], tr, style, ctx);
766                 }
767 
768                 // MID position
769                 if ( path_it->size_default() > 1) {
770                     Geom::Path::const_iterator curve_it1 = path_it->begin();      // incoming curve
771                     Geom::Path::const_iterator curve_it2 = ++(path_it->begin());  // outgoing curve
772 
773                     while (curve_it2 != path_it->end_default())
774                     {
775                         /* Put marker between curve_it1 and curve_it2.
776                          * Loop to end_default (so including closing segment), because when a path is closed,
777                          * there should be a midpoint marker between last segment and closing straight line segment */
778                         Geom::Affine tr(sp_shape_marker_get_transform(*curve_it1, *curve_it2));
779 
780                         sp_shape_print_invoke_marker_printing(this->_marker[i], tr, style, ctx);
781 
782                         ++curve_it1;
783                         ++curve_it2;
784                     }
785                 }
786 
787                 if ( path_it != (pathv.end()-1) && !path_it->empty()) {
788                     Geom::Curve const &lastcurve = path_it->back_default();
789                     Geom::Affine tr = sp_shape_marker_get_transform_at_end(lastcurve);
790                     sp_shape_print_invoke_marker_printing(this->_marker[i], tr, style, ctx);
791                 }
792             }
793         }
794     }
795 
796     // END marker
797     if ( this->_marker[SP_MARKER_LOC_END] || this->_marker[SP_MARKER_LOC]) {
798         /* Get reference to last curve in the path.
799          * For moveto-only path, this returns the "closing line segment". */
800         Geom::Path const &path_last = pathv.back();
801         unsigned int index = path_last.size_default();
802 
803         if (index > 0) {
804             index--;
805         }
806 
807         Geom::Curve const &lastcurve = path_last[index];
808 
809         Geom::Affine tr = sp_shape_marker_get_transform_at_end(lastcurve);
810 
811         for (int i = 0; i < 4; i += 3) {  // SP_MARKER_LOC and SP_MARKER_LOC_END
812             if (this->_marker[i]) {
813                 sp_shape_print_invoke_marker_printing(this->_marker[i], tr, style, ctx);
814             }
815         }
816     }
817 }
818 
update_patheffect(bool write)819 void SPShape::update_patheffect(bool write)
820 {
821     if (auto c_lpe = SPCurve::copy(curveForEdit())) {
822         /* if a path has an lpeitem applied, then reset the curve to the _curve_before_lpe.
823          * This is very important for LPEs to work properly! (the bbox might be recalculated depending on the curve in shape)*/
824         this->setCurveInsync(c_lpe.get());
825         SPRoot *root = this->document->getRoot();
826         if (!sp_version_inside_range(root->version.inkscape, 0, 1, 0, 92)) {
827             this->resetClipPathAndMaskLPE();
828         }
829 
830         bool success = false;
831         if (hasPathEffect() && pathEffectsEnabled()) {
832             success = this->performPathEffect(c_lpe.get(), SP_SHAPE(this));
833             if (success) {
834                 this->setCurveInsync(c_lpe.get());
835                 this->applyToClipPath(this);
836                 this->applyToMask(this);
837             }
838         }
839         if (write && success) {
840             Inkscape::XML::Node *repr = this->getRepr();
841             if (c_lpe != nullptr) {
842                 repr->setAttribute("d", sp_svg_write_path(c_lpe->get_pathvector()));
843             } else {
844                 repr->removeAttribute("d");
845             }
846         }
847         this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
848     }
849 }
850 
show(Inkscape::Drawing & drawing,unsigned int,unsigned int)851 Inkscape::DrawingItem* SPShape::show(Inkscape::Drawing &drawing, unsigned int /*key*/, unsigned int /*flags*/) {
852     // std::cout << "SPShape::show(): " << (getId()?getId():"null") << std::endl;
853     Inkscape::DrawingShape *s = new Inkscape::DrawingShape(drawing);
854 
855     bool has_markers = this->hasMarkers();
856 
857     s->setPath(this->_curve.get());
858 
859     /* This stanza checks that an object's marker style agrees with
860      * the marker objects it has allocated.  sp_shape_set_marker ensures
861      * that the appropriate marker objects are present (or absent) to
862      * match the style.
863      */
864     for (int i = 0 ; i < SP_MARKER_LOC_QTY ; i++) {
865         sp_shape_set_marker (this, i, this->style->marker_ptrs[i]->value());
866     }
867 
868     if (has_markers) {
869         /* provide key and dimension the marker views */
870         if (!s->key()) {
871             s->setKey(SPItem::display_key_new (SP_MARKER_LOC_QTY));
872         }
873 
874         for (int i = 0; i < SP_MARKER_LOC_QTY; i++) {
875             if (_marker[i]) {
876                 sp_marker_show_dimension(_marker[i],
877                                          s->key() + i,
878                                          numberOfMarkers(i));
879             }
880         }
881 
882         /* Update marker views */
883         sp_shape_update_marker_view (this, s);
884 
885         this->context_style = this->style;
886         s->setStyle(this->style, this->context_style);
887         s->setChildrenStyle(this->context_style); // Resolve 'context-xxx' in children.
888     } else if (this->parent) {
889         this->context_style = this->parent->context_style;
890         s->setStyle(this->style, this->context_style);
891     }
892     return s;
893 }
894 
895 /**
896  * Sets style, path, and paintbox.  Updates marker views, including dimensions.
897  */
hide(unsigned int key)898 void SPShape::hide(unsigned int key) {
899     for (int i = 0; i < SP_MARKER_LOC_QTY; ++i) {
900         if (_marker[i]) {
901             for (SPItemView* v = display; v != nullptr; v = v->next) {
902                 if (key == v->key) {
903                     sp_marker_hide(_marker[i], v->arenaitem->key() + i);
904                 }
905             }
906         }
907     }
908 
909     //SPLPEItem::onHide(key);
910 }
911 
912 /**
913 * \param shape Shape.
914 * \return TRUE if the shape has any markers, or FALSE if not.
915 */
hasMarkers() const916 int SPShape::hasMarkers() const
917 {
918     /* Note, we're ignoring 'marker' settings, which technically should apply for
919        all three settings.  This should be fixed later such that if 'marker' is
920        specified, then all three should appear. */
921 
922     // Ignore markers for objects which are inside markers themselves.
923     for (SPObject *parent = this->parent; parent != nullptr; parent = parent->parent) {
924       if (dynamic_cast<SPMarker *>(parent)) {
925         return 0;
926       }
927     }
928 
929     return (
930         this->_curve &&
931         (this->_marker[SP_MARKER_LOC] ||
932          this->_marker[SP_MARKER_LOC_START] ||
933          this->_marker[SP_MARKER_LOC_MID] ||
934          this->_marker[SP_MARKER_LOC_END])
935         );
936 }
937 
938 
939 /**
940 * \param shape Shape.
941 * \param type Marker type (e.g. SP_MARKER_LOC_START)
942 * \return Number of markers that the shape has of this type.
943 */
numberOfMarkers(int type) const944 int SPShape::numberOfMarkers(int type) const {
945     Geom::PathVector const & pathv = this->_curve->get_pathvector();
946 
947     if (pathv.size() == 0) {
948         return 0;
949     }
950     switch(type) {
951 
952         case SP_MARKER_LOC:
953         {
954             if ( this->_marker[SP_MARKER_LOC] ) {
955                 guint n = 0;
956                 for(const auto & path_it : pathv) {
957                     n += path_it.size_default() + 1;
958                 }
959                 return n;
960             } else {
961                 return 0;
962             }
963         }
964         case SP_MARKER_LOC_START:
965             // there is only a start marker on the first path of a pathvector
966             return this->_marker[SP_MARKER_LOC_START] ? 1 : 0;
967 
968         case SP_MARKER_LOC_MID:
969         {
970             if ( this->_marker[SP_MARKER_LOC_MID] ) {
971                 guint n = 0;
972                 for(const auto & path_it : pathv) {
973                     n += path_it.size_default() + 1;
974                 }
975                 n = (n > 1) ? (n - 2) : 0; // Minus the start and end marker, but never negative.
976                                            // A path or polyline may have only one point.
977                 return n;
978             } else {
979                 return 0;
980             }
981         }
982 
983         case SP_MARKER_LOC_END:
984         {
985             // there is only an end marker on the last path of a pathvector
986             return this->_marker[SP_MARKER_LOC_END] ? 1 : 0;
987         }
988 
989         default:
990             return 0;
991     }
992 }
993 
994 /**
995  * Checks if the given marker is used in the shape, and if so, it
996  * releases it by calling sp_marker_hide.  Also detaches signals
997  * and unrefs the marker from the shape.
998  */
999 static void
sp_shape_marker_release(SPObject * marker,SPShape * shape)1000 sp_shape_marker_release (SPObject *marker, SPShape *shape)
1001 {
1002     SPItem *item = dynamic_cast<SPItem *>(shape);
1003     g_return_if_fail(item != nullptr);
1004 
1005     for (int i = 0; i < SP_MARKER_LOC_QTY; i++) {
1006         if (marker == shape->_marker[i]) {
1007             SPItemView *v;
1008             /* Hide marker */
1009             for (v = item->display; v != nullptr; v = v->next) {
1010                 sp_marker_hide(shape->_marker[i], v->arenaitem->key() + i);
1011             }
1012             /* Detach marker */
1013             shape->_release_connect[i].disconnect();
1014             shape->_modified_connect[i].disconnect();
1015             shape->_marker[i]->unhrefObject(item);
1016             shape->_marker[i] = nullptr;
1017         }
1018     }
1019 }
1020 
1021 /**
1022  * No-op.  Exists for handling 'modified' messages
1023  */
1024 static void
sp_shape_marker_modified(SPObject *,guint,SPItem *)1025 sp_shape_marker_modified (SPObject */*marker*/, guint /*flags*/, SPItem */*item*/)
1026 {
1027     /* I think mask does update automagically */
1028     /* g_warning ("Item %s mask %s modified", item->getId(), mask->getId()); */
1029 }
1030 
1031 /**
1032  * Adds a new marker to shape object at the location indicated by key.  value
1033  * must be a valid URI reference resolvable from the shape object (i.e., present
1034  * in the document <defs>).  If the shape object already has a marker
1035  * registered at the given position, it is removed first.  Then the
1036  * new marker is hrefed and its signals connected.
1037  */
1038 void
sp_shape_set_marker(SPObject * object,unsigned int key,const gchar * value)1039 sp_shape_set_marker (SPObject *object, unsigned int key, const gchar *value)
1040 {
1041     SPShape *shape = dynamic_cast<SPShape *>(object);
1042     g_return_if_fail(shape != nullptr);
1043 
1044     if (key > SP_MARKER_LOC_END) {
1045         return;
1046     }
1047 
1048     SPObject *mrk = sp_css_uri_reference_resolve(object->document, value);
1049     SPMarker *marker = dynamic_cast<SPMarker *>(mrk);
1050     if (marker != shape->_marker[key]) {
1051         if (shape->_marker[key]) {
1052             SPItemView *v;
1053 
1054             /* Detach marker */
1055             shape->_release_connect[key].disconnect();
1056             shape->_modified_connect[key].disconnect();
1057 
1058             /* Hide marker */
1059             for (v = shape->display; v != nullptr; v = v->next) {
1060                 sp_marker_hide(shape->_marker[key],
1061                                v->arenaitem->key() + key);
1062             }
1063 
1064             /* Unref marker */
1065             shape->_marker[key]->unhrefObject(object);
1066             shape->_marker[key] = nullptr;
1067         }
1068         if (marker) {
1069             shape->_marker[key] = marker;
1070             shape->_marker[key]->hrefObject(object);
1071             shape->_release_connect[key] = marker->connectRelease(sigc::bind<1>(sigc::ptr_fun(&sp_shape_marker_release), shape));
1072             shape->_modified_connect[key] = marker->connectModified(sigc::bind<2>(sigc::ptr_fun(&sp_shape_marker_modified), shape));
1073         }
1074     }
1075 }
1076 
1077 // CPPIFY: make pure virtual
set_shape()1078 void SPShape::set_shape() {
1079 	//throw;
1080 }
1081 
1082 /* Shape section */
1083 
_setCurve(std::unique_ptr<SPCurve> && new_curve,bool update_display)1084 void SPShape::_setCurve(std::unique_ptr<SPCurve> &&new_curve, bool update_display)
1085 {
1086     _curve = std::move(new_curve);
1087 
1088     if (update_display) {
1089         requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1090     }
1091 }
_setCurve(SPCurve const * new_curve,bool update_display)1092 void SPShape::_setCurve(SPCurve const *new_curve, bool update_display)
1093 {
1094     _setCurve(SPCurve::copy(new_curve), update_display);
1095 }
1096 
1097 /**
1098  * Adds a curve to the shape.
1099  * Any existing curve in the shape will be unreferenced first.
1100  * This routine also triggers a request to update the display.
1101  */
setCurve(std::unique_ptr<SPCurve> && new_curve)1102 void SPShape::setCurve(std::unique_ptr<SPCurve> &&new_curve)
1103 {
1104     _setCurve(std::move(new_curve), true);
1105 }
setCurve(SPCurve const * new_curve)1106 void SPShape::setCurve(SPCurve const *new_curve)
1107 {
1108     _setCurve(new_curve, true);
1109 }
1110 
1111 /**
1112  * Sets _curve_before_lpe to a copy of `new_curve`
1113  */
setCurveBeforeLPE(std::unique_ptr<SPCurve> && new_curve)1114 void SPShape::setCurveBeforeLPE(std::unique_ptr<SPCurve> &&new_curve)
1115 {
1116     _curve_before_lpe = std::move(new_curve);
1117 }
setCurveBeforeLPE(SPCurve const * new_curve)1118 void SPShape::setCurveBeforeLPE(SPCurve const *new_curve)
1119 {
1120     setCurveBeforeLPE(SPCurve::copy(new_curve));
1121 }
1122 
1123 /**
1124  * Same as setCurve() but without updating the display
1125  */
setCurveInsync(std::unique_ptr<SPCurve> && new_curve)1126 void SPShape::setCurveInsync(std::unique_ptr<SPCurve> &&new_curve)
1127 {
1128     _setCurve(std::move(new_curve), false);
1129 }
setCurveInsync(SPCurve const * new_curve)1130 void SPShape::setCurveInsync(SPCurve const *new_curve)
1131 {
1132     _setCurve(new_curve, false);
1133 }
1134 
1135 /**
1136  * Return a borrowed pointer to the curve (if any exists) or NULL if there is no curve
1137  */
curve()1138 SPCurve *SPShape::curve()
1139 {
1140     return _curve.get();
1141 }
curve() const1142 SPCurve const *SPShape::curve() const
1143 {
1144     return _curve.get();
1145 }
1146 
1147 /**
1148  * Return a borrowed pointer of the curve *before* LPE (if any exists) or NULL if there is no curve
1149  */
curveBeforeLPE() const1150 SPCurve const *SPShape::curveBeforeLPE() const
1151 {
1152     return _curve_before_lpe.get();
1153 }
1154 
1155 /**
1156  * Return a borrowed pointer of the curve for edit
1157  */
curveForEdit() const1158 SPCurve const *SPShape::curveForEdit() const
1159 {
1160     if (_curve_before_lpe) {
1161         return _curve_before_lpe.get();
1162     }
1163     return curve();
1164 }
1165 
snappoints(std::vector<Inkscape::SnapCandidatePoint> & p,Inkscape::SnapPreferences const * snapprefs) const1166 void SPShape::snappoints(std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs) const {
1167     if (this->_curve == nullptr) {
1168         return;
1169     }
1170 
1171     Geom::PathVector const &pathv = this->_curve->get_pathvector();
1172 
1173     if (pathv.empty()) {
1174         return;
1175     }
1176 
1177     Geom::Affine const i2dt (this->i2dt_affine ());
1178 
1179     if (snapprefs->isTargetSnappable(Inkscape::SNAPTARGET_OBJECT_MIDPOINT)) {
1180         Geom::OptRect bbox = this->desktopVisualBounds();
1181 
1182         if (bbox) {
1183             p.emplace_back(bbox->midpoint(), Inkscape::SNAPSOURCE_OBJECT_MIDPOINT, Inkscape::SNAPTARGET_OBJECT_MIDPOINT);
1184         }
1185     }
1186 
1187     for(const auto & path_it : pathv) {
1188         if (snapprefs->isTargetSnappable(Inkscape::SNAPTARGET_NODE_CUSP)) {
1189             // Add the first point of the path
1190             p.emplace_back(path_it.initialPoint() * i2dt, Inkscape::SNAPSOURCE_NODE_CUSP, Inkscape::SNAPTARGET_NODE_CUSP);
1191         }
1192 
1193         Geom::Path::const_iterator curve_it1 = path_it.begin();      // incoming curve
1194         Geom::Path::const_iterator curve_it2 = ++(path_it.begin());  // outgoing curve
1195 
1196         while (curve_it1 != path_it.end_default())
1197         {
1198             // For each path: consider midpoints of line segments for snapping
1199             if (snapprefs->isTargetSnappable(Inkscape::SNAPTARGET_LINE_MIDPOINT)) {
1200                 if (Geom::LineSegment const* line_segment = dynamic_cast<Geom::LineSegment const*>(&(*curve_it1))) {
1201                     p.emplace_back(Geom::middle_point(*line_segment) * i2dt, Inkscape::SNAPSOURCE_LINE_MIDPOINT, Inkscape::SNAPTARGET_LINE_MIDPOINT);
1202                 }
1203             }
1204 
1205             if (curve_it2 == path_it.end_default()) { // Test will only pass for the last iteration of the while loop
1206                 if (snapprefs->isTargetSnappable(Inkscape::SNAPTARGET_NODE_CUSP) && !path_it.closed()) {
1207                     // Add the last point of the path, but only for open paths
1208                     // (for closed paths the first and last point will coincide)
1209                     p.emplace_back((*curve_it1).finalPoint() * i2dt, Inkscape::SNAPSOURCE_NODE_CUSP, Inkscape::SNAPTARGET_NODE_CUSP);
1210                 }
1211             } else {
1212                 /* Test whether to add the node between curve_it1 and curve_it2.
1213                  * Loop to end_default (so only iterating through the stroked part); */
1214 
1215                 Geom::NodeType nodetype = Geom::get_nodetype(*curve_it1, *curve_it2);
1216 
1217                 bool c1 = snapprefs->isTargetSnappable(Inkscape::SNAPTARGET_NODE_CUSP) && (nodetype == Geom::NODE_CUSP || nodetype == Geom::NODE_NONE);
1218                 bool c2 = snapprefs->isTargetSnappable(Inkscape::SNAPTARGET_NODE_SMOOTH) && (nodetype == Geom::NODE_SMOOTH || nodetype == Geom::NODE_SYMM);
1219 
1220                 if (c1 || c2) {
1221                     Inkscape::SnapSourceType sst;
1222                     Inkscape::SnapTargetType stt;
1223 
1224                     switch (nodetype) {
1225                     case Geom::NODE_CUSP:
1226                         sst = Inkscape::SNAPSOURCE_NODE_CUSP;
1227                         stt = Inkscape::SNAPTARGET_NODE_CUSP;
1228                         break;
1229                     case Geom::NODE_SMOOTH:
1230                     case Geom::NODE_SYMM:
1231                         sst = Inkscape::SNAPSOURCE_NODE_SMOOTH;
1232                         stt = Inkscape::SNAPTARGET_NODE_SMOOTH;
1233                         break;
1234                     default:
1235                         sst = Inkscape::SNAPSOURCE_UNDEFINED;
1236                         stt = Inkscape::SNAPTARGET_UNDEFINED;
1237                         break;
1238                     }
1239 
1240                     p.emplace_back(curve_it1->finalPoint() * i2dt, sst, stt);
1241                 }
1242             }
1243 
1244             ++curve_it1;
1245             ++curve_it2;
1246         }
1247 
1248         // Find the internal intersections of each path and consider these for snapping
1249         // (using "Method 1" as described in Inkscape::ObjectSnapper::_collectNodes())
1250         if (snapprefs->isTargetSnappable(Inkscape::SNAPTARGET_PATH_INTERSECTION) || snapprefs->isSourceSnappable(Inkscape::SNAPSOURCE_PATH_INTERSECTION)) {
1251             Geom::Crossings cs;
1252 
1253             try {
1254                 cs = self_crossings(path_it); // This can be slow!
1255 
1256                 if (!cs.empty()) { // There might be multiple intersections...
1257                     for (const auto & c : cs) {
1258                         Geom::Point p_ix = path_it.pointAt(c.ta);
1259                         p.emplace_back(p_ix * i2dt, Inkscape::SNAPSOURCE_PATH_INTERSECTION, Inkscape::SNAPTARGET_PATH_INTERSECTION);
1260                     }
1261                 }
1262             } catch (Geom::RangeError &e) {
1263                 // do nothing
1264                 // The exception could be Geom::InfiniteSolutions: then no snappoints should be added
1265             }
1266 
1267         }
1268     }
1269 }
1270 
1271 /*
1272   Local Variables:
1273   mode:c++
1274   c-file-style:"stroustrup"
1275   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1276   indent-tabs-mode:nil
1277   fill-column:99
1278   End:
1279 */
1280 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
1281