Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
lpe-tiling.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
5/*
6 * Authors:
7 * Jabiertxo Arraiza Cenoz <jabier.arraiza@marker.es>
8 * Adam Belis <>
9 * Copyright (C) Authors 2022-2022
10 *
11 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
12 */
13
15
16#include <algorithm>
17#include <functional>
18#include <limits>
22#include <gdk/gdk.h>
23#include <glibmm/i18n.h>
24#include <glibmm/ustring.h>
25#include <gtkmm/box.h>
26#include <gtkmm/combobox.h>
27#include <gtkmm/frame.h>
28#include <gtkmm/image.h>
29#include <gtkmm/label.h>
30#include <gtkmm/object.h>
31#include <gtkmm/spinbutton.h>
32
33#include "inkscape.h"
34#include "preferences.h"
35#include "style.h"
36
37#include "display/curve.h"
38#include "helper/geom.h"
41#include "object/sp-object.h"
42#include "object/sp-path.h"
43#include "object/sp-shape.h"
44#include "object/sp-text.h"
45#include "svg/svg.h"
46#include "ui/icon-loader.h"
47#include "ui/icon-names.h"
49#include "ui/knot/knot-holder.h"
50#include "ui/pack.h"
51#include "ui/util.h"
53
55
56namespace CoS {
57
58class KnotHolderEntityCopyGapX final : public LPEKnotHolderEntity {
59public:
60 KnotHolderEntityCopyGapX(LPETiling * effect) : LPEKnotHolderEntity(effect) {};
61 ~KnotHolderEntityCopyGapX() final;
62
63 void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state) final;
64 void knot_click(guint state) final;
65 Geom::Point knot_get() const final;
66
67 double startpos = dynamic_cast<LPETiling const*> (_effect)->gapx_unit;
68};
69
70class KnotHolderEntityCopyGapY final : public LPEKnotHolderEntity {
71public:
72 KnotHolderEntityCopyGapY(LPETiling * effect) : LPEKnotHolderEntity(effect) {};
73 ~KnotHolderEntityCopyGapY() final;
74
75 void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state) final;
76 void knot_click(guint state) final;
77 Geom::Point knot_get() const final;
78
79 double startpos = dynamic_cast<LPETiling const*> (_effect)->gapy_unit;
80};
81
82} // namespace CoS
83
85 Effect(lpeobject),
86 // do not change name of this parameter is used in oncommit
87 unit(_("Unit:"), _("Unit"), "unit", &wr, this, "px"),
88 lpesatellites(_("lpesatellites"), _("Items satellites"), "lpesatellites", &wr, this, false),
89 num_cols(_("Columns"), _("Number of columns"), "num_cols", &wr, this, 3),
90 num_rows(_("Rows"), _("Number of rows"), "num_rows", &wr, this, 3),
91 gapx(_("Gap X"), _("Horizontal gap between tiles (uses selected unit)"), "gapx", &wr, this, 0.0),
92 gapy(_("Gap Y"), _("Vertical gap between tiles (uses selected unit)"), "gapy", &wr, this, 0.0),
93 scale(_("Scale %"), _("Scale tiles by this percentage"), "scale", &wr, this, 0.0),
94 rotate(_("Rotate °"), _("Rotate tiles by this amount of degrees"), "rotate", &wr, this, 0.0),
95 offset(_("Offset %"), _("Offset tiles by this percentage of width/height"), "offset", &wr, this, 0.0),
96 offset_type(_("Offset type"), _("Choose whether to offset rows or columns"), "offset_type", &wr, this, false),
97 interpolate_scalex(_("Interpolate scale X"), _("Interpolate tile size in each row"), "interpolate_scalex", &wr, this, false),
98 interpolate_scaley(_("Interpolate scale Y"), _("Interpolate tile size in each column"), "interpolate_scaley", &wr, this, true),
99 shrink_interp(_("Minimize gaps"), _("Minimize gaps between scaled objects (does not work with rotation/diagonal mode)"), "shrink_interp", &wr, this, false),
100 interpolate_rotatex(_("Interpolate rotation X"), _("Interpolate tile rotation in row"), "interpolate_rotatex", &wr, this, false),
101 interpolate_rotatey(_("Interpolate rotation Y"), _("Interpolate tile rotation in column"), "interpolate_rotatey", &wr, this, true),
102 split_items(_("Split elements"), _("Split elements, so they can be selected, styled, and moved (if grouped) independently"), "split_items", &wr, this, false),
103 mirrorrowsx(_("Mirror rows in X"), _("Mirror rows horizontally"), "mirrorrowsx", &wr, this, false),
104 mirrorrowsy(_("Mirror rows in Y"), _("Mirror rows vertically"), "mirrorrowsy", &wr, this, false),
105 mirrorcolsx(_("Mirror cols in X"), _("Mirror columns horizontally"), "mirrorcolsx", &wr, this, false),
106 mirrorcolsy(_("Mirror cols in Y"), _("Mirror columns vertically"), "mirrorcolsy", &wr, this, false),
107 mirrortrans(_("Mirror transforms"), _("Mirror transformations"), "mirrortrans", &wr, this, false),
108 link_styles(_("Link styles"), _("Link styles in split mode, can also be used to reset style of copies"), "link_styles", &wr, this, false),
109 random_gap_x(_("Random gaps X"), _("Randomize horizontal gaps"), "random_gap_x", &wr, this, false),
110 random_gap_y(_("Random gaps Y"), _("Randomize vertical gaps"), "random_gap_y", &wr, this, false),
111 random_rotate(_("Random rotation"), _("Randomize tile rotation"), "random_rotate", &wr, this, false),
112 random_scale(_("Random scale"), _("Randomize scale"), "random_scale", &wr, this, false),
113 seed(_("Seed"), _("Randomization seed"), "seed", &wr, this, 1.),
114 transformorigin("transformorigin:", "transformorigin","transformorigin", &wr, this, "", true)
115{
116 show_orig_path = true;
118
119 // register all your parameters here, so Inkscape knows which parameters this effect has:
120 // please intense work on this widget and is important reorder parameters very carefully
149
150 num_cols.param_set_range(1, 9999);// we need the input a bit tiny so this seems enough
153 num_rows.param_set_range(1, 9999);
156 scale.param_set_range(-9999.99,9999.99);
158 gapx.param_set_range(-99999,99999);
159 gapx.param_set_increments(1.0, 10.0);
160 gapy.param_set_range(-99999,99999);
161 gapy.param_set_increments(1.0, 10.0);
162 rotate.param_set_increments(1.0, 10.0);
163 rotate.param_set_range(-900, 900);
164 offset.param_set_range(-300, 300);
165 offset.param_set_increments(1.0, 10.0);
166 seed.param_set_range(1.0, 1.0);
172 _knotholder = nullptr;
175}
176
178{
179 if (_knotholder) {
181 _knotholder = nullptr;
182 }
183}
184
185bool LPETiling::doOnOpen(SPLPEItem const *lpeitem)
186{
187 bool fixed = false;
188 if (!is_load || is_applied) {
189 return fixed;
190 }
191 if (!split_items) {
192 return fixed;
193 }
195 container = lpeitem->parent;
196 return fixed;
197}
198
199void
201{
202 if (split_items) {
203 SPDocument *document = getSPDoc();
204 if (!document) {
205 return;
206 }
207 bool write = false;
208 bool active = !lpesatellites.data().size();
209 for (auto lpereference : lpesatellites.data()) {
210 if (lpereference && lpereference->isAttached() && lpereference.get()->getObject() != nullptr) {
211 active = true;
212 }
213 }
214 if (!active && !is_load && prev_split) {
216 prev_num_cols = 0;
217 prev_num_rows = 0;
218 }
220
223 write = true;
224 size_t pos = 0;
225 for (auto lpereference : lpesatellites.data()) {
226 if (lpereference && lpereference->isAttached()) {
227 auto copies = cast<SPItem>(lpereference->getObject());
228 if (copies) {
229 if (pos > num_cols * num_rows - 2) {
230 copies->setHidden(true);
231 } else if (copies->isHidden()) {
232 copies->setHidden(false);
233 }
234 }
235 }
236 pos++;
237 }
240 }
241 if (!gap_bbox) {
242 return;
243 }
244 Geom::Point center = (*gap_bbox).midpoint() * transformoriginal.inverse();
245 bool forcewrite = false;
249 }
252 }
253 origin *= Geom::Translate(center);
254 origin = origin.inverse();
255 size_t counter = 0;
256 double gapscalex = 0;
257 double maxheight = 0;
258 double maxwidth = 0;
259 double minheight = std::numeric_limits<double>::max();
260 double y[(int)num_cols];
261 double ygap[(int)num_cols];
262 double yset = 0;
263 Geom::OptRect prev_bbox;
265
267 Geom::Affine gapp = base_transform.inverse() * transformoriginal;
268 Geom::Point spcenter_base = (*sp_lpe_item->geometricBounds(transformoriginal)).midpoint();
269 Geom::Point spcenter = (*sp_lpe_item->geometricBounds(base_transform)).midpoint();
270 Geom::Affine gap = gapp.withoutTranslation();
271 if (!bbox) {
272 return;
273 }
274 (*bbox) *= transformoriginal;
275 for (int i = 0; i < num_rows; ++i) {
276 double fracy = 1;
277 if (num_rows != 1) {
278 fracy = i/(double)(num_rows - 1);
279 }
280 for (int j = 0; j < num_cols; ++j) {
281 double x = 0;
282 double fracx = 1;
283 if (num_cols != 1) {
284 fracx = j/(double)(num_cols - 1);
285 }
287 Geom::Scale mirror = Geom::Scale(1,1);
289 gint mx = 1;
290 gint my = 1;
291 if (mirrorrowsx && mirrorcolsx) {
292 mx = (j+i)%2 != 0 ? -1 : 1;
293 } else {
294 if (mirrorrowsx) {
295 mx = i%2 != 0 ? -1 : 1;
296 } else if (mirrorcolsx) {
297 mx = j%2 != 0 ? -1 : 1;
298 }
299 }
300 if (mirrorrowsy && mirrorcolsy) {
301 my = (j+i)%2 != 0 ? -1 : 1;
302 } else {
303 if (mirrorrowsy) {
304 my = i%2 != 0 ? -1 : 1;
305 } else if (mirrorcolsy) {
306 my = j%2 != 0 ? -1 : 1;
307 }
308 }
309 mirror = Geom::Scale(mx, my);
310 }
311 if (mirrortrans && interpolate_scalex && i%2 != 0) {
312 fracx = 1-fracx;
313 }
314 double fracyin = fracy;
315 if (mirrortrans && interpolate_scaley && j%2 != 0) {
316 fracyin = 1-fracyin;
317 }
318 double rotatein = rotate;
320 rotatein = rotatein * (i + j);
321 } else if (interpolate_rotatex) {
322 rotatein = rotatein * j;
323 } else if (interpolate_rotatey) {
324 rotatein = rotatein * i;
325 }
326 if (mirrortrans &&
327 ((interpolate_rotatex && i%2 != 0) ||
328 (interpolate_rotatey && j%2 != 0) ||
330 {
331 rotatein *=-1;
332 }
333 double scalein = 1;
334 double scalegap = scaleok - scalein;
336 scalein = (scalegap * (i + j)) + 1;
337 } else if (interpolate_scalex) {
338 scalein = (scalegap * j) + 1;
339 } else if (interpolate_scaley) {
340 scalein = (scalegap * i) + 1;
341 } else {
342 scalein = scaleok;
343 }
345 r *= Geom::Rotate::from_degrees(rotatein).inverse();
346 }
347 if (random_scale && scaleok != 1.0) {
348 if (random_s.size() == counter) {
349 double max = std::max(1.0,scaleok);
350 double min = std::min(1.0,scaleok);
351 random_s.emplace_back(seed.param_get_random_number() * (max - min) + min);
352 }
353 scalein = random_s[counter];
354 }
355 if (random_rotate && rotate) {
356 if (random_r.size() == counter) {
358 }
359 rotatein = random_r[counter];
360 }
361 if (random_x.size() == counter) {
362 if (random_gap_x && gapx_unit) {
363 random_x.emplace_back((seed.param_get_random_number() * gapx_unit)); // avoid overlapping
364 } else {
365 random_x.emplace_back(0);
366 }
367 }
368 if (random_y.size() == counter) {
369 if (random_gap_y && gapy_unit) {
370 random_y.emplace_back((seed.param_get_random_number() * gapy_unit)); // avoid overlapping
371 } else {
372 random_y.emplace_back(0);
373 }
374 }
375 r *= Geom::Rotate::from_degrees(rotatein);
376 r *= Geom::Scale(scalein, scalein);
377 double scale_fix = end_scale(scaleok, true);
378 double heightrows = original_height * scale_fix;
379 double widthcols = original_width * scale_fix;
380 double fixed_heightrows = heightrows;
381 double fixed_widthcols = widthcols;
382 bool shrink_interpove = shrink_interp;
383 if (rotatein) {
384 shrink_interpove = false;
385 }
386 if (scaleok != 1.0 && (interpolate_scalex || interpolate_scaley)) {
387 maxheight = std::max(maxheight,(*bbox).height() * scalein);
388 maxwidth = std::max(maxwidth,(*bbox).width() * scalein);
389 minheight = std::min(minheight,(*bbox).height() * scalein);
390 widthcols = std::max(original_width * end_scale(scaleok, false), original_width);
391 heightrows = std::max(original_height * end_scale(scaleok, false), original_height);
392 fixed_widthcols = widthcols;
393 fixed_heightrows = heightrows;
394 double cx = (*bbox).width() * scalein;
395 double cy = (*bbox).height() * scalein;
396 cx += gapx_unit;
397 cy += gapy_unit;
398 if (shrink_interpove && (!interpolate_scalex || !interpolate_scaley)) {
399 double px = 0;
400 double py = 0;
401 if (prev_bbox) {
402 px = (*prev_bbox).width();
403 py = (*prev_bbox).height();
404 px += gapx_unit;
405 py += gapy_unit;
406 }
407 if (interpolate_scalex) {
408 if (j) {
409 x = cx - ((cx-px)/2.0);
410 gapscalex += x;
411 x = gapscalex;
412 } else {
413 x = 0;
414 gapscalex = 0;
415 }
416 widthcols = 0;
417 } else if (interpolate_scaley) {
418 x = 0;
419 if (i == 1) {
420 ygap[j] = ((cy-y[j])/2.0);
421 y[j] += ygap[j];
422 }
423 yset = y[j];
424 y[j] += cy + ygap[j];
425 heightrows = 0;
426 }
427 }
428 prev_bbox = bbox;
429 } else {
430 y[j] = 0;
431 }
432 if (!counter) {
433 counter++;
434 continue;
435 }
436 double xset = x;
437 xset += widthcols * j;
438 if (heightrows) {
439 yset = heightrows * i;
440 }
441 SPItem * item = toItem(counter - 1, reset, write);
442 if (item) {
443 if (!(lpesatellites.data().size() > counter - 1 && lpesatellites.data()[counter - 1])) {
444 item->deleteObject(true);
445 return;
446 }
447 prev_bbox = item->geometricBounds();
448 (*prev_bbox) *= r;
449 double offset_x = 0;
450 double offset_y = 0;
451 if (offset != 0) {
452 if (offset_type && j%2) {
453 offset_y = fixed_heightrows/(100.0/(double)offset);
454 }
455 if (!offset_type && i%2) {
456 offset_x = fixed_widthcols/(100.0/(double)offset);
457 }
458 }
459
460
461 auto p = Geom::Point(xset + offset_x - random_x[counter], yset + offset_y - random_y[counter]);
462 auto translate = p * gap.inverse();
463 Geom::Affine finalit = (transformoriginal * Geom::Translate(spcenter_base).inverse() * mirror * Geom::Translate(spcenter_base));
464 finalit *= gapp.inverse() * Geom::Translate(spcenter).inverse() * originatrans.withoutTranslation().inverse() * r * Geom::Translate(translate) * Geom::Translate(spcenter);
465 item->doWriteTransform(finalit);
466 item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
467 forcewrite = forcewrite || write;
468 }
469 counter++;
470 }
471 }
472 //we keep satellites connected and active if write needed
473 bool connected = lpesatellites.is_connected();
474 if (forcewrite || !connected) {
477 if (!connected) {
478 sp_lpe_item_update_patheffect(sp_lpe_item, false, false, true);
479 } else {
481 }
482 }
484 }
485}
486
488{
489 dest->setAttribute("transform", orig->getAttribute("transform"));
490 dest->setAttribute("style", orig->getAttribute("style"));
491 dest->setAttribute("mask", orig->getAttribute("mask"));
492 dest->setAttribute("clip-path", orig->getAttribute("clip-path"));
493 dest->setAttribute("class", orig->getAttribute("class"));
494 for (auto iter : orig->style->properties()) {
495 if (iter->style_src != SPStyleSrc::UNSET) {
496 auto key = iter->id();
497 if (key != SPAttr::FONT && key != SPAttr::D && key != SPAttr::MARKER) {
498 if (auto const attr = orig->getAttribute(iter->name().c_str())) {
499 dest->setAttribute(iter->name(), attr);
500 }
501 }
502 }
503 }
504}
505
507{
508 SPDocument *document = getSPDoc();
509 if (!document) {
510 return;
511 }
512 if ( is<SPGroup>(orig) && is<SPGroup>(dest) && cast<SPGroup>(orig)->getItemCount() == cast<SPGroup>(dest)->getItemCount() ) {
513 if (reset) {
514 cloneStyle(orig, dest);
515 }
516 std::vector< SPObject * > childs = orig->childList(true);
517 size_t index = 0;
518 for (auto & child : childs) {
519 SPObject *dest_child = dest->nthChild(index);
520 cloneD(child, dest_child);
521 index++;
522 }
523 return;
524 } else if( is<SPGroup>(orig) && is<SPGroup>(dest) && cast<SPGroup>(orig)->getItemCount() != cast<SPGroup>(dest)->getItemCount()) {
526 return;
527 }
528
529 if ( is<SPText>(orig) && is<SPText>(dest) && cast<SPText>(orig)->children.size() == cast<SPText>(dest)->children.size()) {
530 if (reset) {
531 cloneStyle(orig, dest);
532 }
533 size_t index = 0;
534 for (auto & child : cast<SPText>(orig)->children) {
535 SPObject *dest_child = dest->nthChild(index);
536 cloneD(&child, dest_child);
537 index++;
538 }
539 }
540
541 auto shape = cast<SPShape>(orig);
542 auto path = cast<SPPath>(dest);
543 if (shape) {
544 SPCurve const *c = shape->curve();
545 if (c) {
546 auto str = sp_svg_write_path(c->get_pathvector());
547 if (shape && !path) {
548 const char *id = dest->getAttribute("id");
549 const char *style = dest->getAttribute("style");
550 Inkscape::XML::Document *xml_doc = dest->document->getReprDoc();
551 Inkscape::XML::Node *dest_node = xml_doc->createElement("svg:path");
552 dest_node->setAttribute("id", id);
553 dest_node->setAttribute("style", style);
554 dest->updateRepr(xml_doc, dest_node, SP_OBJECT_WRITE_ALL);
555 path = cast<SPPath>(dest);
556 }
557 path->setAttribute("d", str);
558 } else {
559 path->removeAttribute("d");
560 }
561 }
562 if (reset) {
563 cloneStyle(orig, dest);
564 }
565}
566
569 SPDocument *document = getSPDoc();
570 if (!document) {
571 return nullptr;
572 }
573 Inkscape::XML::Document *xml_doc = document->getReprDoc();
574 Inkscape::XML::Node *prev = elemref->getRepr();
575 auto group = cast<SPGroup>(elemref);
576 if (group) {
577 Inkscape::XML::Node *container = xml_doc->createElement("svg:g");
578 container->setAttribute("transform", prev->attribute("transform"));
579 container->setAttribute("mask", prev->attribute("mask"));
580 container->setAttribute("clip-path", prev->attribute("clip-path"));
581 container->setAttribute("class", prev->attribute("class"));
582 container->setAttribute("style", prev->attribute("style"));
583 std::vector<SPItem*> const item_list = group->item_list();
584 Inkscape::XML::Node *previous = nullptr;
585 for (auto sub_item : item_list) {
586 Inkscape::XML::Node *resultnode = createPathBase(sub_item);
587
588 container->addChild(resultnode, previous);
589 previous = resultnode;
590 }
591 return container;
592 }
593 Inkscape::XML::Node *resultnode = xml_doc->createElement("svg:path");
594 resultnode->setAttribute("transform", prev->attribute("transform"));
595 resultnode->setAttribute("style", prev->attribute("style"));
596 resultnode->setAttribute("mask", prev->attribute("mask"));
597 resultnode->setAttribute("clip-path", prev->attribute("clip-path"));
598 resultnode->setAttribute("class", prev->attribute("class"));
599 return resultnode;
600}
601
602
603SPItem *
604LPETiling::toItem(size_t i, bool reset, bool &write)
605{
606 SPDocument *document = getSPDoc();
607 if (!document) {
608 return nullptr;
609 }
610
611 SPObject *elemref = nullptr;
612 if (container != sp_lpe_item->parent) {
614 return nullptr;
615 }
616 if (lpesatellites.data().size() > i && lpesatellites.data()[i]) {
617 elemref = lpesatellites.data()[i]->getObject();
618 }
619 Inkscape::XML::Node *phantom = nullptr;
620 bool creation = false;
621 if (elemref) {
622 phantom = elemref->getRepr();
623 } else {
624 creation = true;
625 phantom = createPathBase(sp_lpe_item);
626 reset = true;
627 elemref = container->appendChildRepr(phantom);
628
629 Inkscape::GC::release(phantom);
630 }
631 cloneD(sp_lpe_item, elemref);
633 if (creation) {
634 write = true;
635 lpesatellites.link(elemref, i);
636 }
637 return cast<SPItem>(elemref);
638}
639
640Gtk::ToggleButton* create_radio_button(Gtk::ToggleButton *&group, const Glib::ustring& tooltip, const Glib::ustring& icon_name) {
641 auto const button = Gtk::make_managed<Gtk::ToggleButton>();
642 if (group) {
643 button->set_group(*group);
644 } else {
645 group = button;
646 }
647 button->set_tooltip_text(tooltip);
648 button->set_image_from_icon_name(icon_name, Gtk::IconSize::NORMAL);
649 button->set_halign(Gtk::Align::CENTER);
650 button->set_valign(Gtk::Align::CENTER);
651 button->add_css_class("lpe-square-button");
652 return button;
653}
654
655void align_widgets(const std::vector<Gtk::Widget*>& widgets, int spinbutton_chars = 7) {
656 // traverse container, locate n-th child in each row
657 auto const for_child_n = [&widgets](int const child_index,
658 std::function<void (Gtk::Widget *)> const &action)
659 {
660 for (auto child : widgets) {
661 auto container = dynamic_cast<Gtk::Box *>(child);
662 if (!container) continue;
663
664 auto const children = UI::get_children(*container);
665 if (child_index < children.size()) {
666 action(children[child_index]);
667 }
668 }
669 };
670
671 auto const get_natural_width = [](Gtk::Widget const &widget)
672 {
673 g_assert(widget.get_visible());
674 int natural{}, ignore{};
675 widget.measure(Gtk::Orientation::HORIZONTAL, -1, ignore, natural, ignore, ignore);
676 return natural;
677 };
678
679 // column 0 - labels
680 int max_width = 0;
681 for_child_n(0, [&](Gtk::Widget* child){
682 if (auto label = dynamic_cast<Gtk::Label*>(child)) {
683 label->set_xalign(0); // left-align
684 max_width = std::max(max_width, get_natural_width(*label));
685 }
686 });
687 // align
688 for_child_n(0, [=](Gtk::Widget* child) {
689 if (auto label = dynamic_cast<Gtk::Label*>(child)) {
690 label->set_size_request(max_width);
691 }
692 });
693
694 // column 1 - align spin buttons, if any
695 int button_width = 0;
696 for_child_n(1, [&](Gtk::Widget* child) {
697 if (auto spin = dynamic_cast<Gtk::SpinButton*>(child)) {
698 // arbitrarily selected spinbutton size
699 spin->set_width_chars(spinbutton_chars);
700 button_width = std::max(button_width, get_natural_width(*spin));
701 }
702 });
703 // set min size for comboboxes, if any
704 int combo_size = button_width > 0 ? button_width : 50; // match with spinbuttons, or just min of 50px
705 for_child_n(1, [=](Gtk::Widget* child) {
706 if (auto combo = dynamic_cast<Gtk::ComboBox*>(child)) {
707 combo->set_size_request(combo_size);
708 }
709 });
710}
711
712Gtk::Widget * LPETiling::newWidget()
713{
714 // use manage here, because after deletion of Effect object, others might
715 // still be pointing to this widget.
716 auto const vbox = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL);
717 vbox->set_margin(5);
718
719 Gtk::Widget *combo = nullptr;
720 Gtk::Widget *randbutton = nullptr;
721 Gtk::Box *containerstart = nullptr;
722 Gtk::Box *containerend = nullptr;
723 Gtk::Box *movestart = nullptr;
724 Gtk::Box *moveend = nullptr;
725 Gtk::Box *rowcols = nullptr;
727 bool usemirroricons = prefs->getBool("/live_effects/copy/mirroricons",true);
728 std::vector<Gtk::Widget*> scalars;
729
730 for (auto const param: param_vector) {
731 if (!param->widget_is_visible) continue;
732
733 auto const widg = param->param_newWidget();
734 if (!widg) continue;
735
736 if (param->param_key == "unit") {
738
739 auto const widgcombo = dynamic_cast<UI::Widget::RegisteredUnitMenu *>(widg);
740
741 auto const destroy_child = widgcombo->get_first_child();
742 widgcombo->remove(*destroy_child);
743
744 combo = widgcombo;
745
746 if (!usemirroricons) continue;
747
748 Gtk::ToggleButton *group = nullptr;
749 auto const frame = Gtk::make_managed<Gtk::Frame>(_("Mirroring mode"));
750 frame->set_halign(Gtk::Align::START);
751 auto const cbox = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL,0);
752 auto const vbox1 = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL,0);
753 auto const hbox1 = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL,0);
754 auto const hbox2 = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL,0);
755 auto const vbox2 = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL,0);
756 auto const hbox3 = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL,0);
757 auto const hbox4 = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL,0);
758 vbox2->set_margin_start(5);
759 vbox1->set_margin_bottom(3);
760 UI::pack_start(*cbox, *vbox1, false, false);
761 UI::pack_start(*cbox, *vbox2, false, false);
762 cbox->set_margin_start(6);
763 cbox->set_margin_end(6);
764 cbox->set_margin_bottom(3);
765 cbox->set_halign(Gtk::Align::START);
766 hbox1->set_margin_bottom(3);
767 hbox3->set_margin_bottom(3);
768 frame->set_child(*cbox);
769 UI::pack_start(*vbox, *frame, false, false, 1);
770 UI::pack_start(*vbox1, *hbox1, false, false);
771 UI::pack_start(*vbox1, *hbox2, false, false);
772 UI::pack_start(*vbox2, *hbox3, false, false);
773 UI::pack_start(*vbox2, *hbox4, false, false);
774 generate_buttons(hbox1, group, 0);
775 generate_buttons(hbox2, group, 1);
776 generate_buttons(hbox3, group, 2);
777 generate_buttons(hbox4, group, 3);
778
779 continue;
780 } else if (param->param_key == "seed"){
781 auto const widgrand = dynamic_cast<UI::Widget::RegisteredRandom *>(widg);
782
783 auto const destroy_child = widgrand->get_first_child();
784 widgrand->remove(*destroy_child);
785
786 auto const first = widgrand->get_first_child();
787 first->set_visible(false);
788
789 auto const button = dynamic_cast<Gtk::Button *>(first->get_next_sibling());
790 g_assert(button);
791
792 auto const box = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 6);
793 box->append(*Gtk::manage(sp_get_icon_image(INKSCAPE_ICON("randomize"), Gtk::IconSize::NORMAL)));
794 box->append(*Gtk::make_managed<Gtk::Label>(_("Randomize")));
795 button->set_child(*box);
796
797 button->set_tooltip_markup(_("Randomization seed for random mode for scaling, rotation and gaps"));
798 button->set_has_frame(true);
799 button->set_valign(Gtk::Align::START);
800
801 widgrand->set_vexpand(false);
802 widgrand->set_hexpand(false);
803 widgrand->set_valign(Gtk::Align::START);
804 widgrand->set_halign(Gtk::Align::START);
805 randbutton = Gtk::manage(widgrand);
806
807 continue;
808 } else if (param->param_key == "offset_type" ||
809 param->param_key == "mirrorrowsx" && usemirroricons ||
810 param->param_key == "mirrorrowsy" && usemirroricons ||
811 param->param_key == "mirrorcolsx" && usemirroricons ||
812 param->param_key == "mirrorcolsy" && usemirroricons ||
813 param->param_key == "interpolate_rotatex" ||
814 param->param_key == "interpolate_rotatey" ||
815 param->param_key == "interpolate_scalex" ||
816 param->param_key == "interpolate_scaley" ||
817 param->param_key == "random_scale" ||
818 param->param_key == "random_rotate" ||
819 param->param_key == "random_gap_x" ||
820 param->param_key == "random_gap_y")
821 {
822 continue;
823 } else if (param->param_key == "offset") {
824 UI::pack_start(*movestart, *widg, false, false, 2);
825 auto const container = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL,0);
826 Gtk::ToggleButton *group = nullptr;
827 auto rows = create_radio_button(group, _("Offset rows"), INKSCAPE_ICON("rows"));
828 auto cols = create_radio_button(group, _("Offset columns"), INKSCAPE_ICON("cols"));
829 rows->set_tooltip_markup(_("Offset alternate rows"));
830 cols->set_tooltip_markup(_("Offset alternate cols"));
831 if (offset_type) {
832 cols->set_active();
833 } else {
834 rows->set_active();
835 }
836 UI::pack_start(*container, *rows, false, false, 1);
837 UI::pack_start(*container, *cols, false, false, 1);
838 cols->signal_clicked().connect(sigc::mem_fun (*this, &LPETiling::setOffsetCols));
839 rows->signal_clicked().connect(sigc::mem_fun (*this, &LPETiling::setOffsetRows));
840 UI::pack_start(*moveend, *container, false, false, 2);
841 } else if (param->param_key == "scale") {
842 auto const container = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL,0);
843 Gtk::ToggleButton *group = nullptr;
844 auto cols = create_radio_button(group, _("Interpolate X"), INKSCAPE_ICON("interpolate-scale-x"));
845 auto rows = create_radio_button(group, _("Interpolate Y"), INKSCAPE_ICON("interpolate-scale-y"));
846 auto both = create_radio_button(group, _("Interpolate both"), INKSCAPE_ICON("interpolate-scale-both"));
847 auto none = create_radio_button(group, _("No interpolation"), INKSCAPE_ICON("interpolate-scale-none"));
848 auto rand = create_radio_button(group, _("Interpolate random"), INKSCAPE_ICON("scale-random"));
850 both->set_active();
851 } else if (interpolate_scalex) {
852 cols->set_active();
853 } else if (interpolate_scaley) {
854 rows->set_active();
855 } else if (random_scale) {
856 rand->set_active();
857 } else {
858 none->set_active();
859 }
860 cols->set_tooltip_markup(_("Blend scale from <b>left to right</b> (left column uses original scale, right column uses new scale)"));
861 rows->set_tooltip_markup(_("Blend scale from <b>top to bottom</b> (top row uses original scale, bottom row uses new scale)"));
862 both->set_tooltip_markup(_("Blend scale <b>diagonally</b> (top left tile uses original scale, bottom right tile uses new scale)"));
863 none->set_tooltip_markup(_("Uniform scale"));
864 rand->set_tooltip_markup(_("Random scale (hit <b>Randomize</b> button to shuffle)"));
865 UI::pack_start(*container, *rows, false, false, 1);
866 UI::pack_start(*container, *cols, false, false, 1);
867 UI::pack_start(*container, *both, false, false, 1);
868 UI::pack_start(*container, *none, false, false, 1);
869 UI::pack_start(*container, *rand, false, false, 1);
870 rand->signal_clicked().connect(sigc::mem_fun(*this, &LPETiling::setScaleRandom));
871 none->signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &LPETiling::setScaleInterpolate), false, false));
872 cols->signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &LPETiling::setScaleInterpolate), true, false));
873 rows->signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &LPETiling::setScaleInterpolate), false, true));
874 both->signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &LPETiling::setScaleInterpolate), true, true));
875 UI::pack_start(*movestart, *widg, false, false, 2);
876 UI::pack_start(*moveend, *container, false, false, 2);
877 } else if (param->param_key == "rotate") {
878 UI::pack_start(*movestart, *widg, false, false, 2);
879 auto const container = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL,0);
880 Gtk::ToggleButton *group = nullptr;
881 auto cols = create_radio_button(group, _("Interpolate X"), INKSCAPE_ICON("interpolate-rotate-x"));
882 auto rows = create_radio_button(group, _("Interpolate Y"), INKSCAPE_ICON("interpolate-rotate-y"));
883 auto both = create_radio_button(group, _("Interpolate both"), INKSCAPE_ICON("interpolate-rotate-both"));
884 auto none = create_radio_button(group, _("No interpolation"), INKSCAPE_ICON("interpolate-rotate-none"));
885 auto rand = create_radio_button(group, _("Interpolate random"), INKSCAPE_ICON("rotate-random"));
887 both->set_active();
888 } else if (interpolate_rotatex) {
889 cols->set_active();
890 } else if (interpolate_rotatey) {
891 rows->set_active();
892 } else if (random_rotate) {
893 rand->set_active();
894 } else {
895 none->set_active();
896 }
897 cols->set_tooltip_markup(_("Blend rotation from <b>left to right</b> (left column uses original rotation, right column uses new rotation)"));
898 rows->set_tooltip_markup(_("Blend rotation from <b>top to bottom</b> (top row uses original rotation, bottom row uses new rotation)"));
899 both->set_tooltip_markup(_("Blend rotation <b>diagonally</b> (top left tile uses original rotation, bottom right tile uses new rotation)"));
900 none->set_tooltip_markup(_("Uniform rotation"));
901 rand->set_tooltip_markup(_("Random rotation (hit <b>Randomize</b> button to shuffle)"));
902 UI::pack_start(*container, *rows, false, false, 1);
903 UI::pack_start(*container, *cols, false, false, 1);
904 UI::pack_start(*container, *both, false, false, 1);
905 UI::pack_start(*container, *none, false, false, 1);
906 UI::pack_start(*container, *rand, false, false, 1);
907 rand->signal_clicked().connect(sigc::mem_fun(*this, &LPETiling::setRotateRandom));
908 none->signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &LPETiling::setRotateInterpolate), false, false));
909 cols->signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &LPETiling::setRotateInterpolate), true, false));
910 rows->signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &LPETiling::setRotateInterpolate), false, true));
911 both->signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &LPETiling::setRotateInterpolate), true, true));
912 UI::pack_start(*moveend, *container, false, false, 2);
913 } else if (param->param_key == "gapx") {
914 auto const wrapper = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL,0);
915 movestart = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL,0);
916 moveend = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL,0);
917 moveend->set_homogeneous();
918 moveend->set_valign(Gtk::Align::FILL);
919 auto const container = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL,0);
920 Gtk::ToggleButton *group = nullptr;
921 auto normal = create_radio_button(group, _("Normal"), INKSCAPE_ICON("interpolate-scale-none"));
922 auto randx = create_radio_button(group, _("Random"), INKSCAPE_ICON("gap-random-x"));
923 if (random_gap_x) {
924 randx->set_active();
925 } else {
926 normal->set_active();
927 }
928 normal->set_tooltip_markup(_("All horizontal gaps have the same width"));
929 randx->set_tooltip_markup(_("Random horizontal gaps (hit <b>Randomize</b> button to shuffle)"));
930 normal->signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &LPETiling::setGapXMode), false));
931 randx->signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &LPETiling::setGapXMode), true));
932 UI::pack_start(*container, *normal, false, false, 1);
933 UI::pack_start(*container, *randx, false, false, 1);
934 combo->set_margin_end(0);
935 UI::pack_end(*container, *combo, false, false, 1);
936 UI::pack_start(*movestart, *widg, false, false, 2);
937 UI::pack_start(*moveend, *container, false, false, 2);
938 UI::pack_start(*wrapper, *movestart, false, false);
939 UI::pack_start(*wrapper, *moveend, false, false);
940 //bwidg->set_hexpand(true);
941 combo->set_halign(Gtk::Align::END);
942 widg->set_halign(Gtk::Align::START);
943 UI::pack_start(*vbox, *wrapper, true, true);
944 } else if (param->param_key == "gapy") {
945 UI::pack_start(*movestart, *widg, true, true, 2);
946 auto const container = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL,0);
947 Gtk::ToggleButton *group = nullptr;
948 auto normal = create_radio_button(group, _("Normal"), INKSCAPE_ICON("interpolate-scale-none"));
949 auto randy = create_radio_button(group, _("Random"), INKSCAPE_ICON("gap-random-y"));
950 if (random_gap_y) {
951 randy->set_active();
952 } else {
953 normal->set_active();
954 }
955 normal->set_tooltip_markup(_("All vertical gaps have the same height"));
956 randy->set_tooltip_markup(_("Random vertical gaps (hit <b>Randomize</b> button to shuffle)"));
957 normal->signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &LPETiling::setGapYMode), false));
958 randy->signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &LPETiling::setGapYMode), true));
959 UI::pack_start(*container, *normal, false, false, 1);
960 UI::pack_start(*container, *randy, false, false, 1);
961 widg->set_halign(Gtk::Align::START);
962 UI::pack_start(*moveend, *container, false, false, 2);
963 } else if (param->param_key == "mirrortrans"){
964 auto const container = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL,0);
965 auto const containerwraper = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL,0);
966 containerend = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL,0);
967 containerstart = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL,0);
968 UI::pack_start(*container, *containerwraper, false, true);
969 UI::pack_start(*containerwraper, *containerstart, false, true);
970 UI::pack_start(*containerwraper, *containerend, true, true);
971 UI::pack_end(*containerend, *randbutton, true, true, 2);
972 containerend->set_margin_start(8);
973 UI::pack_start(*containerstart, *widg, false, true, 2);
974 container->set_hexpand(false);
975 containerwraper->set_hexpand(false);
976 containerend->set_hexpand(false);
977 containerstart->set_hexpand(false);
978 UI::pack_start(*vbox, *container, false, true, 1);
979 } else if (
980 param->param_key == "split_items" ||
981 param->param_key == "link_styles" ||
982 param->param_key == "shrink_interp")
983 {
984 UI::pack_start(*containerstart, *widg, true, true, 2);
985 widg->set_vexpand(false);
986 widg->set_hexpand(false);
987 widg->set_valign(Gtk::Align::START);
988 widg->set_halign(Gtk::Align::START);
989 } else if (param->param_key == "num_rows") {
990 rowcols = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL,0);
991 UI::pack_start(*rowcols, *widg, false, false);
992 UI::pack_start(*vbox, *rowcols, false, true, 2);
993 } else if (param->param_key == "num_cols") {
994 UI::pack_start(*rowcols, *widg, false, false);
995 } else {
996 UI::pack_start(*vbox, *widg, false, true, 2);
997 }
998
999 if (auto const tip = param->param_getTooltip()) {
1000 widg->set_tooltip_markup(*tip);
1001 } else {
1002 widg->set_tooltip_markup({});
1003 widg->set_has_tooltip(false);
1004 }
1005
1006 if (dynamic_cast<ScalarParam *>(param)) {
1007 scalars.push_back(widg);
1008 }
1009 }
1010
1011 align_widgets(scalars, 5);
1012
1013 return vbox;
1014}
1015
1016void
1017LPETiling::generate_buttons(Gtk::Box * const container, Gtk::ToggleButton *&group, int const pos)
1018{
1019 for (int i = 0; i < 4; i++) {
1020 int const position = (pos * 4) + i;
1021 auto const &result = getMirrorMap(position);
1022 auto const iconname = Glib::ustring::compose("mirroring-%1", result);
1023 auto button = create_radio_button(group, Glib::ustring(), iconname);
1024 if (getActiveMirror(position)) {
1025 _updating = true;
1026 button->set_active();
1027 _updating = false;
1028 }
1029 button->signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &LPETiling::setMirroring),position));
1030 static constexpr int zero = static_cast<gunichar>('0');
1031 Glib::ustring tooltip = result[0] == zero ? "" : "rx+";
1032 tooltip += result[1] == zero ? "" : "ry+";
1033 tooltip += result[2] == zero ? "" : "cx+";
1034 tooltip += result[3] == zero ? "" : "cy+";
1035 if (tooltip.size()) {
1036 tooltip.erase(tooltip.size()-1);
1037 }
1038 button->set_tooltip_markup(tooltip);
1039 button->set_margin_start(1);
1040 UI::pack_start(*container, *button, false, false, 1);
1041 }
1042}
1043
1044Glib::ustring
1046{
1047 Glib::ustring result = "0000";
1048 if (index == 1) {
1049 result = "1000";
1050 } else if (index == 2) {
1051 result = "1100";
1052 } else if (index == 3) {
1053 result = "0100";
1054 } else if (index == 4) {
1055 result = "0011";
1056 } else if (index == 5) {
1057 result = "1011";
1058 } else if (index == 6) {
1059 result = "1111";
1060 } else if (index == 7) {
1061 result = "0111";
1062 } else if (index == 8) {
1063 result = "0010";
1064 } else if (index == 9) {
1065 result = "1010";
1066 } else if (index == 10) {
1067 result = "1110";
1068 } else if (index == 11) {
1069 result = "0110";
1070 } else if (index == 12) {
1071 result = "0001";
1072 } else if (index == 13) {
1073 result = "1001";
1074 } else if (index == 14) {
1075 result = "1101";
1076 } else if (index == 15) {
1077 result = "0101";
1078 }
1079 return result;
1080}
1081
1082bool
1091
1092void
1094{
1095 if (_updating) {
1096 return;
1097 }
1098 _updating = true;
1099 auto const &result = getMirrorMap(index);
1100 static constexpr int zero = static_cast<gunichar>('0');
1101 mirrorrowsx.param_setValue(result[0] == zero ? false : true);
1102 mirrorrowsy.param_setValue(result[1] == zero ? false : true);
1103 mirrorcolsx.param_setValue(result[2] == zero ? false : true);
1104 mirrorcolsy.param_setValue(result[3] == zero ? false : true);
1106 _updating = false;
1107}
1108
1109void
1114void
1119
1120void
1127
1128void
1135
1136void
1143
1144void
1151
1152void
1156}
1157
1158void
1162}
1163
1164void
1166{
1167 if (lpeitem->getAttribute("transform")) {
1168 transformorigin.param_setValue(lpeitem->getAttribute("transform"), true);
1169 } else {
1171 }
1172 lpeversion.param_setValue("1.3.1", true);
1173 legacy = false;
1174 doBeforeEffect(lpeitem);
1175}
1176
1177void
1179{
1180 if (is_load) {
1181 legacy = lpeversion.param_getSVGValue() < "1.3.1";
1182 }
1183 auto transformorigin_str = lpeitem->getAttribute("transform");
1184 if (transformorigin_str) {
1186 auto transformorigin_str = transformorigin.param_getSVGValue();
1188 if (!transformorigin_str.empty()) {
1189 sp_svg_transform_read(transformorigin_str.c_str(), &transformoriginal);
1190 }
1191 } else {
1194 }
1195 //transformoriginal = transformoriginal.withoutTranslation();
1196 using namespace Geom;
1198 random_x.clear();
1199 random_y.clear();
1200 random_s.clear();
1201 random_r.clear();
1202 if (prev_unit != unit.get_abbreviation()) {
1205 gapx.param_set_value(newgapx);
1206 gapy.param_set_value(newgapy);
1209 }
1210 scaleok = (scale + 100) / 100.0;
1213 if (random_rotate && rotate) {
1215 }
1216 if (random_scale && scaleok != 1) {
1217 affinebase *= Geom::Scale(seed.param_get_random_number() * (std::max(scaleok,1.0) - std::min(scaleok,1.0)) + std::min(scaleok,1.0));
1218 }
1219 if (random_gap_x && gapx_unit) {
1221 }
1222 if (random_gap_y && gapy_unit) {
1224 }
1225 if (!split_items && lpesatellites.data().size()) {
1227 }
1228 if (link_styles) {
1229 reset = true;
1230 }
1231 if (split_items && !lpesatellites.data().size()) {
1233 if (lpesatellites.data().size()) {
1235 }
1236 }
1237 if (legacy) {
1238 auto const prev_display_unit = std::move(display_unit);
1240 if (!display_unit.empty() && display_unit != prev_display_unit) {
1241 //_document->getDocumentScale().inverse()
1242 gapx.param_set_value(Inkscape::Util::Quantity::convert(gapx, display_unit.c_str(), prev_display_unit.c_str()));
1243 gapy.param_set_value(Inkscape::Util::Quantity::convert(gapy, display_unit.c_str(), prev_display_unit.c_str()));
1246 }
1249 } else {
1252 }
1255 Geom::Point A = Point(boundingbox_X.min() - (gapx_unit / 2.0), boundingbox_Y.min() - (gapy_unit / 2.0));
1256 Geom::Point B = Point(boundingbox_X.max() + (gapx_unit / 2.0), boundingbox_Y.max() + (gapy_unit / 2.0));
1257 gap_bbox = Geom::OptRect(A,B);
1258 if (!gap_bbox) {
1259 return;
1260 }
1261
1262 double scale_fix = end_scale(scaleok, true);
1263 (*originalbbox) *= Geom::Translate((*originalbbox).midpoint()).inverse() * Geom::Scale(scale_fix) * Geom::Translate((*originalbbox).midpoint());
1265 (*gap_bbox) *= Geom::Translate((*gap_bbox).midpoint()).inverse() * Geom::Scale(scaleok,scaleok) * Geom::Translate((*gap_bbox).midpoint());
1266 (*originalbbox) *= Geom::Translate((*originalbbox).midpoint()).inverse() * Geom::Scale(scaleok,scaleok) * Geom::Translate((*originalbbox).midpoint());
1267 }
1268 original_width = (*gap_bbox).width();
1269 original_height = (*gap_bbox).height();
1270}
1271
1272double
1273LPETiling::end_scale(double scale_fix, bool tomax) const {
1275 scale_fix = 1 + ((scale_fix - 1) * (num_rows + num_cols -1));
1276 } else if (interpolate_scalex) {
1277 scale_fix = 1 + ((scale_fix - 1) * (num_cols -1));
1278 } else if (interpolate_scaley) {
1279 scale_fix = 1 + ((scale_fix - 1) * (num_rows -1));
1280 }
1281 if (tomax && (random_scale || interpolate_scalex || interpolate_scaley)) {
1282 scale_fix = std::max(scale_fix, 1.0);
1283 }
1284 return scale_fix;
1285}
1286
1289{
1290 Geom::PathVector path_out;
1291 FillRuleBool fillrule = fill_nonZero;
1292 if (current_shape->style &&
1295 {
1296 fillrule = (FillRuleBool)fill_oddEven;
1297 }
1298 path_out = doEffect_path_post(path_in, fillrule);
1299 if (_knotholder) {
1301 }
1302 if (split_items) {
1303 return path_out;
1304 } else {
1305 return path_out * transformoriginal.inverse();
1306 }
1307}
1308
1311{
1312 if (!gap_bbox) {
1313 return path_in;
1314 }
1315 Geom::Point center = (*gap_bbox).midpoint() * transformoriginal.inverse();
1316 Geom::PathVector output;
1317 gint counter = 0;
1318 Geom::OptRect prev_bbox;
1319 double gapscalex = 0;
1320 double maxheight = 0;
1321 double maxwidth = 0;
1322 double minheight = std::numeric_limits<double>::max();
1323 Geom::OptRect bbox = path_in.boundsFast();
1324 if (!bbox) {
1325 return path_in;
1326 }
1327 (*bbox) *= transformoriginal;
1328
1329 double posx = ((*gap_bbox).left() - (*bbox).left()) / (*gap_bbox).width();
1330 double factorx = original_width/(*bbox).width();
1331 double factory = original_height/(*bbox).height();
1332 double y[(int)num_cols];
1333 double yset = 0;
1334 double gap[(int)num_cols];
1335 for (int i = 0; i < num_rows; ++i) {
1336 double fracy = 1;
1337 if (num_rows != 1) {
1338 fracy = i/(double)(num_rows - 1);
1339 }
1340 for (int j = 0; j < num_cols; ++j) {
1341 double x = 0;
1342 double fracx = 1;
1343 if (num_cols != 1) {
1344 fracx = j/(double)(num_cols - 1);
1345 }
1347 r = Geom::identity();
1348 Geom::Scale mirror = Geom::Scale(1,1);
1349 bool reverse_pv = false;
1351 gint mx = 1;
1352 gint my = 1;
1353 if (mirrorrowsx && mirrorcolsx) {
1354 mx = (j+i)%2 != 0 ? -1 : 1;
1355 } else {
1356 if (mirrorrowsx) {
1357 mx = i%2 != 0 ? -1 : 1;
1358 } else if (mirrorcolsx) {
1359 mx = j%2 != 0 ? -1 : 1;
1360 }
1361 }
1362 if (mirrorrowsy && mirrorcolsy) {
1363 my = (j+i)%2 != 0 ? -1 : 1;
1364 } else {
1365 if (mirrorrowsy) {
1366 my = i%2 != 0 ? -1 : 1;
1367 } else if (mirrorcolsy) {
1368 my = j%2 != 0 ? -1 : 1;
1369 }
1370 }
1371 mirror = Geom::Scale(mx, my);
1372 reverse_pv = mx * my == -1;
1373 }
1374 if (mirrortrans && interpolate_scalex && i%2 != 0) {
1375 fracx = 1-fracx;
1376 }
1377 double fracyin = fracy;
1378 if (mirrortrans && interpolate_scaley && j%2 != 0) {
1379 fracyin = 1-fracyin;
1380 }
1381 /* if (mirrortrans && interpolate_scaley && interpolate_scalex) {
1382 fract = 1-fract;
1383 } */
1384 double rotatein = rotate;
1386 rotatein = rotatein * (i + j);
1387 } else if (interpolate_rotatex) {
1388 rotatein = rotatein * j;
1389 } else if (interpolate_rotatey) {
1390 rotatein = rotatein * i;
1391 }
1392 if (mirrortrans &&
1393 ((interpolate_rotatex && i%2 != 0) ||
1394 (interpolate_rotatey && j%2 != 0) ||
1396 {
1397 rotatein *=-1;
1398 }
1399 double scalein = 1;
1400 double scalegap = scaleok - scalein;
1402 scalein = (scalegap * (i + j)) + 1;
1403 } else if (interpolate_scalex) {
1404 scalein = (scalegap * j) + 1;
1405 } else if (interpolate_scaley) {
1406 scalein = (scalegap * i) + 1;
1407 } else {
1408 scalein = scaleok;
1409 }
1410
1411 if (random_scale && scaleok != 1.0) {
1412 if (random_s.size() == counter) {
1413 double max = std::max(1.0,scaleok);
1414 double min = std::min(1.0,scaleok);
1415 random_s.emplace_back(seed.param_get_random_number() * (max - min) + min);
1416 }
1417 scalein = random_s[counter];
1418 }
1419 if (random_rotate && rotate) {
1420 if (random_r.size() == counter) {
1422 }
1423 rotatein = random_r[counter];
1424 }
1425 if (random_x.size() == counter) {
1426 if (random_gap_x && gapx_unit && (j || i)) {
1427 random_x.emplace_back((seed.param_get_random_number() * gapx_unit)); // avoid overlapping
1428 } else {
1429 random_x.emplace_back(0);
1430 }
1431 }
1432 if (random_y.size() == counter) {
1433 if (random_gap_y && gapy_unit && (j || i)) {
1434 random_y.emplace_back((seed.param_get_random_number() * gapy_unit)); // avoid overlapping
1435 } else {
1436 random_y.emplace_back(0);
1437 }
1438 }
1439 r *= Geom::Scale(scalein, scalein);
1440 r *= Geom::Rotate::from_degrees(rotatein);
1441
1443 if (reverse_pv) {
1444 output_pv.reverse();
1445 }
1446
1447 output_pv *= Geom::Translate(center).inverse();
1448 output_pv *= r;
1450 output_pv *= Geom::Rotate::from_degrees(rotate);
1451 }
1453 output_pv *= Geom::Scale(scaleok, scaleok);
1454 }
1455 originatrans = r;
1456 output_pv *= Geom::Translate(center);
1457 if (split_items) {
1458 return output_pv;
1459 }
1460 double scale_fix = end_scale(scaleok, true);
1461 double heightrows = original_height * scale_fix;
1462 double widthcols = original_width * scale_fix;
1463 double fixed_heightrows = heightrows;
1464 double fixed_widthcols = widthcols;
1465
1466 if (rotatein && shrink_interp) {
1469 return path_in;
1470 }
1471 if (scaleok != 1.0 && (interpolate_scalex || interpolate_scaley )) {
1472 Geom::OptRect bbox = output_pv.boundsFast();
1473 if (bbox) {
1474 maxheight = std::max(maxheight,(*bbox).height());
1475 maxwidth = std::max(maxwidth,(*bbox).width());
1476 minheight = std::min(minheight,(*bbox).height());
1477 widthcols = std::max(original_width * end_scale(scaleok, false),original_width);
1478 heightrows = std::max(original_height * end_scale(scaleok, false),original_height);
1479 fixed_widthcols = widthcols;
1480 fixed_heightrows = heightrows;
1481 double cx = (*bbox).width();
1482 double cy = (*bbox).height();
1484 heightrows = 0;
1485 widthcols = 0;
1486 double px = 0;
1487 double py = 0;
1488 if (prev_bbox) {
1489 px = (*prev_bbox).width();
1490 py = (*prev_bbox).height();
1491 }
1492 if (interpolate_scalex) {
1493 if (j) {
1494 x = ((cx - ((cx - px) / 2.0))) * factorx;
1495 gapscalex += x;
1496 x = gapscalex;
1497 } else {
1498 x = 0;
1499 gapscalex = 0;
1500 }
1501 } else {
1502 x = (std::max(original_width * end_scale(scaleok, false), original_width) + posx) * j;
1503 }
1504 if (interpolate_scalex && i == 1) {
1505 y[j] = maxheight * factory;
1506 } else if(i == 0) {
1507 y[j] = 0;
1508 }
1509 if (i == 1 && !interpolate_scalex) {
1510 gap[j] = ((cy * factory) - y[j])/2.0;
1511 } else if (i == 0) {
1512 gap[j] = 0;
1513 }
1514 yset = y[j] + (gap[j] * i);
1515 if (interpolate_scaley) {
1516 y[j] += cy * factory;
1517 } else {
1518 y[j] += maxheight * factory;
1519 }
1520 }
1521 prev_bbox = bbox;
1522 }
1523 } else {
1524 y[j] = 0;
1525 }
1526 double xset = x;
1527 xset += widthcols * j;
1528 if (heightrows) {
1529 yset = heightrows * i;
1530 }
1531 double offset_x = 0;
1532 double offset_y = 0;
1533 if (offset != 0) {
1534 if (offset_type && j%2) {
1535 offset_y = fixed_heightrows/(100.0/(double)offset);
1536 }
1537 if (!offset_type && i%2) {
1538 offset_x = fixed_widthcols/(100.0/(double)offset);
1539
1540 }
1541 }
1542 output_pv *= Geom::Translate(center).inverse() * mirror * Geom::Translate(center);
1543 output_pv *= transformoriginal;
1544 output_pv *= Geom::Translate(Geom::Point(xset + offset_x - random_x[counter],yset + offset_y - random_y[counter]));
1545 output.insert(output.end(), output_pv.begin(), output_pv.end());
1546 counter++;
1547 }
1548 }
1549 return output;
1550}
1551
1552void
1553LPETiling::addCanvasIndicators(SPLPEItem const *lpeitem, std::vector<Geom::PathVector> &hp_vec)
1554{
1555 if (!gap_bbox) {
1556 return;
1557 }
1558 using namespace Geom;
1559 hp_vec.clear();
1561 double scale_fix = end_scale(scaleok, true);
1562 hp *= Geom::Translate((*gap_bbox).midpoint()).inverse() * Geom::Scale(scale_fix) * Geom::Translate((*gap_bbox).midpoint());
1563 hp *= transformoriginal.inverse();
1564 Geom::PathVector pathv;
1565 pathv.push_back(hp);
1566 hp_vec.push_back(pathv);
1567}
1568
1569void
1571{
1573 original_bbox(cast<SPLPEItem>(item), false, true);
1574}
1575
1576void
1578{
1579 auto transformorigin_str = lpeitem->getAttribute("transform");
1580 Geom::Affine ontoggle = Geom::identity();
1581 if (transformorigin_str) {
1582 sp_svg_transform_read(transformorigin_str, &ontoggle);
1583 }
1584 ontoggle = ontoggle;
1585 if (is_visible) {
1586 if ( ontoggle == Geom::identity()) {
1588 } else {
1589 ontoggle = ontoggle * hideaffine.inverse() * transformoriginal;
1591 }
1592 } else {
1593 hideaffine = ontoggle;
1594 }
1596}
1597
1598
1599void
1601{
1602 if (keep_paths) {
1604 return;
1605 }
1607}
1608
1610{
1611 _knotholder = knotholder;
1613 e->create(nullptr, item, knotholder, Inkscape::CANVAS_ITEM_CTRL_TYPE_LPE, "LPE:CopiesGapX",
1614 _("<b>Horizontal gaps between tiles</b>: drag to adjust, <b>Shift+click</b> to reset"));
1615 knotholder->add(e);
1616
1618 f->create(nullptr, item, knotholder, Inkscape::CANVAS_ITEM_CTRL_TYPE_LPE, "LPE:CopiesGapY",
1619 _("<b>Vertical gaps between tiles</b>: drag to adjust, <b>Shift+click</b> to reset"));
1620 knotholder->add(f);
1621}
1622
1623namespace CoS {
1624
1625KnotHolderEntityCopyGapX::~KnotHolderEntityCopyGapX()
1626{
1627 LPETiling* lpe = dynamic_cast<LPETiling *>(_effect);
1628 if (lpe) {
1629 lpe->_knotholder = nullptr;
1630 }
1631}
1632
1633KnotHolderEntityCopyGapY::~KnotHolderEntityCopyGapY()
1634{
1635 LPETiling* lpe = dynamic_cast<LPETiling *>(_effect);
1636 if (lpe) {
1637 lpe->_knotholder = nullptr;
1638 }
1639}
1640
1641void KnotHolderEntityCopyGapX::knot_click(guint state)
1642{
1643 if (!(state & GDK_SHIFT_MASK)) {
1644 return;
1645 }
1646
1647 LPETiling* lpe = dynamic_cast<LPETiling *>(_effect);
1648
1649 lpe->gapx.param_set_value(0);
1650 startpos = 0;
1651 sp_lpe_item_update_patheffect(cast<SPLPEItem>(item), false, false);
1652}
1653
1654void KnotHolderEntityCopyGapY::knot_click(guint state)
1655{
1656 if (!(state & GDK_SHIFT_MASK)) {
1657 return;
1658 }
1659
1660 LPETiling* lpe = dynamic_cast<LPETiling *>(_effect);
1661
1662 lpe->gapy.param_set_value(0);
1663 startpos = 0;
1664 sp_lpe_item_update_patheffect(cast<SPLPEItem>(item), false, false);
1665}
1666
1667void KnotHolderEntityCopyGapX::knot_set(Geom::Point const &p, Geom::Point const&/*origin*/, guint state)
1668{
1669 LPETiling* lpe = dynamic_cast<LPETiling *>(_effect);
1670
1671 Geom::Point const s = snap_knot_position(p, state);
1672 if (lpe->originalbbox) {
1673 Geom::Point point = (*lpe->originalbbox).corner(1);
1674 point *= lpe->transformoriginal.inverse();
1675 double value = s[Geom::X] - point[Geom::X];
1676 if (lpe->legacy) {
1677 Glib::ustring doc_unit = SP_ACTIVE_DOCUMENT->getWidth().unit->abbr.c_str();
1678 value = Inkscape::Util::Quantity::convert((value/lpe->end_scale(lpe->scaleok, false)) * 2, doc_unit.c_str(),lpe->unit.get_abbreviation());
1679 } else {
1680 value = Inkscape::Util::Quantity::convert((value/lpe->end_scale(lpe->scaleok, false)) * 2, "px", lpe->unit.get_abbreviation()) * SP_ACTIVE_DOCUMENT->getDocumentScale()[Geom::X];
1681 }
1682 lpe->gapx.param_set_value(value);
1683 lpe->gapx.write_to_SVG();
1684 }
1685}
1686
1687void KnotHolderEntityCopyGapY::knot_set(Geom::Point const &p, Geom::Point const& /*origin*/, guint state)
1688{
1689 LPETiling* lpe = dynamic_cast<LPETiling *>(_effect);
1690
1691 Geom::Point const s = snap_knot_position(p, state);
1692 if (lpe->originalbbox) {
1693 Geom::Point point = (*lpe->originalbbox).corner(3);
1694 point *= lpe->transformoriginal.inverse();
1695 double value = s[Geom::Y] - point[Geom::Y];
1696 if (lpe->legacy) {
1697 Glib::ustring doc_unit = SP_ACTIVE_DOCUMENT->getWidth().unit->abbr.c_str();
1698 value = Inkscape::Util::Quantity::convert((value/lpe->end_scale(lpe->scaleok, false)) * 2, doc_unit.c_str(),lpe->unit.get_abbreviation());
1699 } else {
1700 value = Inkscape::Util::Quantity::convert((value/lpe->end_scale(lpe->scaleok, false)) * 2, "px", lpe->unit.get_abbreviation()) * SP_ACTIVE_DOCUMENT->getDocumentScale()[Geom::X];
1701 }
1702 lpe->gapy.param_set_value(value);
1703 lpe->gapy.write_to_SVG();
1704 }
1705}
1706
1707Geom::Point KnotHolderEntityCopyGapX::knot_get() const
1708{
1709 LPETiling const * lpe = dynamic_cast<LPETiling const*> (_effect);
1711 if (lpe->originalbbox) {
1712 auto bbox = (*lpe->originalbbox);
1713 double value;
1714 if (lpe->legacy) {
1715 Glib::ustring prev_unit = SP_ACTIVE_DOCUMENT->getDisplayUnit()->abbr.c_str();
1716 value = Inkscape::Util::Quantity::convert(lpe->gapx, lpe->unit.get_abbreviation(), prev_unit.c_str());
1717 } else {
1718 value = Inkscape::Util::Quantity::convert(lpe->gapx, lpe->unit.get_abbreviation(), "px") / SP_ACTIVE_DOCUMENT->getDocumentScale()[Geom::X];
1719 }
1720 double scale = lpe->scaleok;
1721 ret = (bbox).corner(1) + Geom::Point((value * lpe->end_scale(scale, false))/2.0,0);
1722 ret *= lpe->transformoriginal.inverse();
1723 }
1724 return ret;
1725}
1726
1727Geom::Point KnotHolderEntityCopyGapY::knot_get() const
1728{
1729 LPETiling const * lpe = dynamic_cast<LPETiling const*> (_effect);
1731 if (lpe->originalbbox) {
1732 auto bbox = (*lpe->originalbbox);
1733 double value;
1734 if (lpe->legacy) {
1735 Glib::ustring prev_unit = SP_ACTIVE_DOCUMENT->getDisplayUnit()->abbr.c_str();
1736 value = Inkscape::Util::Quantity::convert(lpe->gapy, lpe->unit.get_abbreviation(), prev_unit.c_str());
1737 } else {
1738 value = Inkscape::Util::Quantity::convert(lpe->gapy, lpe->unit.get_abbreviation(), "px") / SP_ACTIVE_DOCUMENT->getDocumentScale()[Geom::X];
1739 }
1740 double scale = lpe->scaleok;
1741 ret = (bbox).corner(3) + Geom::Point(0,(value * lpe->end_scale(scale, false))/2.0);
1742 ret *= lpe->transformoriginal.inverse();
1743 }
1744 return ret;
1745}
1746
1747} // namespace CoS
1748
1749} // namespace Inkscape::LivePathEffect
1750
1751/*
1752 Local Variables:
1753 mode:c++
1754 c-file-style:"stroustrup"
1755 c-file-gaps:((innamespace . 0)(inline-open . 0)(case-label . +))
1756 indent-tabs-mode:nil
1757 fill-column:99
1758 End:
1759*/
1760// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
FillRule
Definition LivarotDefs.h:67
@ fill_oddEven
Definition LivarotDefs.h:68
@ fill_nonZero
Definition LivarotDefs.h:69
double scale
Definition aa.cpp:228
Point origin
Definition aa.cpp:227
3x3 matrix representing an affine transformation.
Definition affine.h:70
Affine inverse() const
Compute the inverse matrix.
Definition affine.cpp:388
Affine withoutTranslation() const
Definition affine.h:169
constexpr C min() const
constexpr C max() const
Axis-aligned rectangle that can be empty.
Definition rect.h:203
Sequence of subpaths.
Definition pathvector.h:122
void push_back(Path const &path)
Append a path at the end.
Definition pathvector.h:172
OptRect boundsFast() const
iterator insert(iterator pos, Path const &p)
Definition pathvector.h:179
void reverse(bool reverse_paths=true)
Reverse the direction of paths in the vector.
iterator begin()
Definition pathvector.h:151
iterator end()
Definition pathvector.h:152
Sequence of contiguous curves, aka spline.
Definition path.h:353
Two-dimensional point that doubles as a vector.
Definition point.h:66
static Rotate from_degrees(Coord deg)
Construct a rotation from its angle in degrees.
Definition transforms.h:218
Rotate inverse() const
Definition transforms.h:209
Scaling from the origin.
Definition transforms.h:150
Translation by a vector.
Definition transforms.h:115
Translate inverse() const
Get the inverse translation.
Definition transforms.h:133
std::vector< StorageType > const & data() const
Definition array.h:48
void param_setValue(bool newvalue)
Definition bool.cpp:89
std::vector< Parameter * > param_vector
Definition effect.h:179
void registerParameter(Parameter *param)
Definition effect.cpp:1710
virtual void resetDefaults(SPItem const *item)
Sets all parameters to their default values and writes them to SVG.
Definition effect.cpp:2014
virtual void processObjects(LPEAction lpe_action)
Definition effect.cpp:1416
void original_bbox(SPLPEItem const *lpeitem, bool absolute=false, bool clip_mask=false, Geom::Affine base_transform=Geom::identity())
void param_setValue(Glib::ustring newvalue, bool write=false)
Definition hidden.cpp:71
Glib::ustring param_getSVGValue() const override
Definition hidden.cpp:53
SPItem * toItem(size_t i, bool reset, bool &write)
double end_scale(double scale_fix, bool tomax) const
std::vector< double > random_s
Definition lpe-tiling.h:134
std::vector< double > random_r
Definition lpe-tiling.h:135
std::vector< double > random_y
Definition lpe-tiling.h:133
void doOnVisibilityToggled(SPLPEItem const *) final
Geom::PathVector doEffect_path(Geom::PathVector const &path_in) final
void doAfterEffect(SPLPEItem const *lpeitem, SPCurve *curve) final
Is performed at the end of the LPE only one time per "lpeitem" in paths/shapes is called in middle of...
void generate_buttons(Gtk::Box *container, Gtk::ToggleButton *&group, int pos)
void doOnApply(SPLPEItem const *lpeitem) final
Is performed a single time when the effect is freshly applied to a path.
void cloneD(SPObject *orig, SPObject *dest)
std::vector< double > random_x
Definition lpe-tiling.h:132
void cloneStyle(SPObject *orig, SPObject *dest)
Glib::ustring getMirrorMap(int index)
void addKnotHolderEntities(KnotHolder *knotholder, SPItem *item) final
void addCanvasIndicators(SPLPEItem const *lpeitem, std::vector< Geom::PathVector > &hp_vec) final
Add possible canvas indicators (i.e., helperpaths other than the original path) to hp_vec This functi...
Geom::PathVector doEffect_path_post(Geom::PathVector const &path_in, FillRuleBool fillrule)
void setScaleInterpolate(bool x, bool y)
Inkscape::XML::Node * createPathBase(SPObject *elemref)
Gtk::Widget * newWidget() final
This creates a managed widget.
bool doOnOpen(SPLPEItem const *) final
Is performed on load document or revert If the item is fixed legacy return true.
void setRotateInterpolate(bool x, bool y)
void doOnRemove(SPLPEItem const *) final
LPETiling(LivePathEffectObject *lpeobject)
friend class CoS::KnotHolderEntityCopyGapX
Definition lpe-tiling.h:67
friend class CoS::KnotHolderEntityCopyGapY
Definition lpe-tiling.h:68
void resetDefaults(SPItem const *item) final
Sets all parameters to their default values and writes them to SVG.
void doBeforeEffect(SPLPEItem const *lpeitem) final
Is performed each time before the effect is updated.
SatelliteArrayParam lpesatellites
Definition lpe-tiling.h:94
void param_set_randomsign(bool randomsign)
Definition random.h:36
void param_set_range(gdouble min, gdouble max)
Definition random.cpp:129
void link(SPObject *to, size_t pos=Glib::ustring::npos)
void param_set_range(double min, double max)
void param_set_increments(double step, double page)
const gchar * get_abbreviation() const
Definition unit.cpp:77
Preference storage class.
Definition preferences.h:66
bool getBool(Glib::ustring const &pref_path, bool def=false)
Retrieve a Boolean value.
static Preferences * get()
Access the singleton Preferences object.
Unit const * unit
Definition units.h:95
static double convert(double from_dist, Unit const *from, Unit const *to)
Convert distances.
Definition units.cpp:525
Glib::ustring abbr
Definition units.h:76
Interface for refcounted XML nodes.
Definition node.h:80
void setAttribute(Util::const_char_ptr key, Util::const_char_ptr value)
Change an attribute of this node.
Definition node.cpp:25
virtual char const * attribute(char const *key) const =0
Get the string representation of a node's attribute.
KnotHolderEntity definition.
void create(SPDesktop *desktop, SPItem *item, KnotHolder *parent, Inkscape::CanvasItemCtrlType type=Inkscape::CANVAS_ITEM_CTRL_TYPE_DEFAULT, Glib::ustring const &name="unknown", char const *tip="", uint32_t color=0xffffff00)
void update_knots()
void add(KnotHolderEntity *e)
Inkscape::LivePathEffect::Effect * _effect
Wrapper around a Geom::PathVector object.
Definition curve.h:26
Typed SVG document implementation.
Definition document.h:103
Inkscape::Util::Quantity getWidth() const
Definition document.cpp:857
Inkscape::XML::Document * getReprDoc()
Our Inkscape::XML::Document.
Definition document.h:213
Geom::Scale getDocumentScale(bool computed=true) const
Returns document scale as defined by width/height (in pixels) and viewBox (real world to user-units).
Definition document.cpp:773
Inkscape::Util::Unit const * getDisplayUnit()
guaranteed not to return nullptr
Definition document.cpp:741
Base class for visual SVG elements.
Definition sp-item.h:109
Geom::OptRect geometricBounds(Geom::Affine const &transform=Geom::identity()) const
Get item's geometric bounding box in this item's coordinate system.
Definition sp-item.cpp:920
void doWriteTransform(Geom::Affine const &transform, Geom::Affine const *adv=nullptr, bool compensate=true)
Set a new transform on an object.
Definition sp-item.cpp:1658
SPObject is an abstract base class of all of the document nodes at the SVG document level.
Definition sp-object.h:160
void setAttribute(Inkscape::Util::const_char_ptr key, Inkscape::Util::const_char_ptr value)
SPDocument * document
Definition sp-object.h:188
SPStyle * style
Represents the style properties, whether from presentation attributes, the style attribute,...
Definition sp-object.h:248
SPObject * parent
Definition sp-object.h:189
Inkscape::XML::Node * updateRepr(unsigned int flags=SP_OBJECT_WRITE_EXT)
Updates the object's repr based on the object's state.
void deleteObject(bool propagate, bool propagate_descendants)
Deletes an object, unparenting it from its parent.
Inkscape::XML::Node * getRepr()
Returns the XML representation of tree.
char const * getAttribute(char const *name) const
SPObject * appendChildRepr(Inkscape::XML::Node *repr)
Append repr as child of this object.
SPObject * nthChild(unsigned index)
void addChild(Inkscape::XML::Node *child, Inkscape::XML::Node *prev=nullptr)
void requestDisplayUpdate(unsigned int flags)
Queues an deferred update of this object's display.
T< SPAttr::FILL_RULE, SPIEnum< SPWindRule > > fill_rule
fill-rule: 0 nonzero, 1 evenodd
Definition style.h:244
Css & result
double c[8][4]
Geom::Point orig
constexpr Coord infinity()
Get a value representing infinity.
Definition coord.h:88
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
Geom::PathVector pathv_to_linear_and_cubic_beziers(Geom::PathVector const &pathv)
Definition geom.cpp:588
Specific geometry functions for Inkscape, not provided my lib2geom.
Gtk::Image * sp_get_icon_image(Glib::ustring const &icon_name, int size)
Icon Loader.
Macro for icon names used in Inkscape.
SPItem * item
Path intersection graph.
LPE <tiling> implementation.
double offset
Glib::ustring label
Various utility functions.
Definition affine.h:22
MultiDegree< n > max(MultiDegree< n > const &p, MultiDegree< n > const &q)
Returns the maximal degree appearing in the two arguments for each variables.
Definition sbasisN.h:158
Affine identity()
Create an identity matrix.
Definition affine.h:210
Piecewise< SBasis > min(SBasis const &f, SBasis const &g)
Return the more negative of the two functions pointwise.
static R & release(R &r)
Decrements the reference count of a anchored object.
Live Path Effects code.
Gtk::ToggleButton * create_radio_button(Gtk::ToggleButton *&group, const Glib::ustring &tooltip, const Glib::ustring &icon_name)
void align_widgets(const std::vector< Gtk::Widget * > &widgets, int spinbutton_chars=7)
void pack_end(Gtk::Box &box, Gtk::Widget &child, bool const expand, bool const fill, unsigned const padding)
Adds child to box, packed with reference to the end of box.
Definition pack.cpp:153
std::vector< Gtk::Widget * > get_children(Gtk::Widget &widget)
Get a vector of the widgetʼs children, from get_first_child() through each get_next_sibling().
Definition util.cpp:155
void pack_start(Gtk::Box &box, Gtk::Widget &child, bool const expand, bool const fill, unsigned const padding)
Adds child to box, packed with reference to the start of box.
Definition pack.cpp:141
Glib::ustring format_classic(T const &... args)
@ CANVAS_ITEM_CTRL_TYPE_LPE
static cairo_user_data_key_t key
static gint counter
Definition box3d.cpp:39
Helpers for using Gtk::Boxes, encapsulating large changes between GTK3 & GTK4.
Path intersection.
Singleton class to access the preferences file in a convenient way.
Ocnode * child[8]
Definition quantize.cpp:33
Conversion between SBasis and Bezier basis polynomials.
Geom::Affine sp_item_transform_repr(SPItem *item)
Find out the inverse of previous transform of an item (from its repr)
Definition sp-item.cpp:1500
void sp_lpe_item_update_patheffect(SPLPEItem *lpeitem, bool wholetree, bool write, bool with_satellites)
Calls any registered handlers for the update_patheffect action.
Interface for XML documents.
Definition document.h:43
virtual Node * createElement(char const *name)=0
Definition curve.h:24
@ SP_WIND_RULE_EVENODD
Definition style-enums.h:26
SPStyle - a style object for SPItem objects.
bool sp_svg_transform_read(gchar const *str, Geom::Affine *transform)
std::string sp_svg_transform_write(Geom::Affine const &transform)
static void sp_svg_write_path(Inkscape::SVG::PathString &str, Geom::Path const &p, bool normalize=false)
Definition svg-path.cpp:109
int index
unsigned int gunichar