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