1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /** \file
3 * LPE <slice> implementation: slices a path with respect to a given line.
4 */
5 /*
6 * Authors:
7 * Maximilian Albert
8 * Johan Engelen
9 * Abhishek Sharma
10 * Jabiertxof
11 *
12 * Copyright (C) Johan Engelen 2007 <j.b.c.engelen@utwente.nl>
13 * Copyright (C) Maximilin Albert 2008 <maximilian.albert@gmail.com>
14 *
15 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
16 */
17
18 #include "live_effects/lpe-slice.h"
19 #include "2geom/affine.h"
20 #include "2geom/path-intersection.h"
21 #include "display/curve.h"
22 #include "helper/geom.h"
23 #include "path-chemistry.h"
24 #include "style.h"
25 #include "svg/path-string.h"
26 #include "svg/svg.h"
27 #include <gtkmm.h>
28 #include "path/path-boolop.h"
29
30 #include "object/sp-defs.h"
31 #include "object/sp-lpe-item.h"
32 #include "object/sp-path.h"
33 #include "object/sp-text.h"
34
35 #include "xml/sp-css-attr.h"
36
37 // this is only to flatten nonzero fillrule
38 #include "livarot/Path.h"
39 #include "livarot/Shape.h"
40
41 // TODO due to internal breakage in glibmm headers, this must be last:
42 #include <glibmm/i18n.h>
43
44 typedef FillRule FillRuleFlatten;
45
46 namespace Inkscape {
47 namespace LivePathEffect {
LPESlice(LivePathEffectObject * lpeobject)48 LPESlice::LPESlice(LivePathEffectObject *lpeobject) :
49 Effect(lpeobject),
50 allow_transforms(_("Allow Transforms"), _("Allow transforms"), "allow_transforms", &wr, this, true),
51 start_point(_("Slice line start"), _("Start point of slice line"), "start_point", &wr, this, _("Adjust start point of slice line")),
52 end_point(_("Slice line end"), _("End point of slice line"), "end_point", &wr, this, _("Adjust end point of slice line")),
53 center_point(_("Slice line mid"), _("Center point of slice line"), "center_point", &wr, this, _("Adjust center point of slice line"))
54 {
55 show_orig_path = true;
56 registerParameter(&allow_transforms);
57 registerParameter(&start_point);
58 registerParameter(&end_point);
59 registerParameter(¢er_point);
60 apply_to_clippath_and_mask = false;
61 previous_center = Geom::Point(0,0);
62 center_point.param_widget_is_visible(false);
63 reset = false;
64 center_horiz = false;
65 center_vert = false;
66 allow_transforms_prev = allow_transforms;
67 on_remove_all = false;
68 parentlpe = nullptr;
69 }
70
71 LPESlice::~LPESlice()
72 = default;
73
74
75 Gtk::Widget *
newWidget()76 LPESlice::newWidget()
77 {
78 // use manage here, because after deletion of Effect object, others might
79 // still be pointing to this widget.
80 Gtk::Box *vbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL));
81
82 vbox->set_border_width(5);
83 vbox->set_homogeneous(false);
84 vbox->set_spacing(2);
85 Gtk::Box *hbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 0));
86 Gtk::Button *center_vert_button = Gtk::manage(new Gtk::Button(Glib::ustring(_("Vertical"))));
87 center_vert_button->signal_clicked().connect(sigc::mem_fun(*this, &LPESlice::centerVert));
88 center_vert_button->set_size_request(110, 20);
89 Gtk::Button *center_horiz_button = Gtk::manage(new Gtk::Button(Glib::ustring(_("Horizontal"))));
90 center_horiz_button->signal_clicked().connect(sigc::mem_fun(*this, &LPESlice::centerHoriz));
91 center_horiz_button->set_size_request(110, 20);
92 Gtk::Button *reset_button = Gtk::manage(new Gtk::Button(Glib::ustring(_("Reset styles"))));
93 reset_button->signal_clicked().connect(sigc::mem_fun(*this, &LPESlice::resetStyles));
94 reset_button->set_size_request(110, 20);
95
96 vbox->pack_start(*hbox, true, true, 2);
97 hbox->pack_start(*reset_button, false, false, 2);
98 hbox->pack_start(*center_vert_button, false, false, 2);
99 hbox->pack_start(*center_horiz_button, false, false, 2);
100 std::vector<Parameter *>::iterator it = param_vector.begin();
101 while (it != param_vector.end()) {
102 if ((*it)->widget_is_visible) {
103 Parameter *param = *it;
104 Gtk::Widget *widg = dynamic_cast<Gtk::Widget *>(param->param_newWidget());
105 Glib::ustring *tip = param->param_getTooltip();
106 if (widg) {
107 vbox->pack_start(*widg, true, true, 2);
108 if (tip) {
109 widg->set_tooltip_text(*tip);
110 } else {
111 widg->set_tooltip_text("");
112 widg->set_has_tooltip(false);
113 }
114 }
115 }
116
117 ++it;
118 }
119 if (Gtk::Widget *widg = defaultParamSet()) {
120 vbox->pack_start(*widg, true, true, 2);
121 }
122 return dynamic_cast<Gtk::Widget *>(vbox);
123 }
124
125
126 void
centerVert()127 LPESlice::centerVert(){
128 center_vert = true;
129 refresh_widgets = true;
130 std::vector<SPLPEItem *> lpeitems = getCurrrentLPEItems();
131 if (lpeitems.size() == 1) {
132 sp_lpe_item = lpeitems[0];
133 sp_lpe_item_update_patheffect(sp_lpe_item, false, false);
134 }
135 }
136
137 void
centerHoriz()138 LPESlice::centerHoriz(){
139 center_horiz = true;
140 refresh_widgets = true;
141 std::vector<SPLPEItem *> lpeitems = getCurrrentLPEItems();
142 if (lpeitems.size() == 1) {
143 sp_lpe_item = lpeitems[0];
144 sp_lpe_item_update_patheffect(sp_lpe_item, false, false);
145 }
146 }
147
sp_has_path_data(SPItem * item,bool originald)148 bool sp_has_path_data(SPItem *item, bool originald)
149 {
150 SPGroup *group = dynamic_cast<SPGroup *>(item);
151 if (group) {
152 std::vector<SPObject *> childs = group->childList(true);
153 for (auto &child : childs) {
154 SPItem *item = dynamic_cast<SPItem *>(child);
155 if (sp_has_path_data(item, originald)) {
156 return true;
157 }
158 }
159 }
160 SPShape *shape = dynamic_cast<SPShape *>(item);
161 if (shape) {
162 SPCurve const *c = shape->curve();
163 if (c && !c->is_empty()) {
164 return true;
165 }
166 if (originald) {
167 if (shape->hasPathEffectRecursive()) {
168 SPCurve const *c = shape->curveBeforeLPE();
169 if (c && !c->is_empty()) {
170 return true;
171 }
172 }
173 }
174 }
175 return false;
176 }
177 /*
178 * Allow chage original-d to d to "reset" temporary the LPE
179 * when the slice dont pass trought item till sp_lpe_item is crossed
180 */
181 void
originalDtoD(SPShape const * shape,SPCurve * curve)182 LPESlice::originalDtoD(SPShape const *shape, SPCurve *curve)
183 {
184 SPCurve const *c = shape->curveBeforeLPE();
185 if (c && !c->is_empty()) {
186 curve->set_pathvector(c->get_pathvector());
187 }
188 }
189
190 /*
191 * Allow chage original-d to d to "reset" temporary the LPE
192 * when the slice dont pass trought item till sp_lpe_item is crossed
193 */
194 void
originalDtoD(SPItem * item)195 LPESlice::originalDtoD(SPItem *item)
196 {
197 SPGroup *group = dynamic_cast<SPGroup *>(item);
198 if (group) {
199 std::vector<SPObject *> childs = group->childList(true);
200 for (auto &child : childs) {
201 SPItem *item = dynamic_cast<SPItem *>(child);
202 originalDtoD(item);
203 }
204 return;
205 }
206 SPShape *shape = dynamic_cast<SPShape *>(item);
207 if (shape) {
208 SPCurve const *c = shape->curveBeforeLPE();
209 if (c && !c->is_empty()) {
210 shape->bbox_vis_cache_is_valid = false;
211 shape->bbox_geom_cache_is_valid = false;
212 shape->setCurveInsync(std::move(c));
213 auto str = sp_svg_write_path(c->get_pathvector());
214 shape->setAttribute("d", str);
215 }
216 }
217 }
218
reloadOriginal(SPLPEItem const * lpeitem)219 void LPESlice::reloadOriginal(SPLPEItem const* lpeitem)
220 {
221 SPLPEItem *originallpeitem = getOriginal(lpeitem);
222 if (originallpeitem) {
223 is_applied = false;
224 sp_lpe_item_update_patheffect(originallpeitem, false, true);
225 }
226 }
227
getOriginal(SPLPEItem const * lpeitem)228 SPLPEItem *LPESlice::getOriginal(SPLPEItem const* lpeitem)
229 {
230 SPLPEItem *lpeparent = nullptr;
231 if (lpeitem->getAttribute("class")) {
232 gchar **strarray = g_strsplit(lpeitem->getAttribute("class"), " ", 0);
233 gchar **iter = strarray;
234 while (*iter != nullptr) {
235 Glib::ustring classsplited = *iter;
236 size_t pos = classsplited.rfind("-slice");
237 if (pos != std::string::npos) {
238 classsplited = classsplited.replace(pos, 6, "");
239 lpeparent = dynamic_cast<SPLPEItem *>(getSPDoc()->getObjectById(classsplited));
240 if (lpeparent && lpeitem != lpeparent) {
241 g_strfreev(strarray);
242 return lpeparent;
243 }
244 }
245 iter++;
246 }
247 g_strfreev(strarray);
248 }
249 return lpeparent;
250 }
251
allowreset(gpointer data)252 gboolean allowreset(gpointer data)
253 {
254 LPESlice *slice = reinterpret_cast<LPESlice *>(data);
255 sp_lpe_item_update_patheffect(slice->sp_lpe_item, false, false);
256 return FALSE;
257 }
258
delayupdate(gpointer data)259 gboolean delayupdate(gpointer data)
260 {
261 LPESlice *slice = reinterpret_cast<LPESlice *>(data);
262 sp_lpe_item_update_patheffect(slice->sp_lpe_item, false, false);
263 return FALSE;
264 }
265
266 void
doAfterEffect(SPLPEItem const * lpeitem,SPCurve * curve)267 LPESlice::doAfterEffect (SPLPEItem const* lpeitem, SPCurve *curve)
268 {
269 bool cleanup = is_load;
270 if (is_applied) {
271 cleanup = true;
272 reloadOriginal(lpeitem);
273 }
274 LPESlice *nextslice = dynamic_cast<LPESlice *>(sp_lpe_item->getNextLPE(this));
275 if (!nextslice || !nextslice->is_visible) {
276 if (boundingbox_X.isSingular() || boundingbox_Y.isSingular()) {
277 return;
278 }
279 std::vector<SPLPEItem *> lpeitems = getCurrrentLPEItems();
280 if (lpeitems.size() != 1) {
281 return;
282 }
283 Glib::ustring theclass = lpeitem->getId();
284 theclass += "-slice";
285 //ungroup
286 if (!is_load && parentlpe && parentlpe != sp_lpe_item->parent && parentlpe != sp_lpe_item->parent->parent) {
287 parentlpe = sp_lpe_item->parent;
288 g_timeout_add(250, &delayupdate, this);
289 return;
290 } else if (!is_load && parentlpe && parentlpe != sp_lpe_item->parent) { // group
291 g_timeout_add(250, &allowreset, this);
292 cleanup = true;
293 }
294 parentlpe = sp_lpe_item->parent;
295 items.clear();
296 std::vector<std::pair<Geom::Line, size_t> > slicer = getSplitLines();
297 if (!slicer.size()) {
298 return;
299 }
300 for (auto item : getSPDoc()->getObjectsByClass(theclass)) {
301 SPItem *extraitem = dynamic_cast<SPItem *>(item);
302 if (extraitem) {
303 extraitem->setHidden("true");
304 }
305 }
306 split(sp_lpe_item, curve, slicer, 0);
307 std::vector<Glib::ustring> newitemstmp;
308 newitemstmp.assign(items.begin(), items.end());
309 items.clear();
310 bool maindata = sp_has_path_data(sp_lpe_item, false);
311 if (!maindata) {
312 Glib::ustring theclass = lpeitem->getId();
313 theclass += "-slice";
314 for (auto item : getSPDoc()->getObjectsByClass(theclass)) {
315 SPLPEItem *splpeitem = dynamic_cast<SPLPEItem *>(item);
316 splpeitem->setHidden(true);
317 sp_lpe_item_update_patheffect(splpeitem, false, false);
318 }
319 if (!curve) { // group
320 originalDtoD(sp_lpe_item);
321 } else {
322 originalDtoD(getCurrentShape(), curve);
323 }
324 return;
325 }
326 bool hidden = sp_lpe_item->isHidden();
327 for (auto item: newitemstmp) {
328 SPItem *spitem = dynamic_cast<SPItem *>(getSPDoc()->getObjectById(item.c_str()));
329 SPLPEItem *splpeitem = dynamic_cast<SPLPEItem *>(spitem);
330 if (hidden) {
331 splpeitem->setHidden("true");
332 }
333 if (spitem && sp_has_path_data(spitem, false)) {
334 items.push_back(item);
335 sp_lpe_item_update_patheffect(splpeitem, false, false);
336 }
337 }
338 for (auto item : getSPDoc()->getObjectsByClass(theclass)) {
339 SPItem *extraitem = dynamic_cast<SPItem *>(item);
340 if (extraitem) {
341 SPLPEItem *spitem = dynamic_cast<SPLPEItem *>(extraitem);
342 if (spitem && !sp_has_path_data(spitem, false)) {
343 if (cleanup) {
344 sp_lpe_item_update_patheffect(spitem, false, false);
345 spitem->deleteObject(true);
346 } else {
347 originalDtoD(spitem);
348 }
349 } else {
350 SPLPEItem *splpeitem = dynamic_cast<SPLPEItem *>(extraitem);
351 if (splpeitem && splpeitem->hasPathEffectOfType(SLICE)) {
352 sp_lpe_item_update_patheffect(splpeitem, false, false);
353 }
354 }
355 }
356 }
357 reset = false;
358 }
359 }
360
361 void
split(SPItem * item,SPCurve * curve,std::vector<std::pair<Geom::Line,size_t>> slicer,size_t splitindex)362 LPESlice::split(SPItem* item, SPCurve *curve, std::vector<std::pair<Geom::Line, size_t> > slicer, size_t splitindex) {
363 SPDocument *document = getSPDoc();
364 if (!document) {
365 return;
366 }
367 Glib::ustring elemref_id = Glib::ustring("slice-");
368 elemref_id += Glib::ustring::format(slicer[splitindex].second);
369 elemref_id += "-";
370 Glib::ustring clean_id = item->getId();
371 SPLPEItem *lpeitem = dynamic_cast<SPLPEItem *>(item);
372 if (!lpeitem) {
373 return;
374 }
375 //First check is to allow effects on "satellittes"
376 if (!lpeitem->hasPathEffectOfType(SLICE) && clean_id.find("slice-") != Glib::ustring::npos) {
377 clean_id = clean_id.replace(0,6,"");
378 elemref_id += clean_id;
379 } else {
380 elemref_id += clean_id;
381 }
382
383 items.push_back(elemref_id);
384
385 SPObject *elemref = getSPDoc()->getObjectById(elemref_id.c_str());
386 bool prevreset = reset;
387 if (!elemref) {
388 reset = true;
389 Inkscape::XML::Node *phantom = createPathBase(item);
390 phantom->setAttribute("id", elemref_id);
391 Glib::ustring classes = sp_lpe_item->getId();
392 classes += "-slice UnoptimicedTransforms";
393 phantom->setAttribute("class", classes.c_str());
394 elemref = parentlpe->appendChildRepr(phantom);
395 Inkscape::GC::release(phantom);
396 parentlpe->reorder(elemref, sp_lpe_item);
397 }
398 Inkscape::XML::Document *xml_doc = getSPDoc()->getReprDoc();
399 if (elemref && elemref->parent != parentlpe) {
400 Inkscape::XML::Node *repr = elemref->getRepr();
401 Inkscape::XML::Node *copy = repr->duplicate(xml_doc);
402 if (copy) {
403 parentlpe->addChild(copy, sp_lpe_item->getRepr());
404 // Retrieve the SPItem of the resulting repr.
405 SPObject *sucessor = document->getObjectByRepr(copy);
406 if (sucessor) {
407 sp_object_ref(elemref);
408 Inkscape::GC::anchor(repr);
409 elemref->deleteObject(false);
410 sucessor->setAttribute("id", elemref_id);
411 Inkscape::GC::release(repr);
412 elemref->setSuccessor(sucessor);
413 sp_object_unref(elemref);
414 elemref = dynamic_cast<SPItem *>(sucessor);
415 g_assert(item != nullptr);
416 }
417 }
418 }
419 SPItem *other = dynamic_cast<SPItem *>(elemref);
420 if (other) {
421 other->setHidden(false);
422 size_t nsplits = slicer.size();
423 if (nsplits) {
424 cloneD(item, other, false);
425 reset = prevreset;
426 splititem(item, curve, slicer[splitindex], true);
427 splititem(other, nullptr, slicer[splitindex], false);
428 splitindex++;
429 if (nsplits > splitindex) {
430 SPLPEItem *splpeother = dynamic_cast<SPLPEItem *>(other);
431 SPLPEItem *splpeitem = dynamic_cast<SPLPEItem *>(item);
432 if (item == sp_lpe_item || !splpeitem->hasPathEffectOfType(SLICE)) {
433 split(item, curve, slicer, splitindex);
434 if (other == sp_lpe_item || !splpeother->hasPathEffectOfType(SLICE)) {
435 split(other, nullptr, slicer, splitindex);
436 }
437 }
438 }
439 }
440 }
441 }
442
443 std::vector<std::pair<Geom::Line, size_t> >
getSplitLines()444 LPESlice::getSplitLines() {
445 std::vector<std::pair<Geom::Line, size_t> > splitlines;
446 LPESlice *prevslice = dynamic_cast<LPESlice *>(sp_lpe_item->getPrevLPE(this));
447 if (prevslice) {
448 splitlines = prevslice->getSplitLines();
449 }
450 if (isVisible()) {
451 Geom::Line line_separation((Geom::Point)start_point, (Geom::Point)end_point);
452 size_t index = sp_lpe_item->getLPEIndex(this);
453 std::pair<Geom::Line, size_t> slice = std::make_pair(line_separation, index);
454 splitlines.push_back(slice);
455 }
456 return splitlines;
457 }
458
459 Inkscape::XML::Node *
createPathBase(SPObject * elemref)460 LPESlice::createPathBase(SPObject *elemref) {
461 SPDocument *document = getSPDoc();
462 if (!document) {
463 return nullptr;
464 }
465 Inkscape::XML::Document *xml_doc = getSPDoc()->getReprDoc();
466 Inkscape::XML::Node *prev = elemref->getRepr();
467 SPGroup *group = dynamic_cast<SPGroup *>(elemref);
468 if (group) {
469 Inkscape::XML::Node *container = xml_doc->createElement("svg:g");
470 container->setAttribute("transform", prev->attribute("transform"));
471 container->setAttribute("mask", prev->attribute("mask"));
472 container->setAttribute("clip-path", prev->attribute("clip-path"));
473 std::vector<SPItem*> const item_list = sp_item_group_item_list(group);
474 Inkscape::XML::Node *previous = nullptr;
475 for (auto sub_item : item_list) {
476 Inkscape::XML::Node *resultnode = createPathBase(sub_item);
477 container->addChild(resultnode, previous);
478 previous = resultnode;
479 }
480 return container;
481 }
482 Inkscape::XML::Node *resultnode = xml_doc->createElement("svg:path");
483 resultnode->setAttribute("transform", prev->attribute("transform"));
484 resultnode->setAttribute("mask", prev->attribute("mask"));
485 resultnode->setAttribute("clip-path", prev->attribute("clip-path"));
486 return resultnode;
487 }
488
489 void
cloneD(SPObject * orig,SPObject * dest,bool is_original)490 LPESlice::cloneD(SPObject *orig, SPObject *dest, bool is_original)
491 {
492 if (!is_original && !g_strcmp0(sp_lpe_item->getId(), orig->getId())) {
493 is_original = true;
494 }
495 SPDocument *document = getSPDoc();
496 if (!document) {
497 return;
498 }
499 SPItem *originalitem = dynamic_cast<SPItem *>(orig);
500 if ( SP_IS_GROUP(orig) && SP_IS_GROUP(dest) && SP_GROUP(orig)->getItemCount() == SP_GROUP(dest)->getItemCount() ) {
501 if (reset) {
502 cloneStyle(orig, dest);
503 }
504 if (!allow_transforms) {
505 auto str = sp_svg_transform_write(originalitem->transform);
506 dest->setAttributeOrRemoveIfEmpty("transform", str);
507 }
508 std::vector< SPObject * > childs = orig->childList(true);
509 size_t index = 0;
510 for (auto &child : childs) {
511 SPObject *dest_child = dest->nthChild(index);
512 cloneD(child, dest_child, is_original);
513 index++;
514 }
515 return;
516 }
517
518 SPShape * shape = SP_SHAPE(orig);
519 SPPath * path = SP_PATH(dest);
520 SPLPEItem *splpeitem = dynamic_cast<SPLPEItem *>(path);
521 if (path && shape && splpeitem) {
522 SPCurve const *c;
523 if (!is_original && shape->hasPathEffectRecursive()) {
524 c = shape->curve();
525 } else {
526 c = shape->curve();
527 }
528 if (c && !c->is_empty()) {
529 auto str = sp_svg_write_path(c->get_pathvector());
530 if (path->hasPathEffectRecursive()) {
531 sp_lpe_item_enable_path_effects(path, false);
532 dest->setAttribute("inkscape:original-d", str);
533 sp_lpe_item_enable_path_effects(path, true);
534 dest->setAttribute("d", str);
535 } else {
536 dest->setAttribute("d", str);
537 }
538 if (!allow_transforms) {
539 auto str = sp_svg_transform_write(originalitem->transform);
540 dest->setAttributeOrRemoveIfEmpty("transform", str);
541 }
542 if (reset) {
543 cloneStyle(orig, dest);
544 }
545 }
546 }
547 }
548
549 static void
sp_flatten(Geom::PathVector & pathvector,FillRuleFlatten fillkind)550 sp_flatten(Geom::PathVector &pathvector, FillRuleFlatten fillkind)
551 {
552 Path *orig = new Path;
553 orig->LoadPathVector(pathvector);
554 Shape *theShape = new Shape;
555 Shape *theRes = new Shape;
556 orig->ConvertWithBackData (1.0);
557 orig->Fill (theShape, 0);
558 theRes->ConvertToShape (theShape, FillRule(fillkind));
559 Path *originaux[1];
560 originaux[0] = orig;
561 Path *res = new Path;
562 theRes->ConvertToForme (res, 1, originaux, true);
563
564 delete theShape;
565 delete theRes;
566 char *res_d = res->svg_dump_path ();
567 delete res;
568 delete orig;
569 pathvector = sp_svg_read_pathv(res_d);
570 }
571
GetFillTyp(SPItem * item)572 static fill_typ GetFillTyp(SPItem *item)
573 {
574 SPCSSAttr *css = sp_repr_css_attr(item->getRepr(), "style");
575 gchar const *val = sp_repr_css_property(css, "fill-rule", nullptr);
576 if (val && strcmp(val, "nonzero") == 0) {
577 return fill_nonZero;
578 } else if (val && strcmp(val, "evenodd") == 0) {
579 return fill_oddEven;
580 } else {
581 return fill_nonZero;
582 }
583 }
584
585 void
splititem(SPItem * item,SPCurve * curve,std::pair<Geom::Line,size_t> slicer,bool toggle,bool is_original)586 LPESlice::splititem(SPItem* item, SPCurve * curve, std::pair<Geom::Line, size_t> slicer, bool toggle, bool is_original)
587 {
588 if (!is_original && !g_strcmp0(sp_lpe_item->getId(), item->getId())) {
589 is_original = true;
590 }
591 Geom::Line line_separation = slicer.first;
592 // check top level split/sp_lpe_item item
593 SPObject *top = sp_lpe_item->parent;
594 SPObject *other = item;
595 while (other && other->parent) {
596 if (other->parent != top) {
597 other = other->parent;
598 } else {
599 break;
600 }
601 }
602 SPItem *topitem = dynamic_cast<SPItem *>(other);
603 if (topitem && topitem != item) {
604 Geom::Affine ptransform = item->getRelativeTransform(topitem);
605 ptransform *= item->document->doc2dt();
606 line_separation *= ptransform.inverse();
607 }
608 Geom::Point s = line_separation.initialPoint();
609 Geom::Point e = line_separation.finalPoint();
610 Geom::Point center = Geom::middle_point(s, e);
611 SPGroup *group = dynamic_cast<SPGroup *>(item);
612 if (group) {
613 std::vector<SPObject *> childs = group->childList(true);
614 for (auto &child : childs) {
615 SPItem *dest_child = dynamic_cast<SPItem *>(child);
616 // groups not need update curve
617 splititem(dest_child, nullptr, slicer, toggle, is_original);
618 }
619 if (!is_original && group->hasPathEffectRecursive()) {
620 sp_lpe_item_update_patheffect(group, false, false);
621 }
622 return;
623 }
624 SPShape *shape = dynamic_cast<SPShape *>(item);
625 if (shape) {
626 SPCurve const *c;
627 c = shape->curve();
628 if (c) {
629 Geom::PathVector original_pathv = pathv_to_linear_and_cubic_beziers(c->get_pathvector());
630 sp_flatten(original_pathv, GetFillTyp(shape));
631 Geom::PathVector path_out;
632 for (const auto & path_it : original_pathv) {
633 if (path_it.empty()) {
634 continue;
635 }
636 Geom::PathVector tmp_pathvector;
637 double time_start = 0.0;
638 int position = 0;
639 bool end_open = false;
640 if (path_it.closed()) {
641 const Geom::Curve &closingline = path_it.back_closed();
642 if (!are_near(closingline.initialPoint(), closingline.finalPoint())) {
643 end_open = true;
644 }
645 }
646 Geom::Path original = path_it;
647 if (end_open && path_it.closed()) {
648 original.close(false);
649 original.appendNew<Geom::LineSegment>( original.initialPoint() );
650 original.close(true);
651 }
652 double dir = line_separation.angle();
653 Geom::Ray ray = line_separation.ray(0);
654 double diagonal = Geom::distance(Geom::Point(boundingbox_X.min(),boundingbox_Y.min()),Geom::Point(boundingbox_X.max(),boundingbox_Y.max()));
655 Geom::Rect bbox(Geom::Point(boundingbox_X.min(),boundingbox_Y.min()),Geom::Point(boundingbox_X.max(),boundingbox_Y.max()));
656 double size_divider = Geom::distance(center, bbox) + diagonal;
657 s = Geom::Point::polar(dir,size_divider) + center;
658 e = Geom::Point::polar(dir + Geom::rad_from_deg(180),size_divider) + center;
659 Geom::Path divider = Geom::Path(s);
660 divider.appendNew<Geom::LineSegment>(e);
661 std::vector<double> crossed;
662 if (Geom::are_near(s,e)) {
663 continue;
664 }
665 Geom::Crossings cs = crossings(original, divider);
666 for(auto & c : cs) {
667 crossed.push_back(c.ta);
668 }
669 double angle = Geom::deg_from_rad(ray.angle());
670 bool toggleside = !(angle > 0 && angle < 180);
671 std::sort(crossed.begin(), crossed.end());
672 for (double time_end : crossed) {
673 if (time_start != time_end && time_end - time_start > Geom::EPSILON) {
674 Geom::Path portion = original.portion(time_start, time_end);
675 if (!portion.empty()) {
676 Geom::Point middle = portion.pointAt((double)portion.size()/2.0);
677 position = Geom::sgn(Geom::cross(e - s, middle - s));
678 if (toggleside) {
679 position *= -1;
680 }
681 if (toggle) {
682 position *= -1;
683 }
684 if (position == 1) {
685 tmp_pathvector.push_back(portion);
686 }
687 portion.clear();
688 }
689 }
690 time_start = time_end;
691 }
692 position = Geom::sgn(Geom::cross(e - s, original.finalPoint() - s));
693 if (toggleside) {
694 position *= -1;
695 }
696 if (toggle) {
697 position *= -1;
698 }
699 if (cs.size()!=0 && (position == 1)) {
700 if (time_start != original.size() && original.size() - time_start > Geom::EPSILON) {
701 Geom::Path portion = original.portion(time_start, original.size());
702 if (!portion.empty()) {
703 if (!original.closed()) {
704 tmp_pathvector.push_back(portion);
705 } else {
706 if (cs.size() > 1 && tmp_pathvector.size() > 0 && tmp_pathvector[0].size() > 0 ) {
707 tmp_pathvector[0] = tmp_pathvector[0].reversed();
708 portion = portion.reversed();
709 portion.setInitial(tmp_pathvector[0].finalPoint());
710 tmp_pathvector[0].append(portion);
711 tmp_pathvector[0] = tmp_pathvector[0].reversed();
712 } else {
713 tmp_pathvector.push_back(portion);
714 }
715 }
716 portion.clear();
717 }
718 }
719 }
720 if (cs.size() > 0 && original.closed()) {
721 for (auto &path : tmp_pathvector) {
722 if (!path.closed()) {
723 path.close();
724 }
725 }
726 }
727 if (cs.size() == 0 && position == 1) {
728 tmp_pathvector.push_back(original);
729 }
730 path_out.insert(path_out.end(), tmp_pathvector.begin(), tmp_pathvector.end());
731 tmp_pathvector.clear();
732 }
733 if (curve && is_original) {
734 curve->set_pathvector(path_out);
735 }
736 auto cpro = SPCurve::copy(shape->curve());
737 if (cpro) {
738 shape->bbox_vis_cache_is_valid = false;
739 shape->bbox_geom_cache_is_valid = false;
740 cpro->set_pathvector(path_out);
741 shape->setCurveInsync(std::move(cpro));
742 auto str = sp_svg_write_path(path_out);
743 if (!is_original && shape->hasPathEffectRecursive()) {
744 sp_lpe_item_enable_path_effects(shape, false);
745 shape->setAttribute("inkscape:original-d", str);
746 sp_lpe_item_enable_path_effects(shape, true);
747 } else {
748 shape->setAttribute("d", str);
749 }
750 }
751 }
752 }
753 }
754
755 void
doBeforeEffect(SPLPEItem const * lpeitem)756 LPESlice::doBeforeEffect (SPLPEItem const* lpeitem)
757 {
758 SPDocument *document = getSPDoc();
759 if (!document) {
760 return;
761 }
762 using namespace Geom;
763 original_bbox(lpeitem, false, true);
764 Point point_a(boundingbox_X.max(), boundingbox_Y.min());
765 Point point_b(boundingbox_X.max(), boundingbox_Y.max());
766 Point point_c(boundingbox_X.middle(), boundingbox_Y.middle());
767 if (center_vert) {
768 double dista = std::abs(end_point[Geom::Y] - boundingbox_Y.min());
769 double distb = std::abs(start_point[Geom::Y] - boundingbox_Y.min());
770 previous_center = Geom::Point(Geom::infinity(), g_random_double_range(0, 1000));
771 end_point.param_setValue(
772 Geom::Point(center_point[Geom::X], dista <= distb ? boundingbox_Y.min() : boundingbox_Y.max()), true);
773 start_point.param_setValue(
774 Geom::Point(center_point[Geom::X], dista > distb ? boundingbox_Y.min() : boundingbox_Y.max()), true);
775 //force update
776 center_vert = false;
777 } else if (center_horiz) {
778 double dista = std::abs(end_point[Geom::X] - boundingbox_X.min());
779 double distb = std::abs(start_point[Geom::X] - boundingbox_X.min());
780 previous_center = Geom::Point(Geom::infinity(), g_random_double_range(0, 1000));
781 end_point.param_setValue(
782 Geom::Point(dista <= distb ? boundingbox_X.min() : boundingbox_X.max(), center_point[Geom::Y]), true);
783 start_point.param_setValue(
784 Geom::Point(dista > distb ? boundingbox_X.min() : boundingbox_X.max(), center_point[Geom::Y]), true);
785 //force update
786 center_horiz = false;
787 } else {
788 if ((Geom::Point)start_point == (Geom::Point)end_point) {
789 start_point.param_setValue(point_a);
790 end_point.param_setValue(point_b);
791 previous_center = Geom::middle_point((Geom::Point)start_point, (Geom::Point)end_point);
792 center_point.param_setValue(previous_center);
793 return;
794 }
795 if (are_near(previous_center, (Geom::Point)center_point, 0.01)) {
796 center_point.param_setValue(Geom::middle_point((Geom::Point)start_point, (Geom::Point)end_point));
797 } else {
798 Geom::Point trans = center_point - Geom::middle_point((Geom::Point)start_point, (Geom::Point)end_point);
799 start_point.param_setValue(start_point * trans);
800 end_point.param_setValue(end_point * trans);
801 }
802 }
803 if (allow_transforms_prev != allow_transforms) {
804 LPESlice *nextslice = dynamic_cast<LPESlice *>(sp_lpe_item->getNextLPE(this));
805 while (nextslice) {
806 if (nextslice->allow_transforms != allow_transforms) {
807 nextslice->allow_transforms_prev = allow_transforms;
808 nextslice->allow_transforms.param_setValue(allow_transforms);
809 }
810 nextslice = dynamic_cast<LPESlice *>(sp_lpe_item->getNextLPE(nextslice));
811 }
812 LPESlice *prevslice = dynamic_cast<LPESlice *>(sp_lpe_item->getPrevLPE(this));
813 while (prevslice) {
814 if (prevslice->allow_transforms != allow_transforms) {
815 prevslice->allow_transforms_prev = allow_transforms;
816 prevslice->allow_transforms.param_setValue(allow_transforms);
817 }
818 prevslice = dynamic_cast<LPESlice *>(sp_lpe_item->getNextLPE(prevslice));
819 }
820 }
821 allow_transforms_prev = allow_transforms;
822 }
823
cloneStyle(SPObject * orig,SPObject * dest)824 void LPESlice::cloneStyle(SPObject *orig, SPObject *dest)
825 {
826 for (auto iter : orig->style->properties()) {
827 if (iter->style_src != SPStyleSrc::UNSET) {
828 auto key = iter->id();
829 if (key != SPAttr::FONT && key != SPAttr::D && key != SPAttr::MARKER) {
830 const gchar *attr = orig->getAttribute(iter->name().c_str());
831 if (attr) {
832 dest->setAttribute(iter->name(), attr);
833 }
834 }
835 }
836 }
837 dest->setAttribute("style", orig->getAttribute("style"));
838 }
839
840 void
resetStyles()841 LPESlice::resetStyles(){
842 std::vector<SPLPEItem *> lpeitems = getCurrrentLPEItems();
843 if (lpeitems.size() == 1) {
844 sp_lpe_item = lpeitems[0];
845 LPESlice *nextslice = dynamic_cast<LPESlice *>(sp_lpe_item->getNextLPE(this));
846 while (nextslice) {
847 nextslice->reset = true;
848 nextslice = dynamic_cast<LPESlice *>(sp_lpe_item->getNextLPE(nextslice));
849 }
850 reset = true;
851 sp_lpe_item_update_patheffect(sp_lpe_item, false, false);
852 }
853 }
854
855 //TODO: Migrate the tree next function to effect.cpp/h to avoid duplication
856 void
doOnVisibilityToggled(SPLPEItem const *)857 LPESlice::doOnVisibilityToggled(SPLPEItem const* /*lpeitem*/)
858 {
859 processObjects(LPE_VISIBILITY);
860 }
861
862
863 void
doOnRemove(SPLPEItem const * lpeitem)864 LPESlice::doOnRemove(SPLPEItem const* lpeitem)
865 {
866 items.clear();
867 std::vector<SPLPEItem *> lpeitems = getCurrrentLPEItems();
868 if (lpeitems.size() == 1) {
869 sp_lpe_item = lpeitems[0];
870 if (!sp_lpe_item->path_effects_enabled) {
871 return;
872 }
873 Glib::ustring theclass = sp_lpe_item->getId();
874 theclass += "-slice";
875 for (auto item : getSPDoc()->getObjectsByClass(theclass)) {
876 items.emplace_back(item->getId());
877 }
878 if (keep_paths) {
879 processObjects(LPE_TO_OBJECTS);
880 items.clear();
881 return;
882 }
883 if (sp_lpe_item->countLPEOfType(SLICE) == 1 || on_remove_all) {
884 processObjects(LPE_ERASE);
885 } else {
886 sp_lpe_item_update_patheffect(sp_lpe_item, false, false);
887 }
888 }
889 }
890
891 void
doOnApply(SPLPEItem const * lpeitem)892 LPESlice::doOnApply (SPLPEItem const* lpeitem)
893 {
894 using namespace Geom;
895 original_bbox(lpeitem, false, true);
896 LPESlice *prevslice = dynamic_cast<LPESlice *>(sp_lpe_item->getPrevLPE(this));
897 if (prevslice) {
898 allow_transforms_prev = prevslice->allow_transforms;
899 allow_transforms.param_setValue(prevslice->allow_transforms);
900 }
901 Point point_a(boundingbox_X.middle(), boundingbox_Y.min());
902 Point point_b(boundingbox_X.middle(), boundingbox_Y.max());
903 Point point_c(boundingbox_X.middle(), boundingbox_Y.middle());
904 start_point.param_setValue(point_a, true);
905 start_point.param_update_default(point_a);
906 end_point.param_setValue(point_b, true);
907 end_point.param_update_default(point_b);
908 center_point.param_setValue(point_c, true);
909 end_point.param_update_default(point_c);
910 previous_center = center_point;
911 }
912
913
914 Geom::PathVector
doEffect_path(Geom::PathVector const & path_in)915 LPESlice::doEffect_path (Geom::PathVector const & path_in)
916 {
917 return path_in;
918 }
919
920 void
addCanvasIndicators(SPLPEItem const *,std::vector<Geom::PathVector> & hp_vec)921 LPESlice::addCanvasIndicators(SPLPEItem const */*lpeitem*/, std::vector<Geom::PathVector> &hp_vec)
922 {
923 using namespace Geom;
924 hp_vec.clear();
925 Geom::Path path;
926 Geom::Point s = start_point;
927 Geom::Point e = end_point;
928 path.start( s );
929 path.appendNew<Geom::LineSegment>( e );
930 Geom::PathVector helper;
931 helper.push_back(path);
932 hp_vec.push_back(helper);
933 }
934
935 } //namespace LivePathEffect
936 } /* namespace Inkscape */
937
938 /*
939 Local Variables:
940 mode:c++
941 c-file-style:"stroustrup"
942 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
943 indent-tabs-mode:nil
944 fill-column:99
945 End:
946 */
947 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
948