1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /** \file
3  * LPE <copy_rotate> implementation
4  */
5 /*
6  * Authors:
7  *   Maximilian Albert <maximilian.albert@gmail.com>
8  *   Johan Engelen <j.b.c.engelen@alumnus.utwente.nl>
9  *   Jabiertxo Arraiza Cenoz <jabier.arraiza@marker.es>
10  * Copyright (C) Authors 2007-2012
11  *
12  * Released under GNU GPL v2+, read the file 'COPYING' for more information.
13  */
14 
15 #include "live_effects/lpe-copy_rotate.h"
16 #include "display/curve.h"
17 #include "helper/geom.h"
18 #include "live_effects/lpeobject.h"
19 #include "object/sp-text.h"
20 #include "path/path-boolop.h"
21 #include "path-chemistry.h"
22 #include "style.h"
23 #include "svg/path-string.h"
24 #include "svg/svg.h"
25 #include "xml/sp-css-attr.h"
26 #include <2geom/intersection-graph.h>
27 #include <2geom/path-intersection.h>
28 #include <2geom/sbasis-to-bezier.h>
29 #include <gdk/gdk.h>
30 #include <gtkmm.h>
31 
32 #include "object/sp-path.h"
33 #include "object/sp-shape.h"
34 
35 // TODO due to internal breakage in glibmm headers, this must be last:
36 #include <glibmm/i18n.h>
37 
38 namespace Inkscape {
39 namespace LivePathEffect {
40 
41 static const Util::EnumData<RotateMethod> RotateMethodData[RM_END] = {
42     { RM_NORMAL, N_("Normal"), "normal" },
43     { RM_KALEIDOSCOPE, N_("Kaleidoscope"), "kaleidoskope" },
44     { RM_FUSE, N_("Fuse paths"), "fuse_paths" }
45 };
46 static const Util::EnumDataConverter<RotateMethod>
47 RMConverter(RotateMethodData, RM_END);
48 
49 bool
pointInTriangle(Geom::Point const & p,Geom::Point const & p1,Geom::Point const & p2,Geom::Point const & p3)50 pointInTriangle(Geom::Point const &p, Geom::Point const &p1, Geom::Point const &p2, Geom::Point const &p3)
51 {
52     //http://totologic.blogspot.com.es/2014/01/accurate-point-in-triangle-test.html
53     using Geom::X;
54     using Geom::Y;
55     double denominator = (p1[X]*(p2[Y] - p3[Y]) + p1[Y]*(p3[X] - p2[X]) + p2[X]*p3[Y] - p2[Y]*p3[X]);
56     double t1 = (p[X]*(p3[Y] - p1[Y]) + p[Y]*(p1[X] - p3[X]) - p1[X]*p3[Y] + p1[Y]*p3[X]) / denominator;
57     double t2 = (p[X]*(p2[Y] - p1[Y]) + p[Y]*(p1[X] - p2[X]) - p1[X]*p2[Y] + p1[Y]*p2[X]) / -denominator;
58     double s = t1 + t2;
59 
60     return 0 <= t1 && t1 <= 1 && 0 <= t2 && t2 <= 1 && s <= 1;
61 }
62 
LPECopyRotate(LivePathEffectObject * lpeobject)63 LPECopyRotate::LPECopyRotate(LivePathEffectObject *lpeobject) :
64     Effect(lpeobject),
65     method(_("Method:"), _("Rotate methods"), "method", RMConverter, &wr, this, RM_NORMAL),
66     origin(_("Origin"), _("Adjust origin of the rotation"), "origin", &wr, this,  _("Adjust origin of the rotation")),
67     starting_point(_("Start point"), _("Starting point to define start angle"), "starting_point", &wr, this, _("Adjust starting point to define start angle")),
68     starting_angle(_("Starting angle"), _("Angle of the first copy"), "starting_angle", &wr, this, 0.0),
69     rotation_angle(_("Rotation angle"), _("Angle between two successive copies"), "rotation_angle", &wr, this, 60.0),
70     num_copies(_("Number of copies"), _("Number of copies of the original path"), "num_copies", &wr, this, 6),
71     gap(_("Gap"), _("Gap space between copies, use small negative gaps to fix some joins"), "gap", &wr, this, -0.01),
72     copies_to_360(_("Distribute evenly"), _("Angle between copies is 360°/number of copies (ignores rotation angle setting)"), "copies_to_360", &wr, this, true),
73     mirror_copies(_("Mirror copies"), _("Mirror between copies"), "mirror_copies", &wr, this, false),
74     split_items(_("Split elements"), _("Split elements, so each can have its own style"), "split_items", &wr, this, false),
75     dist_angle_handle(100.0)
76 {
77     show_orig_path = true;
78     _provides_knotholder_entities = true;
79     //0.92 compatibility
80     if (this->getRepr()->attribute("fuse_paths") && strcmp(this->getRepr()->attribute("fuse_paths"), "true") == 0){
81         this->getRepr()->removeAttribute("fuse_paths");
82         this->getRepr()->setAttribute("method", "kaleidoskope");
83         this->getRepr()->setAttribute("mirror_copies", "true");
84     };
85     // register all your parameters here, so Inkscape knows which parameters this effect has:
86     registerParameter(&method);
87     registerParameter(&num_copies);
88     registerParameter(&starting_angle);
89     registerParameter(&starting_point);
90     registerParameter(&rotation_angle);
91     registerParameter(&gap);
92     registerParameter(&origin);
93     registerParameter(&copies_to_360);
94     registerParameter(&mirror_copies);
95     registerParameter(&split_items);
96 
97     gap.param_set_range(std::numeric_limits<double>::lowest(), std::numeric_limits<double>::max());
98     gap.param_set_increments(0.01, 0.01);
99     gap.param_set_digits(5);
100     num_copies.param_set_range(1, std::numeric_limits<gint>::max());
101     num_copies.param_make_integer();
102     apply_to_clippath_and_mask = true;
103     previous_num_copies = num_copies;
104     previous_origin = Geom::Point(0,0);
105     previous_start_point = Geom::Point(0,0);
106     starting_point.param_widget_is_visible(false);
107     reset = false;
108 }
109 
110 LPECopyRotate::~LPECopyRotate()
111 = default;
112 
113 void
doAfterEffect(SPLPEItem const * lpeitem,SPCurve * curve)114 LPECopyRotate::doAfterEffect (SPLPEItem const* lpeitem, SPCurve *curve)
115 {
116     if (split_items) {
117         SPDocument *document = getSPDoc();
118         if (!document) {
119             return;
120         }
121         items.clear();
122         container = dynamic_cast<SPObject *>(sp_lpe_item->parent);
123         if (previous_num_copies != num_copies) {
124             gint numcopies_gap = previous_num_copies - num_copies;
125             if (numcopies_gap > 0 && num_copies != 0) {
126                 guint counter = num_copies - 1;
127                 while (numcopies_gap > 0) {
128                     Glib::ustring id = Glib::ustring("rotated-");
129                     id += std::to_string(counter);
130                     id += "-";
131                     id += this->lpeobj->getId();
132                     if (id.empty()) {
133                         return;
134                     }
135                     SPObject *elemref = document->getObjectById(id.c_str());
136                     if (elemref) {
137                         SP_ITEM(elemref)->setHidden(true);
138                     }
139                     counter++;
140                     numcopies_gap--;
141                 }
142             }
143             previous_num_copies = num_copies;
144         }
145         SPObject *elemref = nullptr;
146         guint counter = 0;
147         Glib::ustring id = "rotated-0-";
148         id += this->lpeobj->getId();
149         while((elemref = document->getObjectById(id.c_str()))) {
150             id = Glib::ustring("rotated-");
151             id += std::to_string(counter);
152             id += "-";
153             id += this->lpeobj->getId();
154             if (SP_ITEM(elemref)->isHidden()) {
155                 items.push_back(id);
156             }
157             counter++;
158         }
159         Geom::Affine m = Geom::Translate(-origin) * Geom::Rotate(-(Geom::rad_from_deg(starting_angle)));
160         for (size_t i = 1; i < num_copies; ++i) {
161             Geom::Affine r = Geom::identity();
162             if(mirror_copies && i%2 != 0) {
163                 r *= Geom::Rotate(Geom::Angle(half_dir)).inverse();
164                 r *= Geom::Scale(1, -1);
165                 r *= Geom::Rotate(Geom::Angle(half_dir));
166             }
167 
168             Geom::Rotate rot(-(Geom::rad_from_deg(rotation_angle * i)));
169             Geom::Affine t = m * r * rot * Geom::Rotate(Geom::rad_from_deg(starting_angle)) * Geom::Translate(origin);
170             if (method != RM_NORMAL) {
171                 if(mirror_copies && i%2 != 0) {
172                     t = m *  r * rot * Geom::Rotate(-Geom::rad_from_deg(starting_angle)) * Geom::Translate(origin);
173                 }
174             } else {
175                 if(mirror_copies && i%2 != 0) {
176                     t = m * Geom::Rotate(Geom::rad_from_deg(-rotation_angle)) * r * rot * Geom::Rotate(-Geom::rad_from_deg(starting_angle)) * Geom::Translate(origin);
177                 }
178             }
179             t *= sp_lpe_item->transform;
180             toItem(t, i-1, reset);
181         }
182         reset = false;
183     } else {
184         processObjects(LPE_ERASE);
185         items.clear();
186     }
187 }
188 
cloneStyle(SPObject * orig,SPObject * dest)189 void LPECopyRotate::cloneStyle(SPObject *orig, SPObject *dest)
190 {
191     dest->getRepr()->setAttribute("style", orig->getRepr()->attribute("style"));
192     for (auto iter : orig->style->properties()) {
193         if (iter->style_src != SPStyleSrc::UNSET) {
194             auto key = iter->id();
195             if (key != SPAttr::FONT && key != SPAttr::D && key != SPAttr::MARKER) {
196                 const gchar *attr = orig->getRepr()->attribute(iter->name().c_str());
197                 if (attr) {
198                     dest->getRepr()->setAttribute(iter->name(), attr);
199                 }
200             }
201         }
202     }
203 }
204 
205 void
cloneD(SPObject * orig,SPObject * dest,Geom::Affine transform,bool reset)206 LPECopyRotate::cloneD(SPObject *orig, SPObject *dest, Geom::Affine transform, bool reset)
207 {
208     SPDocument *document = getSPDoc();
209     if (!document) {
210         return;
211     }
212     if ( SP_IS_GROUP(orig) && SP_IS_GROUP(dest) && SP_GROUP(orig)->getItemCount() == SP_GROUP(dest)->getItemCount() ) {
213         if (reset) {
214             cloneStyle(orig, dest);
215         }
216         std::vector< SPObject * > childs = orig->childList(true);
217         size_t index = 0;
218         for (auto & child : childs) {
219             SPObject *dest_child = dest->nthChild(index);
220             cloneD(child, dest_child, transform, reset);
221             index++;
222         }
223         return;
224     }
225 
226     if ( SP_IS_TEXT(orig) && SP_IS_TEXT(dest) && SP_TEXT(orig)->children.size() == SP_TEXT(dest)->children.size()) {
227         if (reset) {
228             cloneStyle(orig, dest);
229         }
230         size_t index = 0;
231         for (auto & child : SP_TEXT(orig)->children) {
232             SPObject *dest_child = dest->nthChild(index);
233             cloneD(&child, dest_child, transform, reset);
234             index++;
235         }
236     }
237 
238     SPShape * shape =  SP_SHAPE(orig);
239     SPPath * path =  SP_PATH(dest);
240     if (shape) {
241         SPCurve const *c = shape->curve();
242         if (c) {
243             auto str = sp_svg_write_path(c->get_pathvector());
244             if (shape && !path) {
245                 const char * id = dest->getRepr()->attribute("id");
246                 const char * style = dest->getRepr()->attribute("style");
247                 Inkscape::XML::Document *xml_doc = dest->document->getReprDoc();
248                 Inkscape::XML::Node *dest_node = xml_doc->createElement("svg:path");;
249                 dest_node->setAttribute("id", id);
250                 dest_node->setAttribute("inkscape:connector-curvature", "0");
251                 dest_node->setAttribute("style", style);
252                 dest->updateRepr(xml_doc, dest_node, SP_OBJECT_WRITE_ALL);
253                 path =  SP_PATH(dest);
254             }
255             path->getRepr()->setAttribute("d", str);
256         } else {
257             path->getRepr()->removeAttribute("d");
258         }
259 
260     }
261     if (reset) {
262         cloneStyle(orig, dest);
263     }
264 }
265 
266 Inkscape::XML::Node *
createPathBase(SPObject * elemref)267 LPECopyRotate::createPathBase(SPObject *elemref) {
268     SPDocument *document = getSPDoc();
269     if (!document) {
270         return nullptr;
271     }
272     Inkscape::XML::Document *xml_doc = document->getReprDoc();
273     Inkscape::XML::Node *prev = elemref->getRepr();
274     SPGroup *group = dynamic_cast<SPGroup *>(elemref);
275     if (group) {
276         Inkscape::XML::Node *container = xml_doc->createElement("svg:g");
277         container->setAttribute("transform", prev->attribute("transform"));
278         std::vector<SPItem*> const item_list = sp_item_group_item_list(group);
279         Inkscape::XML::Node *previous = nullptr;
280         for (auto sub_item : item_list) {
281             Inkscape::XML::Node *resultnode = createPathBase(sub_item);
282             container->addChild(resultnode, previous);
283             previous = resultnode;
284         }
285         return container;
286     }
287     Inkscape::XML::Node *resultnode = xml_doc->createElement("svg:path");
288     resultnode->setAttribute("transform", prev->attribute("transform"));
289     resultnode->setAttribute("style", prev->attribute("style"));
290     return resultnode;
291 }
292 
293 void
toItem(Geom::Affine transform,size_t i,bool reset)294 LPECopyRotate::toItem(Geom::Affine transform, size_t i, bool reset)
295 {
296     SPDocument *document = getSPDoc();
297     if (!document) {
298         return;
299     }
300     Inkscape::XML::Document *xml_doc = document->getReprDoc();
301     Glib::ustring elemref_id = Glib::ustring("rotated-");
302     elemref_id += std::to_string(i);
303     elemref_id += "-";
304     elemref_id += this->lpeobj->getId();
305     items.push_back(elemref_id);
306     SPObject *elemref = document->getObjectById(elemref_id.c_str());
307     Inkscape::XML::Node *phantom = nullptr;
308     if (elemref) {
309         phantom = elemref->getRepr();
310     } else {
311         phantom = createPathBase(sp_lpe_item);
312         phantom->setAttribute("id", elemref_id);
313         reset = true;
314         elemref = container->appendChildRepr(phantom);
315         elemref->parent->reorder(elemref, sp_lpe_item);
316         Inkscape::GC::release(phantom);
317     }
318     cloneD(SP_OBJECT(sp_lpe_item), elemref, transform, reset);
319     elemref->getRepr()->setAttributeOrRemoveIfEmpty("transform", sp_svg_transform_write(transform));
320     SP_ITEM(elemref)->setHidden(false);
321     if (elemref->parent != container) {
322         Inkscape::XML::Node *copy = phantom->duplicate(xml_doc);
323         copy->setAttribute("id", elemref_id);
324         container->appendChildRepr(copy);
325         Inkscape::GC::release(copy);
326         elemref->deleteObject();
327     }
328 }
329 
330 void
resetStyles()331 LPECopyRotate::resetStyles(){
332     reset = true;
333     doAfterEffect(sp_lpe_item, nullptr);
334 }
335 
newWidget()336 Gtk::Widget * LPECopyRotate::newWidget()
337 {
338     // use manage here, because after deletion of Effect object, others might
339     // still be pointing to this widget.
340     Gtk::Box *vbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL));
341 
342     vbox->set_border_width(5);
343     vbox->set_homogeneous(false);
344     vbox->set_spacing(2);
345     std::vector<Parameter *>::iterator it = param_vector.begin();
346     while (it != param_vector.end()) {
347         if ((*it)->widget_is_visible) {
348             Parameter *param = *it;
349             Gtk::Widget *widg = dynamic_cast<Gtk::Widget *>(param->param_newWidget());
350             Glib::ustring *tip = param->param_getTooltip();
351             if (widg) {
352                 vbox->pack_start(*widg, true, true, 2);
353                 if (tip) {
354                     widg->set_tooltip_text(*tip);
355                 } else {
356                     widg->set_tooltip_text("");
357                     widg->set_has_tooltip(false);
358                 }
359             }
360         }
361 
362         ++it;
363     }
364     Gtk::Box * hbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL,0));
365     Gtk::Button * reset_button = Gtk::manage(new Gtk::Button(Glib::ustring(_("Reset styles"))));
366     reset_button->signal_clicked().connect(sigc::mem_fun (*this,&LPECopyRotate::resetStyles));
367     reset_button->set_size_request(110, 20);
368     vbox->pack_start(*hbox, true, true, 2);
369     hbox->pack_start(*reset_button, false, false, 2);
370     if(Gtk::Widget* widg = defaultParamSet()) {
371         vbox->pack_start(*widg, true, true, 2);
372     }
373     return dynamic_cast<Gtk::Widget *>(vbox);
374 }
375 
376 
377 void
doOnApply(SPLPEItem const * lpeitem)378 LPECopyRotate::doOnApply(SPLPEItem const* lpeitem)
379 {
380     using namespace Geom;
381     original_bbox(lpeitem, false, true);
382 
383     A = Point(boundingbox_X.min(), boundingbox_Y.middle());
384     B = Point(boundingbox_X.middle(), boundingbox_Y.middle());
385     origin.param_setValue(A, true);
386     origin.param_update_default(A);
387     dist_angle_handle = L2(B - A);
388     dir = unit_vector(B - A);
389 }
390 
391 void
doBeforeEffect(SPLPEItem const * lpeitem)392 LPECopyRotate::doBeforeEffect (SPLPEItem const* lpeitem)
393 {
394     using namespace Geom;
395     original_bbox(lpeitem, false, true);
396     if (copies_to_360 && num_copies > 2) {
397         rotation_angle.param_set_value(360.0/(double)num_copies);
398     }
399     if (method != RM_NORMAL && rotation_angle * num_copies > 360 && rotation_angle > 0 && copies_to_360) {
400         num_copies.param_set_value(floor(360/rotation_angle));
401     }
402     if (method != RM_NORMAL  && mirror_copies && copies_to_360) {
403         num_copies.param_set_increments(2.0,10.0);
404         if ((int)num_copies%2 !=0) {
405             num_copies.param_set_value(num_copies+1);
406             rotation_angle.param_set_value(360.0/(double)num_copies);
407         }
408     } else {
409         num_copies.param_set_increments(1.0, 10.0);
410     }
411 
412     A = Point(boundingbox_X.min(), boundingbox_Y.middle());
413     B = Point(boundingbox_X.middle(), boundingbox_Y.middle());
414     if (Geom::are_near(A, B, 0.01)) {
415         B += Geom::Point(1.0, 0.0);
416     }
417     dir = unit_vector(B - A);
418     // I first suspected the minus sign to be a bug in 2geom but it is
419     // likely due to SVG's choice of coordinate system orientation (max)
420     bool near_start_point = Geom::are_near(previous_start_point, (Geom::Point)starting_point, 0.01);
421     bool near_origin = Geom::are_near(previous_origin, (Geom::Point)origin, 0.01);
422     if (!near_start_point) {
423         starting_angle.param_set_value(deg_from_rad(-angle_between(dir, starting_point - origin)));
424         if (GDK_SHIFT_MASK) {
425             dist_angle_handle = L2(B - A);
426         } else {
427             dist_angle_handle = L2(starting_point - origin);
428         }
429     }
430     if (dist_angle_handle < 1.0) {
431         dist_angle_handle = 1.0;
432     }
433     double distance = dist_angle_handle;
434     if (previous_start_point != Geom::Point(0,0) || previous_origin != Geom::Point(0,0)) {
435         distance = Geom::distance(previous_origin, starting_point);
436     }
437     start_pos = origin + dir * Rotate(-rad_from_deg(starting_angle)) * distance;
438     if (!near_start_point || !near_origin || split_items) {
439         starting_point.param_setValue(start_pos);
440     }
441 
442     previous_origin = (Geom::Point)origin;
443     previous_start_point = (Geom::Point)starting_point;
444 }
445 
446 void
split(Geom::PathVector & path_on,Geom::Path const & divider)447 LPECopyRotate::split(Geom::PathVector &path_on, Geom::Path const &divider)
448 {
449     Geom::PathVector tmp_path;
450     double time_start = 0.0;
451     Geom::Path original = path_on[0];
452     int position = 0;
453     Geom::Crossings cs = crossings(original,divider);
454     std::vector<double> crossed;
455     for(auto & c : cs) {
456         crossed.push_back(c.ta);
457     }
458     std::sort(crossed.begin(), crossed.end());
459     for (double time_end : crossed) {
460         if (time_start == time_end || time_end - time_start < Geom::EPSILON) {
461             continue;
462         }
463         Geom::Path portion_original = original.portion(time_start,time_end);
464         if (!portion_original.empty()) {
465             Geom::Point side_checker = portion_original.pointAt(0.0001);
466             position = Geom::sgn(Geom::cross(divider[1].finalPoint() - divider[0].finalPoint(), side_checker - divider[0].finalPoint()));
467             if (rotation_angle != 180) {
468                 position = pointInTriangle(side_checker, divider.initialPoint(), divider[0].finalPoint(), divider[1].finalPoint());
469             }
470             if (position == 1) {
471                 tmp_path.push_back(portion_original);
472             }
473             portion_original.clear();
474             time_start = time_end;
475         }
476     }
477     position = Geom::sgn(Geom::cross(divider[1].finalPoint() - divider[0].finalPoint(), original.finalPoint() - divider[0].finalPoint()));
478     if (rotation_angle != 180) {
479         position = pointInTriangle(original.finalPoint(), divider.initialPoint(), divider[0].finalPoint(), divider[1].finalPoint());
480     }
481     if (cs.size() > 0 && position == 1) {
482         Geom::Path portion_original = original.portion(time_start, original.size());
483         if(!portion_original.empty()){
484             if (!original.closed()) {
485                 tmp_path.push_back(portion_original);
486             } else {
487                 if (tmp_path.size() > 0 && tmp_path[0].size() > 0 ) {
488                     portion_original.setFinal(tmp_path[0].initialPoint());
489                     portion_original.append(tmp_path[0]);
490                     tmp_path[0] = portion_original;
491                 } else {
492                     tmp_path.push_back(portion_original);
493                 }
494             }
495             portion_original.clear();
496         }
497     }
498     if (cs.size()==0  && position == 1) {
499         tmp_path.push_back(original);
500     }
501     path_on = tmp_path;
502 }
503 
504 Geom::PathVector
doEffect_path(Geom::PathVector const & path_in)505 LPECopyRotate::doEffect_path (Geom::PathVector const & path_in)
506 {
507     Geom::PathVector path_out;
508     double diagonal = Geom::distance(Geom::Point(boundingbox_X.min(),boundingbox_Y.min()),Geom::Point(boundingbox_X.max(),boundingbox_Y.max()));
509     Geom::OptRect bbox = sp_lpe_item->geometricBounds();
510     size_divider = Geom::distance(origin,bbox) + (diagonal * 6);
511     Geom::Point line_start  = origin + dir * Geom::Rotate(-(Geom::rad_from_deg(starting_angle))) * size_divider;
512     Geom::Point line_end = origin + dir * Geom::Rotate(-(Geom::rad_from_deg(rotation_angle + starting_angle))) * size_divider;
513     divider = Geom::Path(line_start);
514     divider.appendNew<Geom::LineSegment>((Geom::Point)origin);
515     divider.appendNew<Geom::LineSegment>(line_end);
516     Geom::OptRect trianglebounds = divider.boundsFast();
517     divider.close();
518     half_dir = unit_vector(Geom::middle_point(line_start,line_end) - (Geom::Point)origin);
519     FillRuleBool fillrule = fill_nonZero;
520     if (current_shape->style &&
521         current_shape->style->fill_rule.set &&
522         current_shape->style->fill_rule.computed == SP_WIND_RULE_EVENODD)
523     {
524         fillrule = (FillRuleBool)fill_oddEven;
525     }
526     if (method != RM_NORMAL) {
527         if (method != RM_KALEIDOSCOPE) {
528             path_out = doEffect_path_post(path_in, fillrule);
529         } else {
530             path_out = pathv_to_linear_and_cubic_beziers(path_in);
531         }
532         if (num_copies == 0) {
533             return path_out;
534         }
535         Geom::PathVector triangle;
536         triangle.push_back(divider);
537         path_out = sp_pathvector_boolop(path_out, triangle, bool_op_inters, fillrule, fillrule);
538         if ( !split_items ) {
539             path_out = doEffect_path_post(path_out, fillrule);
540         } else {
541             path_out *= Geom::Translate(half_dir * gap);
542         }
543     } else {
544         path_out = doEffect_path_post(path_in, fillrule);
545     }
546     if (!split_items && method != RM_NORMAL) {
547         Geom::PathVector path_out_tmp;
548         for (const auto & path_it : path_out) {
549             if (path_it.empty()) {
550                 continue;
551             }
552             Geom::Path::const_iterator curve_it1 = path_it.begin();
553             Geom::Path::const_iterator curve_endit = path_it.end_default();
554             Geom::Path res;
555             if (path_it.closed()) {
556                 const Geom::Curve &closingline = path_it.back_closed();
557                 // the closing line segment is always of type
558                 // Geom::LineSegment.
559                 if (are_near(closingline.initialPoint(), closingline.finalPoint())) {
560                     // closingline.isDegenerate() did not work, because it only checks for
561                     // *exact* zero length, which goes wrong for relative coordinates and
562                     // rounding errors...
563                     // the closing line segment has zero-length. So stop before that one!
564                     curve_endit = path_it.end_open();
565                 }
566             }
567             while (curve_it1 != curve_endit) {
568                 if (!Geom::are_near(curve_it1->initialPoint(), curve_it1->pointAt(0.5), 0.05)) {
569                     if (!res.empty()) {
570                         res.setFinal(curve_it1->initialPoint());
571                     }
572                     Geom::Curve *c = curve_it1->duplicate();
573                     res.append(c);
574                 }
575                 ++curve_it1;
576             }
577             if (path_it.closed()) {
578                 res.close();
579             }
580             path_out_tmp.push_back(res);
581         }
582         path_out = path_out_tmp;
583     }
584     return pathv_to_linear_and_cubic_beziers(path_out);
585 }
586 
587 Geom::PathVector
doEffect_path_post(Geom::PathVector const & path_in,FillRuleBool fillrule)588 LPECopyRotate::doEffect_path_post (Geom::PathVector const & path_in, FillRuleBool fillrule)
589 {
590     if ((split_items || num_copies == 1) && method == RM_NORMAL) {
591          if (split_items) {
592              Geom::PathVector path_out = pathv_to_linear_and_cubic_beziers(path_in);
593              Geom::Affine m = Geom::Translate(-origin) * Geom::Rotate(-(Geom::rad_from_deg(starting_angle)));
594              Geom::Affine t = m * Geom::Rotate(-Geom::rad_from_deg(starting_angle)) * Geom::Rotate(Geom::rad_from_deg(starting_angle)) * Geom::Translate(origin);
595              return path_out * t;
596          }
597         return path_in;
598     }
599 
600     Geom::Affine pre = Geom::Translate(-origin) * Geom::Rotate(-Geom::rad_from_deg(starting_angle));
601     Geom::PathVector original_pathv = pathv_to_linear_and_cubic_beziers(path_in);
602     Geom::PathVector output_pv;
603     Geom::PathVector output;
604     for (int i = 0; i < num_copies; ++i) {
605         Geom::Rotate rot(-Geom::rad_from_deg(rotation_angle * i));
606         Geom::Affine r = Geom::identity();
607         if( i%2 != 0 && mirror_copies) {
608             r *= Geom::Rotate(Geom::Angle(half_dir)).inverse();
609             r *= Geom::Scale(1, -1);
610             r *= Geom::Rotate(Geom::Angle(half_dir));
611         }
612         Geom::Affine t = pre * r * rot * Geom::Rotate(Geom::rad_from_deg(starting_angle)) * Geom::Translate(origin);
613         if(mirror_copies && i%2 != 0) {
614             t = pre * r * rot * Geom::Rotate(Geom::rad_from_deg(starting_angle)).inverse() * Geom::Translate(origin);
615         }
616         if (method != RM_NORMAL) {
617             //we use safest way to union
618             Geom::PathVector join_pv = original_pathv * t;
619             join_pv *= Geom::Translate(half_dir * rot * gap);
620             if (!output_pv.empty()) {
621                 output_pv = sp_pathvector_boolop(output_pv, join_pv, bool_op_union, fillrule, fillrule);
622             } else {
623                 output_pv = join_pv;
624             }
625         } else {
626             t = pre * Geom::Rotate(-Geom::rad_from_deg(starting_angle)) * r * rot * Geom::Rotate(Geom::rad_from_deg(starting_angle)) * Geom::Translate(origin);
627             if(mirror_copies && i%2 != 0) {
628                 t = pre * Geom::Rotate(Geom::rad_from_deg(-starting_angle-rotation_angle)) * r * rot * Geom::Rotate(-Geom::rad_from_deg(starting_angle)) * Geom::Translate(origin);
629             }
630             output_pv = path_in * t;
631             output.insert(output.end(), output_pv.begin(), output_pv.end());
632         }
633     }
634     if (method != RM_NORMAL) {
635         output = output_pv;
636     }
637     return output;
638 }
639 
640 void
addCanvasIndicators(SPLPEItem const *,std::vector<Geom::PathVector> & hp_vec)641 LPECopyRotate::addCanvasIndicators(SPLPEItem const */*lpeitem*/, std::vector<Geom::PathVector> &hp_vec)
642 {
643     using namespace Geom;
644     hp_vec.clear();
645     Geom::Path hp;
646     hp.start(start_pos);
647     hp.appendNew<Geom::LineSegment>((Geom::Point)origin);
648     hp.appendNew<Geom::LineSegment>(origin + dir * Rotate(-rad_from_deg(rotation_angle+starting_angle)) * Geom::distance(origin,starting_point));
649     Geom::PathVector pathv;
650     pathv.push_back(hp);
651     hp_vec.push_back(pathv);
652 }
653 
654 void
resetDefaults(SPItem const * item)655 LPECopyRotate::resetDefaults(SPItem const* item)
656 {
657     Effect::resetDefaults(item);
658     original_bbox(SP_LPE_ITEM(item), false, true);
659 }
660 
661 void
doOnVisibilityToggled(SPLPEItem const *)662 LPECopyRotate::doOnVisibilityToggled(SPLPEItem const* /*lpeitem*/)
663 {
664     processObjects(LPE_VISIBILITY);
665 }
666 
667 void
doOnRemove(SPLPEItem const * lpeitem)668 LPECopyRotate::doOnRemove (SPLPEItem const* lpeitem)
669 {
670     std::vector<SPLPEItem *> lpeitems = getCurrrentLPEItems();
671     if (lpeitems.size() == 1) {
672         sp_lpe_item = lpeitems[0];
673         if (!sp_lpe_item->path_effects_enabled) {
674             return;
675         }
676         //set "keep paths" hook on sp-lpe-item.cpp
677         if (keep_paths) {
678             processObjects(LPE_TO_OBJECTS);
679             items.clear();
680             return;
681         }
682         processObjects(LPE_ERASE);
683     }
684 }
685 
686 } //namespace LivePathEffect
687 } /* namespace Inkscape */
688 
689 /*
690   Local Variables:
691   mode:c++
692   c-file-style:"stroustrup"
693   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
694   indent-tabs-mode:nil
695   fill-column:99
696   End:
697 */
698 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
699