1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /** \file
3  * Base class for live path effect items
4  */
5 /*
6  * Authors:
7  *   Johan Engelen <j.b.c.engelen@ewi.utwente.nl>
8  *   Bastien Bouclet <bgkweb@gmail.com>
9  *   Abhishek Sharma
10  *
11  * Copyright (C) 2008 authors
12  *
13  * Released under GNU GPL v2+, read the file 'COPYING' for more information.
14  */
15 
16 #ifdef HAVE_CONFIG_H
17 #endif
18 
19 #include <glibmm/i18n.h>
20 
21 #include "bad-uri-exception.h"
22 
23 #include "attributes.h"
24 #include "desktop.h"
25 #include "display/curve.h"
26 #include "inkscape.h"
27 #include "live_effects/effect.h"
28 #include "live_effects/lpe-bool.h"
29 #include "live_effects/lpe-clone-original.h"
30 #include "live_effects/lpe-copy_rotate.h"
31 #include "live_effects/lpe-lattice2.h"
32 #include "live_effects/lpe-measure-segments.h"
33 #include "live_effects/lpe-slice.h"
34 #include "live_effects/lpe-mirror_symmetry.h"
35 #include "message-stack.h"
36 #include "path-chemistry.h"
37 #include "sp-clippath.h"
38 #include "sp-ellipse.h"
39 #include "sp-spiral.h"
40 #include "sp-star.h"
41 #include "sp-item-group.h"
42 #include "sp-mask.h"
43 #include "sp-path.h"
44 #include "sp-rect.h"
45 #include "sp-root.h"
46 #include "svg/svg.h"
47 #include "ui/shape-editor.h"
48 #include "uri.h"
49 
50 /* LPEItem base class */
51 
52 static void lpeobject_ref_modified(SPObject *href, guint flags, SPLPEItem *lpeitem);
53 static void sp_lpe_item_create_original_path_recursive(SPLPEItem *lpeitem);
54 static void sp_lpe_item_cleanup_original_path_recursive(SPLPEItem *lpeitem, bool keep_paths, bool force = false, bool is_clip_mask = false);
55 
56 typedef std::list<std::string> HRefList;
57 static std::string patheffectlist_svg_string(PathEffectList const & list);
58 static std::string hreflist_svg_string(HRefList const & list);
59 
60 namespace {
clear_path_effect_list(PathEffectList * const l)61     void clear_path_effect_list(PathEffectList* const l) {
62         PathEffectList::iterator it =  l->begin();
63         while ( it !=  l->end()) {
64             (*it)->unlink();
65             delete *it;
66             it = l->erase(it);
67         }
68     }
69 }
70 
SPLPEItem()71 SPLPEItem::SPLPEItem()
72     : SPItem()
73     , path_effects_enabled(1)
74     , path_effect_list(new PathEffectList())
75     , lpe_modified_connection_list(new std::list<sigc::connection>())
76     , current_path_effect(nullptr)
77     , lpe_helperpaths()
78 {
79 }
80 
81 SPLPEItem::~SPLPEItem() = default;
82 
build(SPDocument * document,Inkscape::XML::Node * repr)83 void SPLPEItem::build(SPDocument *document, Inkscape::XML::Node *repr) {
84     this->readAttr(SPAttr::INKSCAPE_PATH_EFFECT);
85 
86     SPItem::build(document, repr);
87 }
88 
release()89 void SPLPEItem::release() {
90     // disconnect all modified listeners:
91 
92     for (auto & mod_it : *this->lpe_modified_connection_list)
93     {
94         mod_it.disconnect();
95     }
96 
97     delete this->lpe_modified_connection_list;
98     this->lpe_modified_connection_list = nullptr;
99 
100     clear_path_effect_list(this->path_effect_list);
101     // delete the list itself
102     delete this->path_effect_list;
103     this->path_effect_list = nullptr;
104 
105     SPItem::release();
106 }
107 
set(SPAttr key,gchar const * value)108 void SPLPEItem::set(SPAttr key, gchar const* value) {
109     switch (key) {
110         case SPAttr::INKSCAPE_PATH_EFFECT:
111             {
112                 this->current_path_effect = nullptr;
113 
114                 // Disable the path effects while populating the LPE list
115                 sp_lpe_item_enable_path_effects(this, false);
116 
117                 // disconnect all modified listeners:
118                 for (auto & mod_it : *this->lpe_modified_connection_list)
119                 {
120                     mod_it.disconnect();
121                 }
122 
123                 this->lpe_modified_connection_list->clear();
124                 clear_path_effect_list(this->path_effect_list);
125 
126                 // Parse the contents of "value" to rebuild the path effect reference list
127                 if ( value ) {
128                     std::istringstream iss(value);
129                     std::string href;
130 
131                     while (std::getline(iss, href, ';'))
132                     {
133                         Inkscape::LivePathEffect::LPEObjectReference *path_effect_ref = new Inkscape::LivePathEffect::LPEObjectReference(this);
134 
135                         try {
136                             path_effect_ref->link(href.c_str());
137                         } catch (Inkscape::BadURIException &e) {
138                             g_warning("BadURIException when trying to find LPE: %s", e.what());
139                             path_effect_ref->unlink();
140                             delete path_effect_ref;
141                             path_effect_ref = nullptr;
142                         }
143 
144                         this->path_effect_list->push_back(path_effect_ref);
145 
146                         if ( path_effect_ref->lpeobject && path_effect_ref->lpeobject->get_lpe() ) {
147                             // connect modified-listener
148                             this->lpe_modified_connection_list->push_back(
149                                                 path_effect_ref->lpeobject->connectModified(sigc::bind(sigc::ptr_fun(&lpeobject_ref_modified), this)) );
150                         } else {
151                             // something has gone wrong in finding the right patheffect.
152                             g_warning("Unknown LPE type specified, LPE stack effectively disabled");
153                             // keep the effect in the lpestack, so the whole stack is effectively disabled but maintained
154                         }
155                     }
156                 }
157 
158                 sp_lpe_item_enable_path_effects(this, true);
159             }
160             break;
161 
162         default:
163             SPItem::set(key, value);
164             break;
165     }
166 }
167 
update(SPCtx * ctx,unsigned int flags)168 void SPLPEItem::update(SPCtx* ctx, unsigned int flags) {
169     SPItem::update(ctx, flags);
170 
171     // update the helperpaths of all LPEs applied to the item
172     // TODO: re-add for the new node tool
173 }
174 
modified(unsigned int flags)175 void SPLPEItem::modified(unsigned int flags) {
176     //stop update when modified and make the effect update on the LPE transform method if the effect require it
177     //if (SP_IS_GROUP(this) && (flags & SP_OBJECT_MODIFIED_FLAG) && (flags & SP_OBJECT_USER_MODIFIED_FLAG_B)) {
178     //  sp_lpe_item_update_patheffect(this, true, false);
179     //}
180 }
181 
write(Inkscape::XML::Document * xml_doc,Inkscape::XML::Node * repr,guint flags)182 Inkscape::XML::Node* SPLPEItem::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) {
183     if (flags & SP_OBJECT_WRITE_EXT) {
184         if ( hasPathEffect() ) {
185             repr->setAttributeOrRemoveIfEmpty("inkscape:path-effect", patheffectlist_svg_string(*this->path_effect_list));
186         } else {
187             repr->removeAttribute("inkscape:path-effect");
188         }
189     }
190 
191     SPItem::write(xml_doc, repr, flags);
192 
193     return repr;
194 }
195 
196 /**
197  * returns true when LPE was successful.
198  */
performPathEffect(SPCurve * curve,SPShape * current,bool is_clip_or_mask)199 bool SPLPEItem::performPathEffect(SPCurve *curve, SPShape *current, bool is_clip_or_mask) {
200 
201     if (!curve) {
202         return false;
203     }
204 
205     if (this->hasPathEffect() && this->pathEffectsEnabled()) {
206         PathEffectList path_effect_list(*this->path_effect_list);
207         size_t path_effect_list_size = path_effect_list.size();
208         for (auto &lperef : path_effect_list) {
209             LivePathEffectObject *lpeobj = lperef->lpeobject;
210             if (!lpeobj) {
211                 /** \todo Investigate the cause of this.
212                  * For example, this happens when copy pasting an object with LPE applied. Probably because the object is pasted while the effect is not yet pasted to defs, and cannot be found.
213                 */
214                 g_warning("SPLPEItem::performPathEffect - NULL lpeobj in list!");
215                 return false;
216             }
217 
218             Inkscape::LivePathEffect::Effect *lpe = lpeobj->get_lpe();
219             if (!lpe || !performOnePathEffect(curve, current, lpe, is_clip_or_mask)) {
220                 return false;
221             }
222             if (path_effect_list_size != this->path_effect_list->size()) {
223                 break;
224             }
225         }
226     }
227     return true;
228 }
229 
230 /**
231  * returns true when LPE was successful.
232  */
performOnePathEffect(SPCurve * curve,SPShape * current,Inkscape::LivePathEffect::Effect * lpe,bool is_clip_or_mask)233 bool SPLPEItem::performOnePathEffect(SPCurve *curve, SPShape *current, Inkscape::LivePathEffect::Effect *lpe, bool is_clip_or_mask) {
234     if (!lpe) {
235         /** \todo Investigate the cause of this.
236          * Not sure, but I think this can happen when an unknown effect type is specified...
237          */
238         g_warning("SPLPEItem::performPathEffect - lpeobj with invalid lpe in the stack!");
239         return false;
240     }
241     if (lpe->isVisible()) {
242         if (lpe->acceptsNumClicks() > 0 && !lpe->isReady()) {
243             // if the effect expects mouse input before being applied and the input is not finished
244             // yet, we don't alter the path
245             return false;
246         }
247         //if is not clip or mask or LPE apply to clip and mask
248         if (!is_clip_or_mask || lpe->apply_to_clippath_and_mask) {
249             lpe->setCurrentShape(current);
250             if (!SP_IS_GROUP(this)) {
251                 lpe->pathvector_before_effect = curve->get_pathvector();
252             }
253             // To Calculate BBox on shapes and nested LPE
254             current->setCurveInsync(curve);
255             // Groups have their doBeforeEffect called elsewhere
256             if (lpe->lpeversion.param_getSVGValue() != "0") { // we are on 1 or up
257                 current->bbox_vis_cache_is_valid = false;
258                 current->bbox_geom_cache_is_valid = false;
259             }
260             if (!SP_IS_GROUP(this) && !is_clip_or_mask) {
261                 lpe->doBeforeEffect_impl(this);
262             }
263 
264             try {
265                 lpe->doEffect(curve);
266                 lpe->has_exception = false;
267             }
268 
269             catch (std::exception & e) {
270                 g_warning("Exception during LPE %s execution. \n %s", lpe->getName().c_str(), e.what());
271                 if (SP_ACTIVE_DESKTOP && SP_ACTIVE_DESKTOP->messageStack()) {
272                     SP_ACTIVE_DESKTOP->messageStack()->flash( Inkscape::WARNING_MESSAGE,
273                                     _("An exception occurred during execution of the Path Effect.") );
274                 }
275                 lpe->doOnException(this);
276                 return false;
277             }
278 
279 
280             if (!SP_IS_GROUP(this)) {
281                 // To have processed the shape to doAfterEffect
282                 current->setCurveInsync(curve);
283                 if (curve) {
284                     lpe->pathvector_after_effect = curve->get_pathvector();
285                 }
286                 lpe->doAfterEffect_impl(this, curve);
287             }
288             // we need this on slice LPE to calulate correcly effects
289             if (dynamic_cast<Inkscape::LivePathEffect::LPESlice *>(lpe)) { // we are on 1 or up
290                 current->bbox_vis_cache_is_valid = false;
291                 current->bbox_geom_cache_is_valid = false;
292             }
293         }
294     }
295     return true;
296 }
297 
298 /**
299  * returns true when LPE write unoptimiced
300  */
optimizeTransforms()301 bool SPLPEItem::optimizeTransforms()
302 {
303     if (dynamic_cast<SPGroup *>(this)) {
304         return false;
305     }
306     // this next towo check could be brong on stars or spirals without transforms
307     // while finish transform change from optimiced to unoptimized
308     if (dynamic_cast<SPSpiral *>(this) && this->getAttribute("transform")) {
309         return false;
310     }
311     if (dynamic_cast<SPStar *>(this) && this->getAttribute("transform")) {
312         return false;
313     }
314     auto* mask_path = this->getMaskObject();
315     if(mask_path) {
316         return false;
317     }
318     auto* clip_path = this->getClipObject();
319     if(clip_path) {
320         return false;
321     }
322     PathEffectList path_effect_list(*this->path_effect_list);
323     for (auto &lperef : path_effect_list) {
324         if (!lperef) {
325             continue;
326         }
327         LivePathEffectObject *lpeobj = lperef->lpeobject;
328         if (lpeobj) {
329             Inkscape::LivePathEffect::Effect *lpe = lpeobj->get_lpe();
330             if (lpe) {
331                 if (dynamic_cast<Inkscape::LivePathEffect::LPEMeasureSegments *>(lpe) ||
332                     dynamic_cast<Inkscape::LivePathEffect::LPECloneOriginal *>(lpe) ||
333                     dynamic_cast<Inkscape::LivePathEffect::LPEMirrorSymmetry *>(lpe) ||
334                     dynamic_cast<Inkscape::LivePathEffect::LPESlice *>(lpe) ||
335                     dynamic_cast<Inkscape::LivePathEffect::LPELattice2 *>(lpe) ||
336                     dynamic_cast<Inkscape::LivePathEffect::LPEBool *>(lpe) ||
337                     dynamic_cast<Inkscape::LivePathEffect::LPECopyRotate *>(lpe)) {
338                     return false;
339                 }
340             }
341         }
342     }
343     gchar *classes = g_strdup(getRepr()->attribute("class"));
344     if (classes) {
345         Glib::ustring classdata = classes;
346         size_t pos = classdata.find("UnoptimicedTransforms");
347         if ( pos != std::string::npos ) {
348             return false;
349         }
350     }
351     g_free(classes);
352     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
353     return !prefs->getBool("/options/preservetransform/value", false);
354 }
355 
356 /**
357  * notify tranbsform applied to a LPE
358  */
notifyTransform(Geom::Affine const & postmul)359 void SPLPEItem::notifyTransform(Geom::Affine const &postmul)
360 {
361     if (!pathEffectsEnabled())
362         return;
363 
364     PathEffectList path_effect_list(*this->path_effect_list);
365     for (auto &lperef : path_effect_list) {
366         if (!lperef) {
367             continue;
368         }
369         LivePathEffectObject *lpeobj = lperef->lpeobject;
370         if (lpeobj) {
371             Inkscape::LivePathEffect::Effect *lpe = lpeobj->get_lpe();
372             if (lpe && !lpe->is_load) {
373                 lpe->transform_multiply(postmul, this);
374             }
375         }
376     }
377 }
378 
379 // CPPIFY: make pure virtual
update_patheffect(bool)380 void SPLPEItem::update_patheffect(bool /*write*/) {
381     //throw;
382 }
383 
384 /**
385  * Calls any registered handlers for the update_patheffect action
386  */
387 void
sp_lpe_item_update_patheffect(SPLPEItem * lpeitem,bool wholetree,bool write)388 sp_lpe_item_update_patheffect (SPLPEItem *lpeitem, bool wholetree, bool write)
389 {
390 #ifdef SHAPE_VERBOSE
391     g_message("sp_lpe_item_update_patheffect: %p\n", lpeitem);
392 #endif
393     g_return_if_fail (lpeitem != nullptr);
394     g_return_if_fail (SP_IS_OBJECT (lpeitem));
395     g_return_if_fail (SP_IS_LPE_ITEM (lpeitem));
396 
397     // Do not check for LPE item to allow LPE work on clips/mask
398     if (!lpeitem->pathEffectsEnabled())
399         return;
400 
401     SPLPEItem *top = nullptr;
402 
403     if (wholetree) {
404         SPLPEItem *prev_parent = lpeitem;
405         SPLPEItem *parent = dynamic_cast<SPLPEItem*>(prev_parent->parent);
406         while (parent && parent->hasPathEffectRecursive()) {
407             prev_parent = parent;
408             parent = dynamic_cast<SPLPEItem*>(prev_parent->parent);
409         }
410         top = prev_parent;
411     }
412     else {
413         top = lpeitem;
414     }
415     top->update_patheffect(write);
416 }
417 
418 /**
419  * Gets called when any of the lpestack's lpeobject repr contents change: i.e. parameter change in any of the stacked LPEs
420  */
421 static void
lpeobject_ref_modified(SPObject *,guint flags,SPLPEItem * lpeitem)422 lpeobject_ref_modified(SPObject */*href*/, guint flags, SPLPEItem *lpeitem)
423 {
424 #ifdef SHAPE_VERBOSE
425     g_message("lpeobject_ref_modified");
426 #endif
427     if (flags != 29 && flags != 253) {
428         sp_lpe_item_update_patheffect (lpeitem, true, true);
429     }
430 }
431 
432 static void
sp_lpe_item_create_original_path_recursive(SPLPEItem * lpeitem)433 sp_lpe_item_create_original_path_recursive(SPLPEItem *lpeitem)
434 {
435     g_return_if_fail(lpeitem != nullptr);
436 
437     SPClipPath *clip_path = SP_ITEM(lpeitem)->getClipObject();
438     if(clip_path) {
439         std::vector<SPObject*> clip_path_list = clip_path->childList(true);
440         for (auto iter : clip_path_list) {
441             SPLPEItem * clip_data = dynamic_cast<SPLPEItem *>(iter);
442             sp_lpe_item_create_original_path_recursive(clip_data);
443         }
444     }
445 
446     SPMask *mask_path = SP_ITEM(lpeitem)->getMaskObject();
447     if(mask_path) {
448         std::vector<SPObject*> mask_path_list = mask_path->childList(true);
449         for (auto iter : mask_path_list) {
450             SPLPEItem * mask_data = dynamic_cast<SPLPEItem *>(iter);
451             sp_lpe_item_create_original_path_recursive(mask_data);
452         }
453     }
454     if (SP_IS_GROUP(lpeitem)) {
455         std::vector<SPItem*> item_list = sp_item_group_item_list(SP_GROUP(lpeitem));
456         for (auto subitem : item_list) {
457             if (SP_IS_LPE_ITEM(subitem)) {
458                 sp_lpe_item_create_original_path_recursive(SP_LPE_ITEM(subitem));
459             }
460         }
461     } else if (SPPath * path = dynamic_cast<SPPath *>(lpeitem)) {
462         Inkscape::XML::Node *pathrepr = path->getRepr();
463         if ( !pathrepr->attribute("inkscape:original-d") ) {
464             if (gchar const * value = pathrepr->attribute("d")) {
465                 Geom::PathVector pv = sp_svg_read_pathv(value);
466                 pathrepr->setAttribute("inkscape:original-d", value);
467                 path->setCurveBeforeLPE(std::make_unique<SPCurve>(pv));
468             }
469         }
470     } else if (SPShape * shape = dynamic_cast<SPShape *>(lpeitem)) {
471         if (!shape->curveBeforeLPE()) {
472             shape->setCurveBeforeLPE(shape->curve());
473         }
474     }
475 }
476 
477 static void
sp_lpe_item_cleanup_original_path_recursive(SPLPEItem * lpeitem,bool keep_paths,bool force,bool is_clip_mask)478 sp_lpe_item_cleanup_original_path_recursive(SPLPEItem *lpeitem, bool keep_paths, bool force, bool is_clip_mask)
479 {
480     g_return_if_fail(lpeitem != nullptr);
481     SPItem  *item  = dynamic_cast<SPItem *>(lpeitem);
482     if (!item) {
483         return;
484     }
485     SPGroup *group = dynamic_cast<SPGroup*>(lpeitem);
486     SPShape *shape = dynamic_cast<SPShape*>(lpeitem);
487     SPPath  *path  = dynamic_cast<SPPath *>(lpeitem);
488     SPClipPath *clip_path = item->getClipObject();
489     if(clip_path) {
490         std::vector<SPObject*> clip_path_list = clip_path->childList(true);
491         for (auto iter : clip_path_list) {
492             SPLPEItem* clip_data = dynamic_cast<SPLPEItem*>(iter);
493             if (clip_data) {
494                 sp_lpe_item_cleanup_original_path_recursive(clip_data, keep_paths, lpeitem && !lpeitem->hasPathEffectRecursive(), true);
495             }
496         }
497     }
498 
499     SPMask *mask_path = item->getMaskObject();
500     if(mask_path) {
501         std::vector<SPObject*> mask_path_list = mask_path->childList(true);
502         for (auto iter : mask_path_list) {
503             SPLPEItem* mask_data = dynamic_cast<SPLPEItem*>(iter);
504             if (mask_data) {
505                 sp_lpe_item_cleanup_original_path_recursive(mask_data, keep_paths, lpeitem && !lpeitem->hasPathEffectRecursive(), true);
506             }
507         }
508     }
509 
510     if (group) {
511         std::vector<SPItem*> item_list = sp_item_group_item_list(SP_GROUP(lpeitem));
512         for (auto iter : item_list) {
513             SPLPEItem* subitem = dynamic_cast<SPLPEItem*>(iter);
514             if (subitem) {
515                 sp_lpe_item_cleanup_original_path_recursive(subitem, keep_paths);
516             }
517         }
518     } else if (path) {
519         Inkscape::XML::Node *repr = lpeitem->getRepr();
520         if (repr->attribute("inkscape:original-d") &&
521             !lpeitem->hasPathEffectRecursive() &&
522             (!is_clip_mask ||
523             ( is_clip_mask && force)))
524         {
525             if (!keep_paths) {
526                 repr->setAttribute("d", repr->attribute("inkscape:original-d"));
527             }
528             repr->removeAttribute("inkscape:original-d");
529             path->setCurveBeforeLPE(nullptr);
530             if (!(shape->curve()->get_segment_count())) {
531                 repr->parent()->removeChild(repr);
532             }
533         } else {
534             if (!keep_paths) {
535                 sp_lpe_item_update_patheffect(lpeitem, true, true);
536             }
537         }
538     } else if (shape) {
539         Inkscape::XML::Node *repr = lpeitem->getRepr();
540         SPCurve const *c_lpe = shape->curve();
541         Glib::ustring d_str;
542         if (c_lpe) {
543             d_str = sp_svg_write_path(c_lpe->get_pathvector());
544         } else if (shape->getAttribute("d")) {
545             // when "c_lpe" is null we need to stop to allow LPE on elenments with mask and clip
546             // work properly but we need not block when doing CUT/COPY... Operatrions using original-d
547             // so if we not in this case stop execution
548             Inkscape::Preferences *prefs = Inkscape::Preferences::get();
549             bool flattening = prefs->getBool("/live_effects/flattening", false);
550             if (!flattening) {
551                 return;
552             }
553             d_str = shape->getAttribute("d");
554         } else {
555             return;
556         }
557         if (!lpeitem->hasPathEffectRecursive() &&
558             (!is_clip_mask ||
559             ( is_clip_mask && force)))
560         {
561             if (!keep_paths) {
562                 repr->removeAttribute("d");
563                 shape->setCurveBeforeLPE(nullptr);
564             } else {
565                 const char * id = repr->attribute("id");
566                 const char * style = repr->attribute("style");
567                 // remember the position of the item
568                 gint pos = shape->getRepr()->position();
569                 // remember parent
570                 Inkscape::XML::Node *parent = shape->getRepr()->parent();
571                 // remember class
572                 char const *class_attr = shape->getRepr()->attribute("class");
573                 // remember title
574                 gchar *title = shape->title();
575                 // remember description
576                 gchar *desc = shape->desc();
577                 // remember transformation
578                 gchar const *transform_str = shape->getRepr()->attribute("transform");
579                 // Mask
580                 gchar const *mask_str = (gchar *) shape->getRepr()->attribute("mask");
581                 // Clip path
582                 gchar const *clip_str = (gchar *) shape->getRepr()->attribute("clip-path");
583 
584                 /* Rotation center */
585                 gchar const *transform_center_x = shape->getRepr()->attribute("inkscape:transform-center-x");
586                 gchar const *transform_center_y = shape->getRepr()->attribute("inkscape:transform-center-y");
587 
588                 // remember highlight color
589                 guint32 highlight_color = 0;
590                 if (shape->isHighlightSet())
591                     highlight_color = shape->highlight_color();
592 
593                 // It's going to resurrect, so we delete without notifying listeners.
594                 SPDocument * doc = shape->document;
595                 shape->deleteObject(false);
596                 Inkscape::XML::Document *xml_doc = doc->getReprDoc();
597                 Inkscape::XML::Node *repr = xml_doc->createElement("svg:path");
598                 // restore id
599                 repr->setAttribute("id", id);
600                 // restore class
601                 repr->setAttribute("class", class_attr);
602                 // restore transform
603                 repr->setAttribute("transform", transform_str);
604                 // restore clip
605                 repr->setAttribute("clip-path", clip_str);
606                 // restore mask
607                 repr->setAttribute("mask", mask_str);
608                 // restore transform_center_x
609                 repr->setAttribute("inkscape:transform-center-x", transform_center_x);
610                 // restore transform_center_y
611                 repr->setAttribute("inkscape:transform-center-y", transform_center_y);
612                 //restore d
613                 repr->setAttribute("d", d_str);
614                 //restore style
615                 repr->setAttribute("style", style);
616                 // add the new repr to the parent
617                 parent->appendChild(repr);
618                 SPObject* newObj = doc->getObjectByRepr(repr);
619                 if (title && newObj) {
620                     newObj->setTitle(title);
621                     g_free(title);
622                 }
623                 if (desc && newObj) {
624                     newObj->setDesc(desc);
625                     g_free(desc);
626                 }
627                 if (highlight_color && newObj) {
628                     SP_ITEM(newObj)->setHighlightColor( highlight_color );
629                 }
630                 // move to the saved position
631                 repr->setPosition(pos > 0 ? pos : 0);
632                 Inkscape::GC::release(repr);
633                 lpeitem = dynamic_cast<SPLPEItem *>(newObj);
634             }
635         } else {
636             if (!keep_paths) {
637                 sp_lpe_item_update_patheffect(lpeitem, true, true);
638             }
639         }
640     }
641 }
642 
643 
644 
addPathEffect(std::string value,bool reset)645 void SPLPEItem::addPathEffect(std::string value, bool reset)
646 {
647     if (!value.empty()) {
648         // Apply the path effects here because in the casse of a group, lpe->resetDefaults
649         // needs that all the subitems have their effects applied
650         SPGroup *group = dynamic_cast<SPGroup *>(this);
651         if (group) {
652             sp_lpe_item_update_patheffect(this, false, true);
653         }
654         // Disable the path effects while preparing the new lpe
655         sp_lpe_item_enable_path_effects(this, false);
656 
657         // Add the new reference to the list of LPE references
658         HRefList hreflist;
659         for (PathEffectList::const_iterator it = this->path_effect_list->begin(); it != this->path_effect_list->end(); ++it)
660         {
661             hreflist.push_back( std::string((*it)->lpeobject_href) );
662         }
663         hreflist.push_back(value); // C++11: should be emplace_back std::move'd  (also the reason why passed by value to addPathEffect)
664 
665         this->setAttributeOrRemoveIfEmpty("inkscape:path-effect", hreflist_svg_string(hreflist));
666         // Make sure that ellipse is stored as <svg:path>
667         if( SP_IS_GENERICELLIPSE(this)) {
668             SP_GENERICELLIPSE(this)->write( this->getRepr()->document(), this->getRepr(), SP_OBJECT_WRITE_EXT );
669         }
670 
671 
672         LivePathEffectObject *lpeobj = this->path_effect_list->back()->lpeobject;
673         if (lpeobj && lpeobj->get_lpe()) {
674             Inkscape::LivePathEffect::Effect *lpe = lpeobj->get_lpe();
675             // Ask the path effect to reset itself if it doesn't have parameters yet
676             if (reset) {
677                 // has to be called when all the subitems have their lpes applied
678                 lpe->resetDefaults(this);
679             }
680             // Moved here to fix #1299461, we can call previous function twice after
681             // if anyone find necessary
682             // make sure there is an original-d for paths!!!
683             sp_lpe_item_create_original_path_recursive(this);
684             // perform this once when the effect is applied
685             lpe->doOnApply_impl(this);
686         }
687 
688         //Enable the path effects now that everything is ready to apply the new path effect
689         sp_lpe_item_enable_path_effects(this, true);
690 
691         // Apply the path effect
692         sp_lpe_item_update_patheffect(this, true, true);
693     }
694 }
695 
addPathEffect(LivePathEffectObject * new_lpeobj)696 void SPLPEItem::addPathEffect(LivePathEffectObject * new_lpeobj)
697 {
698     const gchar * repr_id = new_lpeobj->getRepr()->attribute("id");
699     gchar *hrefstr = g_strdup_printf("#%s", repr_id);
700     this->addPathEffect(hrefstr, false);
701     g_free(hrefstr);
702 }
703 
704 /**
705  *  If keep_path is true, the item should not be updated, effectively 'flattening' the LPE.
706  */
removeCurrentPathEffect(bool keep_paths)707 void SPLPEItem::removeCurrentPathEffect(bool keep_paths)
708 {
709     Inkscape::LivePathEffect::LPEObjectReference* lperef = this->getCurrentLPEReference();
710     if (!lperef) {
711         return;
712     }
713     if (Inkscape::LivePathEffect::Effect* effect_ = this->getCurrentLPE()) {
714         effect_->keep_paths = keep_paths;
715         effect_->on_remove_all = false;
716         effect_->doOnRemove(this);
717         this->path_effect_list->remove(lperef); //current lpe ref is always our 'own' pointer from the path_effect_list
718         this->setAttributeOrRemoveIfEmpty("inkscape:path-effect", patheffectlist_svg_string(*this->path_effect_list));
719         if (!keep_paths) {
720             // Make sure that ellipse is stored as <svg:circle> or <svg:ellipse> if possible.
721             if( SP_IS_GENERICELLIPSE(this)) {
722                 SP_GENERICELLIPSE(this)->write( this->getRepr()->document(), this->getRepr(), SP_OBJECT_WRITE_EXT );
723             }
724         }
725         sp_lpe_item_cleanup_original_path_recursive(this, keep_paths);
726     }
727 }
728 
729 /**
730  *  If keep_path is true, the item should not be updated, effectively 'flattening' the LPE.
731  */
removeAllPathEffects(bool keep_paths)732 void SPLPEItem::removeAllPathEffects(bool keep_paths)
733 {
734     if (keep_paths) {
735         if (path_effect_list->empty()) {
736             return;
737         }
738     }
739     PathEffectList path_effect_list(*this->path_effect_list);
740     for (auto &lperef : path_effect_list) {
741         if (!lperef) {
742             continue;
743         }
744         LivePathEffectObject *lpeobj = lperef->lpeobject;
745         if (lpeobj) {
746             Inkscape::LivePathEffect::Effect * lpe = lpeobj->get_lpe();
747             if (lpe) {
748                 lpe->keep_paths = keep_paths;
749                 lpe->on_remove_all = true;
750                 lpe->doOnRemove(this);
751             }
752         }
753     }
754     clear_path_effect_list(this->path_effect_list);
755     this->removeAttribute("inkscape:path-effect");
756     if (!keep_paths) {
757         // Make sure that ellipse is stored as <svg:circle> or <svg:ellipse> if possible.
758         if (SP_IS_GENERICELLIPSE(this)) {
759             SP_GENERICELLIPSE(this)->write(this->getRepr()->document(), this->getRepr(), SP_OBJECT_WRITE_EXT);
760         }
761     }
762     sp_lpe_item_cleanup_original_path_recursive(this, keep_paths);
763 
764 }
765 
downCurrentPathEffect()766 void SPLPEItem::downCurrentPathEffect()
767 {
768     Inkscape::LivePathEffect::LPEObjectReference* lperef = getCurrentLPEReference();
769     if (!lperef)
770         return;
771 
772     PathEffectList new_list = *this->path_effect_list;
773     PathEffectList::iterator cur_it = find( new_list.begin(), new_list.end(), lperef );
774     if (cur_it != new_list.end()) {
775         PathEffectList::iterator down_it = cur_it;
776         ++down_it;
777         if (down_it != new_list.end()) { // perhaps current effect is already last effect
778             std::iter_swap(cur_it, down_it);
779         }
780     }
781 
782     this->setAttributeOrRemoveIfEmpty("inkscape:path-effect", patheffectlist_svg_string(new_list));
783 
784     sp_lpe_item_cleanup_original_path_recursive(this, false);
785 }
786 
upCurrentPathEffect()787 void SPLPEItem::upCurrentPathEffect()
788 {
789     Inkscape::LivePathEffect::LPEObjectReference* lperef = getCurrentLPEReference();
790     if (!lperef)
791         return;
792 
793     PathEffectList new_list = *this->path_effect_list;
794     PathEffectList::iterator cur_it = find( new_list.begin(), new_list.end(), lperef );
795     if (cur_it != new_list.end() && cur_it != new_list.begin()) {
796         PathEffectList::iterator up_it = cur_it;
797         --up_it;
798         std::iter_swap(cur_it, up_it);
799     }
800 
801     this->setAttributeOrRemoveIfEmpty("inkscape:path-effect", patheffectlist_svg_string(new_list));
802 
803     sp_lpe_item_cleanup_original_path_recursive(this, false);
804 }
805 
806 /** used for shapes so they can see if they should also disable shape calculation and read from d= */
hasBrokenPathEffect() const807 bool SPLPEItem::hasBrokenPathEffect() const
808 {
809     if (path_effect_list->empty()) {
810         return false;
811     }
812 
813     // go through the list; if some are unknown or invalid, return true
814     PathEffectList path_effect_list(*this->path_effect_list);
815     for (auto &lperef : path_effect_list) {
816         LivePathEffectObject *lpeobj = lperef->lpeobject;
817         if (!lpeobj || !lpeobj->get_lpe()) {
818             return true;
819         }
820     }
821 
822     return false;
823 }
824 
hasPathEffectOfTypeRecursive(int const type,bool is_ready) const825 bool SPLPEItem::hasPathEffectOfTypeRecursive(int const type, bool is_ready) const
826 {
827     SPLPEItem * parent_lpe_item = dynamic_cast<SPLPEItem *>(parent);
828     if (parent_lpe_item) {
829         return hasPathEffectOfType(type, is_ready) || parent_lpe_item->hasPathEffectOfTypeRecursive(type, is_ready);
830     } else {
831         return hasPathEffectOfType(type, is_ready);
832     }
833 }
834 
hasPathEffectOfType(int const type,bool is_ready) const835 bool SPLPEItem::hasPathEffectOfType(int const type, bool is_ready) const
836 {
837     if (path_effect_list->empty()) {
838         return false;
839     }
840 
841     for (PathEffectList::const_iterator it = path_effect_list->begin(); it != path_effect_list->end(); ++it)
842     {
843         LivePathEffectObject const *lpeobj = (*it)->lpeobject;
844         if (lpeobj) {
845             Inkscape::LivePathEffect::Effect const* lpe = lpeobj->get_lpe();
846             if (lpe && (lpe->effectType() == type)) {
847                 if (is_ready || lpe->isReady()) {
848                     return true;
849                 }
850             }
851         }
852     }
853 
854     return false;
855 }
856 
857 /**
858  * returns true when any LPE apply to clip or mask.
859  */
hasPathEffectOnClipOrMask(SPLPEItem * shape) const860 bool SPLPEItem::hasPathEffectOnClipOrMask(SPLPEItem * shape) const
861 {
862     if (shape->hasPathEffectRecursive()) {
863         return true;
864     }
865     if (!path_effect_list || path_effect_list->empty()) {
866         return false;
867     }
868 
869     PathEffectList path_effect_list(*this->path_effect_list);
870     for (auto &lperef : path_effect_list) {
871         LivePathEffectObject *lpeobj = lperef->lpeobject;
872         if (!lpeobj) {
873             continue;
874         }
875         Inkscape::LivePathEffect::Effect *lpe = lpeobj->get_lpe();
876         if (lpe->apply_to_clippath_and_mask) {
877             return true;
878         }
879     }
880     return false;
881 }
882 
883 /**
884  * returns true when any LPE apply to clip or mask. recursive mode
885  */
hasPathEffectOnClipOrMaskRecursive(SPLPEItem * shape) const886 bool SPLPEItem::hasPathEffectOnClipOrMaskRecursive(SPLPEItem * shape) const
887 {
888     SPLPEItem * parent_lpe_item = dynamic_cast<SPLPEItem *>(parent);
889     if (parent_lpe_item) {
890         return hasPathEffectOnClipOrMask(shape) || parent_lpe_item->hasPathEffectOnClipOrMaskRecursive(shape);
891     }
892     else {
893         return hasPathEffectOnClipOrMask(shape);
894     }
895 }
896 
hasPathEffect() const897 bool SPLPEItem::hasPathEffect() const
898 {
899     if (!path_effect_list || path_effect_list->empty()) {
900         return false;
901     }
902 
903     // go through the list; if some are unknown or invalid, we are not an LPE item!
904     PathEffectList path_effect_list(*this->path_effect_list);
905     for (auto &lperef : path_effect_list) {
906         LivePathEffectObject *lpeobj = lperef->lpeobject;
907         if (!lpeobj || !lpeobj->get_lpe()) {
908             return false;
909         }
910     }
911 
912     return true;
913 }
914 
hasPathEffectRecursive() const915 bool SPLPEItem::hasPathEffectRecursive() const
916 {
917     SPLPEItem * parent_lpe_item = dynamic_cast<SPLPEItem *>(parent);
918     if (parent_lpe_item) {
919         return hasPathEffect() || parent_lpe_item->hasPathEffectRecursive();
920     }
921     else {
922         return hasPathEffect();
923     }
924 }
925 
926 void
resetClipPathAndMaskLPE(bool fromrecurse)927 SPLPEItem::resetClipPathAndMaskLPE(bool fromrecurse)
928 {
929     if (fromrecurse) {
930         SPGroup*   group = dynamic_cast<SPGroup  *>(this);
931         SPShape*   shape = dynamic_cast<SPShape  *>(this);
932         if (group) {
933             std::vector<SPItem*> item_list = sp_item_group_item_list(group);
934             for (auto iter2 : item_list) {
935                 SPLPEItem * subitem = dynamic_cast<SPLPEItem *>(iter2);
936                 if (subitem) {
937                     subitem->resetClipPathAndMaskLPE(true);
938                 }
939             }
940         } else if (shape) {
941             shape->setCurveInsync(SPCurve::copy(shape->curveForEdit()));
942             if (!hasPathEffectOnClipOrMaskRecursive(shape)) {
943                 shape->removeAttribute("inkscape:original-d");
944                 shape->setCurveBeforeLPE(nullptr);
945             } else {
946                 // make sure there is an original-d for paths!!!
947                 sp_lpe_item_create_original_path_recursive(shape);
948             }
949         }
950         return;
951     }
952     SPClipPath *clip_path = this->getClipObject();
953     if(clip_path) {
954         std::vector<SPObject*> clip_path_list = clip_path->childList(true);
955         for (auto iter : clip_path_list) {
956             SPGroup*   group = dynamic_cast<SPGroup  *>(iter);
957             SPShape*   shape = dynamic_cast<SPShape  *>(iter);
958             if (group) {
959                 std::vector<SPItem*> item_list = sp_item_group_item_list(group);
960                 for (auto iter2 : item_list) {
961                     SPLPEItem * subitem = dynamic_cast<SPLPEItem *>(iter2);
962                     if (subitem) {
963                         subitem->resetClipPathAndMaskLPE(true);
964                     }
965                 }
966             } else if (shape) {
967                 shape->setCurveInsync(SPCurve::copy(shape->curveForEdit()));
968                 if (!hasPathEffectOnClipOrMaskRecursive(shape)) {
969                     shape->removeAttribute("inkscape:original-d");
970                     shape->setCurveBeforeLPE(nullptr);
971                 } else {
972                     // make sure there is an original-d for paths!!!
973                     sp_lpe_item_create_original_path_recursive(shape);
974                 }
975             }
976         }
977     }
978     SPMask *mask = this->getMaskObject();
979     if(mask) {
980         std::vector<SPObject*> mask_list = mask->childList(true);
981         for (auto iter : mask_list) {
982             SPGroup*   group = dynamic_cast<SPGroup  *>(iter);
983             SPShape*   shape = dynamic_cast<SPShape  *>(iter);
984             if (group) {
985                 std::vector<SPItem*> item_list = sp_item_group_item_list(group);
986                 for (auto iter2 : item_list) {
987                     SPLPEItem * subitem = dynamic_cast<SPLPEItem *>(iter2);
988                     if (subitem) {
989                         subitem->resetClipPathAndMaskLPE(true);
990                     }
991                 }
992             } else if (shape) {
993                 shape->setCurveInsync(SPCurve::copy(shape->curveForEdit()));
994                 if (!hasPathEffectOnClipOrMaskRecursive(shape)) {
995                     shape->removeAttribute("inkscape:original-d");
996                     shape->setCurveBeforeLPE(nullptr);
997                 } else {
998                     // make sure there is an original-d for paths!!!
999                     sp_lpe_item_create_original_path_recursive(shape);
1000                 }
1001             }
1002         }
1003     }
1004 }
1005 
1006 void
applyToClipPath(SPItem * to,Inkscape::LivePathEffect::Effect * lpe)1007 SPLPEItem::applyToClipPath(SPItem* to, Inkscape::LivePathEffect::Effect *lpe)
1008 {
1009     if (lpe && !lpe->apply_to_clippath_and_mask) {
1010         return;
1011     }
1012     SPClipPath *clip_path = to->getClipObject();
1013     if(clip_path) {
1014         std::vector<SPObject*> clip_path_list = clip_path->childList(true);
1015         for (auto clip_data : clip_path_list) {
1016             applyToClipPathOrMask(SP_ITEM(clip_data), to, lpe);
1017         }
1018     }
1019 }
1020 
1021 void
applyToMask(SPItem * to,Inkscape::LivePathEffect::Effect * lpe)1022 SPLPEItem::applyToMask(SPItem* to, Inkscape::LivePathEffect::Effect *lpe)
1023 {
1024     if (lpe && !lpe->apply_to_clippath_and_mask) {
1025         return;
1026     }
1027     SPMask *mask = to->getMaskObject();
1028     if(mask) {
1029         std::vector<SPObject*> mask_list = mask->childList(true);
1030         for (auto mask_data : mask_list) {
1031             applyToClipPathOrMask(SP_ITEM(mask_data), to, lpe);
1032         }
1033     }
1034 }
1035 
1036 void
applyToClipPathOrMask(SPItem * clip_mask,SPItem * to,Inkscape::LivePathEffect::Effect * lpe)1037 SPLPEItem::applyToClipPathOrMask(SPItem *clip_mask, SPItem* to, Inkscape::LivePathEffect::Effect *lpe)
1038 {
1039     SPGroup*   group = dynamic_cast<SPGroup  *>(clip_mask);
1040     SPShape*   shape = dynamic_cast<SPShape  *>(clip_mask);
1041     SPRoot *root = this->document->getRoot();
1042     if (group) {
1043         std::vector<SPItem*> item_list = sp_item_group_item_list(group);
1044         for (auto subitem : item_list) {
1045             applyToClipPathOrMask(subitem, to, lpe);
1046         }
1047     } else if (shape) {
1048         if (sp_version_inside_range(root->version.inkscape, 0, 1, 0, 92)) {
1049             shape->removeAttribute("inkscape:original-d");
1050         } else {
1051             auto c = SPCurve::copy(shape->curve());
1052             if (c) {
1053                 bool success = false;
1054                 try {
1055                     if (lpe) {
1056                         success = this->performOnePathEffect(c.get(), shape, lpe, true);
1057                     } else {
1058                         success = this->performPathEffect(c.get(), shape, true);
1059                     }
1060                 } catch (std::exception & e) {
1061                     g_warning("Exception during LPE execution. \n %s", e.what());
1062                     if (SP_ACTIVE_DESKTOP && SP_ACTIVE_DESKTOP->messageStack()) {
1063                         SP_ACTIVE_DESKTOP->messageStack()->flash( Inkscape::WARNING_MESSAGE,
1064                                         _("An exception occurred during execution of the Path Effect.") );
1065                     }
1066                     success = false;
1067                 }
1068                 if (success && c) {
1069                     auto str = sp_svg_write_path(c->get_pathvector());
1070                     shape->setCurveInsync(std::move(c));
1071                     shape->setAttribute("d", str);
1072                 } else {
1073                      // LPE was unsuccessful or doeffect stack return null.. Read the old 'd'-attribute.
1074                     if (gchar const * value = shape->getAttribute("d")) {
1075                         Geom::PathVector pv = sp_svg_read_pathv(value);
1076                         shape->setCurve(std::make_unique<SPCurve>(pv));
1077                     }
1078                 }
1079                 shape->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1080             }
1081         }
1082     }
1083 }
1084 
1085 Inkscape::LivePathEffect::Effect*
getPathEffectOfType(int type)1086 SPLPEItem::getPathEffectOfType(int type)
1087 {
1088     PathEffectList path_effect_list(*this->path_effect_list);
1089     for (auto &lperef : path_effect_list) {
1090         LivePathEffectObject *lpeobj = lperef->lpeobject;
1091         if (lpeobj) {
1092             Inkscape::LivePathEffect::Effect* lpe = lpeobj->get_lpe();
1093             if (lpe && (lpe->effectType() == type)) {
1094                 return lpe;
1095             }
1096         }
1097     }
1098     return nullptr;
1099 }
1100 
1101 Inkscape::LivePathEffect::Effect const*
getPathEffectOfType(int type) const1102 SPLPEItem::getPathEffectOfType(int type) const
1103 {
1104     std::list<Inkscape::LivePathEffect::LPEObjectReference *>::const_iterator i;
1105     for (i = path_effect_list->begin(); i != path_effect_list->end(); ++i) {
1106         LivePathEffectObject const *lpeobj = (*i)->lpeobject;
1107         if (lpeobj) {
1108             Inkscape::LivePathEffect::Effect const *lpe = lpeobj->get_lpe();
1109             if (lpe && (lpe->effectType() == type)) {
1110                 return lpe;
1111             }
1112         }
1113     }
1114     return nullptr;
1115 }
1116 
editNextParamOncanvas(SPDesktop * dt)1117 void SPLPEItem::editNextParamOncanvas(SPDesktop *dt)
1118 {
1119     Inkscape::LivePathEffect::LPEObjectReference *lperef = this->getCurrentLPEReference();
1120     if (lperef && lperef->lpeobject && lperef->lpeobject->get_lpe()) {
1121         lperef->lpeobject->get_lpe()->editNextParamOncanvas(this, dt);
1122     }
1123 }
1124 
child_added(Inkscape::XML::Node * child,Inkscape::XML::Node * ref)1125 void SPLPEItem::child_added(Inkscape::XML::Node *child, Inkscape::XML::Node *ref) {
1126     SPItem::child_added(child, ref);
1127 
1128     if (this->hasPathEffectRecursive()) {
1129         SPObject *ochild = this->get_child_by_repr(child);
1130 
1131         if ( ochild && SP_IS_LPE_ITEM(ochild) ) {
1132             sp_lpe_item_create_original_path_recursive(SP_LPE_ITEM(ochild));
1133         }
1134     }
1135 }
remove_child(Inkscape::XML::Node * child)1136 void SPLPEItem::remove_child(Inkscape::XML::Node * child) {
1137     if (this->hasPathEffectRecursive()) {
1138         SPObject *ochild = this->get_child_by_repr(child);
1139 
1140         if ( ochild && SP_IS_LPE_ITEM(ochild) ) {
1141             // we not need to update item because keep paths is false
1142             sp_lpe_item_cleanup_original_path_recursive(SP_LPE_ITEM(ochild), false);
1143         }
1144     }
1145 
1146     SPItem::remove_child(child);
1147 }
1148 
patheffectlist_svg_string(PathEffectList const & list)1149 static std::string patheffectlist_svg_string(PathEffectList const & list)
1150 {
1151     HRefList hreflist;
1152 
1153     for (auto it : list)
1154     {
1155         hreflist.push_back( std::string(it->lpeobject_href) ); // C++11: use emplace_back
1156     }
1157 
1158     return hreflist_svg_string(hreflist);
1159 }
1160 
1161 /**
1162  *  THE function that should be used to generate any patheffectlist string.
1163  * one of the methods to change the effect list:
1164  *  - create temporary href list
1165  *  - populate the templist with the effects from the old list that you want to have and their order
1166  *  - call this function with temp list as param
1167  */
hreflist_svg_string(HRefList const & list)1168 static std::string hreflist_svg_string(HRefList const & list)
1169 {
1170     std::string r;
1171     bool semicolon_first = false;
1172 
1173     for (const auto & it : list)
1174     {
1175         if (semicolon_first) {
1176             r += ';';
1177         }
1178 
1179         semicolon_first = true;
1180 
1181         r += it;
1182     }
1183 
1184     return r;
1185 }
1186 
1187 // Return a copy of the effect list
getEffectList()1188 PathEffectList SPLPEItem::getEffectList()
1189 {
1190     return *path_effect_list;
1191 }
1192 
1193 // Return a copy of the effect list
getEffectList() const1194 PathEffectList const SPLPEItem::getEffectList() const
1195 {
1196     return *path_effect_list;
1197 }
1198 
1199 Inkscape::LivePathEffect::LPEObjectReference*
getPrevLPEReference(Inkscape::LivePathEffect::LPEObjectReference * lperef)1200 SPLPEItem::getPrevLPEReference(Inkscape::LivePathEffect::LPEObjectReference* lperef)
1201 {
1202     Inkscape::LivePathEffect::LPEObjectReference* prev= nullptr;
1203     for (auto & it : *path_effect_list) {
1204         if (it->lpeobject_repr == lperef->lpeobject_repr) {
1205             break;
1206         }
1207         prev = it;
1208     }
1209     return prev;
1210 }
1211 
1212 Inkscape::LivePathEffect::LPEObjectReference*
getNextLPEReference(Inkscape::LivePathEffect::LPEObjectReference * lperef)1213 SPLPEItem::getNextLPEReference(Inkscape::LivePathEffect::LPEObjectReference* lperef)
1214 {
1215     bool match = false;
1216     for (auto & it : *path_effect_list) {
1217         if (match) {
1218             return it;
1219         }
1220         if (it->lpeobject_repr == lperef->lpeobject_repr) {
1221             match = true;
1222         }
1223     }
1224     return nullptr;
1225 }
1226 
1227 size_t
getLPEReferenceIndex(Inkscape::LivePathEffect::LPEObjectReference * lperef) const1228 SPLPEItem::getLPEReferenceIndex(Inkscape::LivePathEffect::LPEObjectReference* lperef) const
1229 {
1230     size_t counter = 0;
1231     for (auto & it : *path_effect_list) {
1232         if (it->lpeobject_repr == lperef->lpeobject_repr) {
1233             return counter;
1234         }
1235         counter++;
1236     }
1237     return Glib::ustring::npos;
1238 }
1239 
getCurrentLPEReference()1240 Inkscape::LivePathEffect::LPEObjectReference* SPLPEItem::getCurrentLPEReference()
1241 {
1242     if (!this->current_path_effect && !this->path_effect_list->empty()) {
1243         setCurrentPathEffect(this->path_effect_list->back());
1244     }
1245 
1246     return this->current_path_effect;
1247 }
1248 
getCurrentLPE()1249 Inkscape::LivePathEffect::Effect* SPLPEItem::getCurrentLPE()
1250 {
1251     Inkscape::LivePathEffect::LPEObjectReference* lperef = getCurrentLPEReference();
1252 
1253     if (lperef && lperef->lpeobject)
1254         return lperef->lpeobject->get_lpe();
1255     else
1256         return nullptr;
1257 }
1258 
getPrevLPE(Inkscape::LivePathEffect::Effect * lpe)1259 Inkscape::LivePathEffect::Effect* SPLPEItem::getPrevLPE(Inkscape::LivePathEffect::Effect* lpe)
1260 {
1261     Inkscape::LivePathEffect::Effect* prev = nullptr;
1262     for (auto & it : *path_effect_list) {
1263         if (it->lpeobject == lpe->getLPEObj()) {
1264             break;
1265         }
1266         prev = it->lpeobject->get_lpe();
1267     }
1268     return prev;
1269 }
1270 
getNextLPE(Inkscape::LivePathEffect::Effect * lpe)1271 Inkscape::LivePathEffect::Effect* SPLPEItem::getNextLPE(Inkscape::LivePathEffect::Effect* lpe)
1272 {
1273     bool match = false;
1274     for (auto & it : *path_effect_list) {
1275         if (match) {
1276             return it->lpeobject->get_lpe();
1277         }
1278         if (it->lpeobject == lpe->getLPEObj()) {
1279             match = true;
1280         }
1281     }
1282     return nullptr;
1283 }
1284 
countLPEOfType(int const type,bool inc_hidden,bool is_ready) const1285 size_t SPLPEItem::countLPEOfType(int const type, bool inc_hidden, bool is_ready) const
1286 {
1287     size_t counter = 0;
1288     if (path_effect_list->empty()) {
1289         return counter;
1290     }
1291 
1292     for (PathEffectList::const_iterator it = path_effect_list->begin(); it != path_effect_list->end(); ++it)
1293     {
1294         LivePathEffectObject const *lpeobj = (*it)->lpeobject;
1295         if (lpeobj) {
1296             Inkscape::LivePathEffect::Effect const* lpe = lpeobj->get_lpe();
1297             if (lpe && (lpe->effectType() == type) && (lpe->is_visible || inc_hidden)) {
1298                 if (is_ready || lpe->isReady()) {
1299                     counter++;
1300                 }
1301             }
1302         }
1303     }
1304 
1305     return counter;
1306 }
1307 
1308 size_t
getLPEIndex(Inkscape::LivePathEffect::Effect * lpe) const1309 SPLPEItem::getLPEIndex(Inkscape::LivePathEffect::Effect* lpe) const
1310 {
1311     size_t counter = 0;
1312     for (auto & it : *path_effect_list) {
1313         if (it->lpeobject == lpe->getLPEObj()) {
1314             return counter;
1315         }
1316         counter++;
1317     }
1318     return Glib::ustring::npos;
1319 }
1320 
setCurrentPathEffect(Inkscape::LivePathEffect::LPEObjectReference * lperef)1321 bool SPLPEItem::setCurrentPathEffect(Inkscape::LivePathEffect::LPEObjectReference* lperef)
1322 {
1323     for (auto & it : *path_effect_list) {
1324         if (it->lpeobject_repr == lperef->lpeobject_repr) {
1325             this->current_path_effect = it;  // current_path_effect should always be a pointer from the path_effect_list !
1326             return true;
1327         }
1328     }
1329 
1330     return false;
1331 }
1332 
1333 /**
1334  * Writes a new "inkscape:path-effect" string to xml, where the old_lpeobjects are substituted by the new ones.
1335  *  Note that this method messes up the item's \c PathEffectList.
1336  */
replacePathEffects(std::vector<LivePathEffectObject const * > const & old_lpeobjs,std::vector<LivePathEffectObject const * > const & new_lpeobjs)1337 void SPLPEItem::replacePathEffects( std::vector<LivePathEffectObject const *> const &old_lpeobjs,
1338                                     std::vector<LivePathEffectObject const *> const &new_lpeobjs )
1339 {
1340     HRefList hreflist;
1341     for (PathEffectList::const_iterator it = this->path_effect_list->begin(); it != this->path_effect_list->end(); ++it)
1342     {
1343         LivePathEffectObject const * current_lpeobj = (*it)->lpeobject;
1344         std::vector<LivePathEffectObject const *>::const_iterator found_it(std::find(old_lpeobjs.begin(), old_lpeobjs.end(), current_lpeobj));
1345 
1346         if ( found_it != old_lpeobjs.end() ) {
1347             std::vector<LivePathEffectObject const *>::difference_type found_index = std::distance (old_lpeobjs.begin(), found_it);
1348             const gchar * repr_id = new_lpeobjs[found_index]->getRepr()->attribute("id");
1349             gchar *hrefstr = g_strdup_printf("#%s", repr_id);
1350             hreflist.push_back( std::string(hrefstr) );
1351             g_free(hrefstr);
1352         }
1353         else {
1354             hreflist.push_back( std::string((*it)->lpeobject_href) );
1355         }
1356     }
1357 
1358     this->setAttributeOrRemoveIfEmpty("inkscape:path-effect", hreflist_svg_string(hreflist));
1359 }
1360 
1361 /**
1362  *  Check all effects in the stack if they are used by other items, and fork them if so.
1363  *  It is not recommended to fork the effects by yourself calling LivePathEffectObject::fork_private_if_necessary,
1364  *  use this method instead.
1365  *  Returns true if one or more effects were forked; returns false if nothing was done.
1366  */
forkPathEffectsIfNecessary(unsigned int nr_of_allowed_users,bool recursive)1367 bool SPLPEItem::forkPathEffectsIfNecessary(unsigned int nr_of_allowed_users, bool recursive)
1368 {
1369     bool forked = false;
1370     SPGroup * group = dynamic_cast<SPGroup *>(this);
1371     if (group && recursive) {
1372         std::vector<SPItem*> item_list = sp_item_group_item_list(group);
1373         for (auto child:item_list) {
1374             SPLPEItem *lpeitem = dynamic_cast<SPLPEItem *>(child);
1375             if (lpeitem && lpeitem->forkPathEffectsIfNecessary(nr_of_allowed_users, recursive)) {
1376                 forked = true;
1377             }
1378         }
1379     }
1380 
1381     if ( this->hasPathEffect() ) {
1382         // If one of the path effects is used by 2 or more items, fork it
1383         // so that each object has its own independent copy of the effect.
1384         // Note: replacing path effects messes up the path effect list
1385 
1386         // Clones of the LPEItem will increase the refcount of the lpeobjects.
1387         // Therefore, nr_of_allowed_users should be increased with the number of clones (i.e. refs to the lpeitem)
1388         nr_of_allowed_users += this->hrefcount;
1389 
1390         std::vector<LivePathEffectObject const *> old_lpeobjs, new_lpeobjs;
1391         PathEffectList effect_list = this->getEffectList();
1392         for (auto & it : effect_list)
1393         {
1394             LivePathEffectObject *lpeobj = it->lpeobject;
1395             if (lpeobj) {
1396                 LivePathEffectObject *forked_lpeobj = lpeobj->fork_private_if_necessary(nr_of_allowed_users);
1397                 if (forked_lpeobj && forked_lpeobj != lpeobj) {
1398                     forked = true;
1399                     forked_lpeobj->get_lpe()->is_load = true;
1400                     old_lpeobjs.push_back(lpeobj);
1401                     new_lpeobjs.push_back(forked_lpeobj);
1402                 }
1403             }
1404         }
1405 
1406         if (forked) {
1407             this->replacePathEffects(old_lpeobjs, new_lpeobjs);
1408         }
1409     }
1410 
1411     return forked;
1412 }
1413 
1414 // Enable or disable the path effects of the item.
sp_lpe_item_enable_path_effects(SPLPEItem * lpeitem,bool enable)1415 void sp_lpe_item_enable_path_effects(SPLPEItem *lpeitem, bool enable)
1416 {
1417     if (enable) {
1418         lpeitem->path_effects_enabled++;
1419     }
1420     else {
1421         lpeitem->path_effects_enabled--;
1422     }
1423 }
1424 
1425 // Are the path effects enabled on this item ?
pathEffectsEnabled() const1426 bool SPLPEItem::pathEffectsEnabled() const
1427 {
1428     return path_effects_enabled > 0;
1429 }
1430 
1431 // 1.1 COPYPASTECLONESTAMPLPEBUG
autoFlattenFix()1432 bool SPLPEItem::autoFlattenFix() {
1433     if (path_effect_list->empty()) {
1434         return false;
1435     }
1436     for (PathEffectList::const_iterator it = path_effect_list->begin(); it != path_effect_list->end(); ++it)
1437     {
1438         LivePathEffectObject const *lpeobj = (*it)->lpeobject;
1439         if (lpeobj) {
1440             Inkscape::LivePathEffect::Effect const* lpe = lpeobj->get_lpe();
1441             if (lpe) {
1442                 Inkscape::LivePathEffect::LPECopyRotate const *rc = dynamic_cast<Inkscape::LivePathEffect::LPECopyRotate const *>(lpe);
1443                 Inkscape::LivePathEffect::LPEMirrorSymmetry const *ms = dynamic_cast<Inkscape::LivePathEffect::LPEMirrorSymmetry const *>(lpe);
1444                 Inkscape::LivePathEffect::LPESlice const *sl = dynamic_cast<Inkscape::LivePathEffect::LPESlice const *>(lpe);
1445                 Inkscape::LivePathEffect::LPEBool const *bo = dynamic_cast<Inkscape::LivePathEffect::LPEBool const *>(lpe);
1446                 if ((rc && rc->split_items) || (ms && ms->split_items)) {
1447                     return true;
1448                 } else if (sl || bo) {
1449                     return true;
1450                 }
1451             }
1452         }
1453     }
1454     return false;
1455 }
1456 
removeAllAutoFlatten()1457 void SPLPEItem::removeAllAutoFlatten()
1458 {
1459     cleanupAutoFlatten();
1460     if (autoFlattenFix()) {
1461         sp_lpe_item_enable_path_effects(this, false);
1462     }
1463     SPGroup *group = dynamic_cast<SPGroup *>(this);
1464     if (group) {
1465         std::vector<SPItem*> item_list = sp_item_group_item_list(group);
1466         for (auto iter : item_list) {
1467             SPLPEItem *subitem = dynamic_cast<SPLPEItem *>(iter);
1468             if (subitem) {
1469                 subitem->removeAllAutoFlatten();
1470             }
1471         }
1472     }
1473     if (autoFlattenFix()) {
1474         SPDocument *doc = this->document;
1475         gchar *id = g_strdup(this->getId());
1476         removeAllPathEffects(true);
1477         if (doc) {
1478             SPObject *newobj = doc->getObjectById(id);
1479             SPLPEItem *newlpitem = dynamic_cast<SPLPEItem *>(newobj);
1480             if (newlpitem && !newlpitem->path_effects_enabled) {
1481                 sp_lpe_item_enable_path_effects(newlpitem, true);
1482             }
1483         }
1484         g_free(id);
1485     }
1486 }
1487 
cleanupAutoFlatten()1488 void SPLPEItem::cleanupAutoFlatten()
1489 {
1490     SPGroup* group = dynamic_cast<SPGroup  *>(this);
1491     if (group) {
1492         std::vector<SPItem*> item_list = sp_item_group_item_list(group);
1493         for (auto iter : item_list) {
1494             SPLPEItem * subitem = dynamic_cast<SPLPEItem *>(iter);
1495             if (subitem) {
1496                 subitem->cleanupAutoFlatten();
1497             }
1498         }
1499     }
1500     Glib::ustring  classes = "-slice";
1501     if (getAttribute("class")) {
1502         Glib::ustring classupd = getAttribute("class");
1503         Glib::ustring finalclass = "";
1504         for (auto classindi : Glib::Regex::split_simple(" ", classupd)) {
1505             size_t pos = classindi.find(classes);
1506             if (pos == Glib::ustring::npos && classindi != "UnoptimicedTransforms") {
1507                 if (finalclass != "") {
1508                     finalclass += " ";
1509                 }
1510                 finalclass += classindi;
1511             }
1512         }
1513         setAttribute("class", finalclass == "" ? nullptr : finalclass.c_str());
1514     }
1515 }
1516 
sp_lpe_item_remove_autoflatten(SPItem * item,const gchar * id)1517 SPObject * sp_lpe_item_remove_autoflatten(SPItem *item, const gchar *id) {
1518     SPObject *ret = dynamic_cast<SPObject *>(item);
1519     SPLPEItem *lpeitem = dynamic_cast<SPLPEItem *>(item);
1520     if (lpeitem) {
1521         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1522         prefs->setBool("/live_effects/flattening", true);
1523         lpeitem->removeAllAutoFlatten();
1524         prefs->setBool("/live_effects/flattening", false);
1525         // we want active document to use clipboard doc
1526         ret = SP_ACTIVE_DOCUMENT->getObjectById(id);
1527     }
1528     return ret;
1529 }
1530 // END COPYPASTECLONESTAMPLPEBUG
1531 
1532 /*
1533   Local Variables:
1534   mode:c++
1535   c-file-style:"stroustrup"
1536   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1537   indent-tabs-mode:nil
1538   fill-column:99
1539   End:
1540 */
1541 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
1542