1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Released under GNU GPL v2+, read the file 'COPYING' for more information.
4  */
5 #include "live_effects/lpe-powerclip.h"
6 #include "display/curve.h"
7 #include "live_effects/lpeobject-reference.h"
8 #include "live_effects/lpeobject.h"
9 #include "object/sp-clippath.h"
10 #include "object/sp-defs.h"
11 #include "object/sp-item-group.h"
12 #include "object/sp-item.h"
13 #include "object/sp-path.h"
14 #include "object/sp-shape.h"
15 #include "object/sp-use.h"
16 #include "style.h"
17 #include "svg/svg.h"
18 
19 #include <2geom/intersection-graph.h>
20 #include <2geom/path-intersection.h>
21 // TODO due to internal breakage in glibmm headers, this must be last:
22 #include <glibmm/i18n.h>
23 
24 namespace Inkscape {
25 namespace LivePathEffect {
26 
LPEPowerClip(LivePathEffectObject * lpeobject)27 LPEPowerClip::LPEPowerClip(LivePathEffectObject *lpeobject)
28     : Effect(lpeobject)
29     , hide_clip(_("Hide clip"), _("Hide clip"), "hide_clip", &wr, this, false)
30     , inverse(_("Inverse clip"), _("Inverse clip"), "inverse", &wr, this, true)
31     , flatten(_("Flatten clip"), _("Flatten clip, see fill rule once convert to paths"), "flatten", &wr, this, false)
32     , message(
33           _("Info Box"), _("Important messages"), "message", &wr, this,
34           _("Use fill-rule evenodd on <b>fill and stroke</b> dialog if no flatten result after convert clip to paths."))
35 {
36     registerParameter(&inverse);
37     registerParameter(&flatten);
38     registerParameter(&hide_clip);
39     registerParameter(&message);
40     message.param_set_min_height(55);
41     _updating = false;
42     _legacy = false;
43     // legazy fix between 0.92.4 launch and 1.0beta1
44     if (this->getRepr()->attribute("is_inverse")) {
45         this->getRepr()->removeAttribute("is_inverse");
46         _legacy = true;
47     }
48 }
49 
50 LPEPowerClip::~LPEPowerClip() = default;
51 
sp_bbox_without_clip(SPLPEItem * lpeitem)52 Geom::Path sp_bbox_without_clip(SPLPEItem *lpeitem)
53 {
54     Geom::OptRect bbox = lpeitem->visualBounds(Geom::identity(), true, false, true);
55     if (bbox) {
56         (*bbox).expandBy(5);
57         return Geom::Path(*bbox);
58     }
59     return Geom::Path();
60 }
61 
sp_get_recursive_pathvector(SPLPEItem * item,Geom::PathVector res,bool dir,bool inverse)62 Geom::PathVector sp_get_recursive_pathvector(SPLPEItem *item, Geom::PathVector res, bool dir, bool inverse)
63 {
64     SPGroup *group = dynamic_cast<SPGroup *>(item);
65     if (group) {
66         std::vector<SPItem *> item_list = sp_item_group_item_list(group);
67         for (auto child : item_list) {
68             if (child) {
69                 SPLPEItem *childitem = dynamic_cast<SPLPEItem *>(child);
70                 if (childitem) {
71                     res = sp_get_recursive_pathvector(childitem, res, dir, inverse);
72                 }
73             }
74         }
75     }
76     SPShape *shape = dynamic_cast<SPShape *>(item);
77     if (shape && shape->curve()) {
78         for (auto path : shape->curve()->get_pathvector()) {
79             if (!path.empty()) {
80                 bool pathdir = Geom::path_direction(path);
81                 if (pathdir == dir && inverse) {
82                     path = path.reversed();
83                 }
84                 res.push_back(path);
85             }
86         }
87     }
88     return res;
89 }
90 
getClipPathvector()91 Geom::PathVector LPEPowerClip::getClipPathvector()
92 {
93     Geom::PathVector res;
94     Geom::PathVector res_hlp;
95     if (!sp_lpe_item) {
96         return res;
97     }
98 
99     SPObject *clip_path = sp_lpe_item->getClipObject();
100     if (clip_path) {
101         std::vector<SPObject*> clip_path_list = clip_path->childList(true);
102         clip_path_list.pop_back();
103         if (clip_path_list.size()) {
104             for (auto clip : clip_path_list) {
105                 SPLPEItem *childitem = dynamic_cast<SPLPEItem *>(clip);
106                 if (childitem) {
107                     res_hlp = sp_get_recursive_pathvector(childitem, res_hlp, false, inverse);
108                     if (is_load && _legacy) {
109                         childitem->doWriteTransform(Geom::Translate(0, -999999));
110                     }
111                     if (!childitem->style || !childitem->style->display.set ||
112                         childitem->style->display.value != SP_CSS_DISPLAY_NONE) {
113                         childitem->style->display.set = TRUE;
114                         childitem->style->display.value = SP_CSS_DISPLAY_NONE;
115                         childitem->updateRepr(SP_OBJECT_WRITE_NO_CHILDREN | SP_OBJECT_WRITE_EXT);
116                     }
117                 }
118             }
119             if (is_load && _legacy) {
120                 res_hlp *= Geom::Translate(0, -999999);
121                 _legacy = false;
122             }
123         }
124     }
125     Geom::Path bbox = sp_bbox_without_clip(sp_lpe_item);
126     if (hide_clip) {
127         return bbox;
128     }
129     if (inverse && isVisible()) {
130         res.push_back(bbox);
131     }
132     for (auto path : res_hlp) {
133         res.push_back(path);
134     }
135     return res;
136 }
137 
add()138 void LPEPowerClip::add()
139 {
140     SPDocument *document = getSPDoc();
141     if (!document || !sp_lpe_item) {
142         return;
143     }
144     SPObject *clip_path = sp_lpe_item->getClipObject();
145     SPObject *elemref = NULL;
146     if (clip_path) {
147         Inkscape::XML::Document *xml_doc = document->getReprDoc();
148         Inkscape::XML::Node *parent = clip_path->getRepr();
149         SPLPEItem *childitem = dynamic_cast<SPLPEItem *>(clip_path->childList(true).back());
150         if (childitem) {
151             if (const gchar *powerclip = childitem->getRepr()->attribute("class")) {
152                 if (!strcmp(powerclip, "powerclip")) {
153                     Glib::ustring newclip = Glib::ustring("clipath_") + getId();
154                     Glib::ustring uri = Glib::ustring("url(#") + newclip + Glib::ustring(")");
155                     parent = clip_path->getRepr()->duplicate(xml_doc);
156                     parent->setAttribute("id", newclip);
157                     clip_path = SP_OBJECT(document->getDefs()->appendChildRepr(parent));
158                     Inkscape::GC::release(parent);
159                     sp_lpe_item->setAttribute("clip-path", uri);
160                     SPLPEItem *childitemdel = dynamic_cast<SPLPEItem *>(clip_path->childList(true).back());
161                     if (childitemdel) {
162                         childitemdel->setAttribute("id", getId());
163                         return;
164                     }
165                 }
166             }
167         }
168         Inkscape::XML::Node *clip_path_node = xml_doc->createElement("svg:path");
169         parent->appendChild(clip_path_node);
170         Inkscape::GC::release(clip_path_node);
171         elemref = document->getObjectByRepr(clip_path_node);
172         if (elemref) {
173             if (childitem) {
174                 elemref->setAttribute("style", childitem->getAttribute("style"));
175             } else {
176                 elemref->setAttribute("style", "fill-rule:evenodd");
177             }
178             elemref->setAttribute("class", "powerclip");
179             elemref->setAttribute("id", getId());
180             elemref->setAttribute("d", sp_svg_write_path(getClipPathvector()));
181         } else {
182             sp_lpe_item->removeCurrentPathEffect(false);
183         }
184     } else {
185         sp_lpe_item->removeCurrentPathEffect(false);
186     }
187 }
188 
getId()189 Glib::ustring LPEPowerClip::getId() { return Glib::ustring("lpe_") + Glib::ustring(getLPEObj()->getId()); }
190 
upd()191 void LPEPowerClip::upd()
192 {
193     SPDocument *document = getSPDoc();
194     if (!document || !sp_lpe_item) {
195         return;
196     }
197     SPObject *elemref = document->getObjectById(getId().c_str());
198     if (elemref && sp_lpe_item) {
199         elemref->setAttribute("d", sp_svg_write_path(getClipPathvector()));
200         elemref->updateRepr(SP_OBJECT_WRITE_NO_CHILDREN | SP_OBJECT_WRITE_EXT);
201     } else {
202         add();
203     }
204 }
205 
206 
doBeforeEffect(SPLPEItem const * lpeitem)207 void LPEPowerClip::doBeforeEffect(SPLPEItem const *lpeitem)
208 {
209     if (!_updating) {
210         upd();
211     }
212 }
213 
214 void
doOnRemove(SPLPEItem const *)215 LPEPowerClip::doOnRemove (SPLPEItem const* /*lpeitem*/)
216 {
217     SPDocument *document = getSPDoc();
218     if (!document) {
219         return;
220     }
221     if (keep_paths) {
222         SPObject *clip_path = sp_lpe_item->getClipObject();
223         if (clip_path) {
224             SPLPEItem *childitem = dynamic_cast<SPLPEItem *>(clip_path->childList(true).front());
225             childitem->deleteObject();
226         }
227         return;
228     }
229     _updating = true;
230     SPObject *elemref = document->getObjectById(getId().c_str());
231     if (elemref) {
232         elemref->deleteObject();
233     }
234     SPObject *clip_path = sp_lpe_item->getClipObject();
235     if (clip_path) {
236         std::vector<SPObject *> clip_path_list = clip_path->childList(true);
237         for (auto clip : clip_path_list) {
238             SPLPEItem *childitem = dynamic_cast<SPLPEItem *>(clip);
239             if (childitem) {
240                 if (!childitem->style || childitem->style->display.set ||
241                     childitem->style->display.value == SP_CSS_DISPLAY_NONE) {
242                     childitem->style->display.set = TRUE;
243                     childitem->style->display.value = SP_CSS_DISPLAY_BLOCK;
244                     childitem->updateRepr(SP_OBJECT_WRITE_NO_CHILDREN | SP_OBJECT_WRITE_EXT);
245                 }
246             }
247         }
248     }
249 }
250 
251 Geom::PathVector
doEffect_path(Geom::PathVector const & path_in)252 LPEPowerClip::doEffect_path(Geom::PathVector const & path_in){
253     Geom::PathVector path_out = path_in;
254     if (flatten) {
255         Geom::PathVector c_pv = getClipPathvector();
256         std::unique_ptr<Geom::PathIntersectionGraph> pig(new Geom::PathIntersectionGraph(c_pv, path_out));
257         if (pig && !c_pv.empty() && !path_out.empty()) {
258             path_out = pig->getIntersection();
259         }
260     }
261     return path_out;
262 }
263 
doOnVisibilityToggled(SPLPEItem const * lpeitem)264 void LPEPowerClip::doOnVisibilityToggled(SPLPEItem const *lpeitem) { upd(); }
265 
sp_remove_powerclip(Inkscape::Selection * sel)266 void sp_remove_powerclip(Inkscape::Selection *sel)
267 {
268     if (!sel->isEmpty()) {
269         auto selList = sel->items();
270         for (auto i = boost::rbegin(selList); i != boost::rend(selList); ++i) {
271             SPLPEItem *lpeitem = dynamic_cast<SPLPEItem *>(*i);
272             if (lpeitem) {
273                 if (lpeitem->hasPathEffect() && lpeitem->pathEffectsEnabled()) {
274                     PathEffectList path_effect_list(*lpeitem->path_effect_list);
275                     for (auto &lperef : path_effect_list) {
276                         LivePathEffectObject *lpeobj = lperef->lpeobject;
277                         if (!lpeobj) {
278                             /** \todo Investigate the cause of this.
279                              * For example, this happens when copy pasting an object with LPE applied. Probably because
280                              * the object is pasted while the effect is not yet pasted to defs, and cannot be found.
281                              */
282                             g_warning("SPLPEItem::performPathEffect - NULL lpeobj in list!");
283                             return;
284                         }
285                         if (LPETypeConverter.get_key(lpeobj->effecttype) == "powerclip") {
286                             lpeitem->setCurrentPathEffect(lperef);
287                             lpeitem->removeCurrentPathEffect(false);
288                             break;
289                         }
290                     }
291                 }
292             }
293         }
294     }
295 }
296 
sp_inverse_powerclip(Inkscape::Selection * sel)297 void sp_inverse_powerclip(Inkscape::Selection *sel) {
298     if (!sel->isEmpty()) {
299         auto selList = sel->items();
300         for(auto i = boost::rbegin(selList); i != boost::rend(selList); ++i) {
301             SPLPEItem* lpeitem = dynamic_cast<SPLPEItem*>(*i);
302             if (lpeitem) {
303                 SPClipPath *clip_path = SP_ITEM(lpeitem)->getClipObject();
304                 if(clip_path) {
305                     std::vector<SPObject*> clip_path_list = clip_path->childList(true);
306                     for (auto iter : clip_path_list) {
307                         SPUse *use = dynamic_cast<SPUse*>(iter);
308                         if (use) {
309                             g_warning("We can`t add inverse clip on clones");
310                             return;
311                         }
312                     }
313                     Effect::createAndApply(POWERCLIP, SP_ACTIVE_DOCUMENT, lpeitem);
314                     Effect* lpe = lpeitem->getCurrentLPE();
315                     if (lpe) {
316                         lpe->getRepr()->setAttribute("inverse", "true");
317                     }
318                 }
319             }
320         }
321     }
322 }
323 
324 }; //namespace LivePathEffect
325 }; /* namespace Inkscape */
326 
327 /*
328   Local Variables:
329   mode:c++
330   c-file-style:"stroustrup"
331   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
332   indent-tabs-mode:nil
333   fill-column:99
334   End:
335 */
336 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
337