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