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 ÷r)
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