1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Authors:
4  *   Lauris Kaplinski <lauris@kaplinski.com>
5  *   bulia byak <buliabyak@users.sf.net>
6  *   Johan Engelen <j.b.c.engelen@ewi.utwente.nl>
7  *   Abhishek Sharma
8  *   Jon A. Cruz <jon@joncruz.org>
9  *
10  * Copyright (C) 2001-2006 authors
11  * Copyright (C) 2001 Ximian, Inc.
12  *
13  * Released under GNU GPL v2+, read the file 'COPYING' for more information.
14  */
15 
16 #include "sp-item.h"
17 
18 #include <glibmm/i18n.h>
19 
20 #include "bad-uri-exception.h"
21 #include "svg/svg.h"
22 #include "print.h"
23 #include "display/drawing-item.h"
24 #include "attributes.h"
25 #include "document.h"
26 
27 #include "inkscape.h"
28 #include "desktop.h"
29 #include "gradient-chemistry.h"
30 #include "conn-avoid-ref.h"
31 #include "conditions.h"
32 #include "filter-chemistry.h"
33 
34 #include "sp-clippath.h"
35 #include "sp-desc.h"
36 #include "sp-guide.h"
37 #include "sp-hatch.h"
38 #include "sp-item-rm-unsatisfied-cns.h"
39 #include "sp-mask.h"
40 #include "sp-pattern.h"
41 #include "sp-rect.h"
42 #include "sp-root.h"
43 #include "sp-switch.h"
44 #include "sp-text.h"
45 #include "sp-textpath.h"
46 #include "sp-title.h"
47 #include "sp-use.h"
48 
49 #include "style.h"
50 #include "uri.h"
51 
52 
53 #include "util/find-last-if.h"
54 
55 #include "extract-uri.h"
56 
57 #include "live_effects/lpeobject.h"
58 #include "live_effects/effect.h"
59 #include "live_effects/lpeobject-reference.h"
60 
61 #include "util/units.h"
62 
63 #define noSP_ITEM_DEBUG_IDLE
64 
65 //#define OBJECT_TRACE
66 
67 static SPItemView*          sp_item_view_list_remove(SPItemView     *list,
68                                                      SPItemView     *view);
69 
70 
SPItem()71 SPItem::SPItem() : SPObject() {
72 
73     sensitive = TRUE;
74     bbox_valid = FALSE;
75 
76     _highlightColor = nullptr;
77 
78     transform_center_x = 0;
79     transform_center_y = 0;
80 
81     freeze_stroke_width = false;
82     _is_evaluated = true;
83     _evaluated_status = StatusUnknown;
84 
85     transform = Geom::identity();
86     // doc_bbox = Geom::OptRect();
87 
88     display = nullptr;
89 
90     clip_ref = nullptr;
91     mask_ref = nullptr;
92 
93     style->signal_fill_ps_changed.connect(sigc::bind(sigc::ptr_fun(fill_ps_ref_changed), this));
94     style->signal_stroke_ps_changed.connect(sigc::bind(sigc::ptr_fun(stroke_ps_ref_changed), this));
95 
96     avoidRef = nullptr;
97 }
98 
99 SPItem::~SPItem() = default;
100 
getClipObject() const101 SPClipPath *SPItem::getClipObject() const { return clip_ref ? clip_ref->getObject() : nullptr; }
102 
getMaskObject() const103 SPMask *SPItem::getMaskObject() const { return mask_ref ? mask_ref->getObject() : nullptr; }
104 
getMaskRef()105 SPMaskReference &SPItem::getMaskRef()
106 {
107     if (!mask_ref) {
108         mask_ref = new SPMaskReference(this);
109         mask_ref->changedSignal().connect(sigc::bind(sigc::ptr_fun(mask_ref_changed), this));
110     }
111 
112     return *mask_ref;
113 }
114 
getClipRef()115 SPClipPathReference &SPItem::getClipRef()
116 {
117     if (!clip_ref) {
118         clip_ref = new SPClipPathReference(this);
119         clip_ref->changedSignal().connect(sigc::bind(sigc::ptr_fun(clip_ref_changed), this));
120     }
121 
122     return *clip_ref;
123 }
124 
getAvoidRef()125 SPAvoidRef &SPItem::getAvoidRef()
126 {
127     if (!avoidRef) {
128         avoidRef = new SPAvoidRef(this);
129     }
130     return *avoidRef;
131 }
132 
isVisibleAndUnlocked() const133 bool SPItem::isVisibleAndUnlocked() const {
134     return (!isHidden() && !isLocked());
135 }
136 
isVisibleAndUnlocked(unsigned display_key) const137 bool SPItem::isVisibleAndUnlocked(unsigned display_key) const {
138     return (!isHidden(display_key) && !isLocked());
139 }
140 
isLocked() const141 bool SPItem::isLocked() const {
142     for (SPObject const *o = this; o != nullptr; o = o->parent) {
143         SPItem const *item = dynamic_cast<SPItem const *>(o);
144         if (item && !(item->sensitive)) {
145             return true;
146         }
147     }
148     return false;
149 }
150 
setLocked(bool locked)151 void SPItem::setLocked(bool locked) {
152     setAttribute("sodipodi:insensitive",
153                  ( locked ? "1" : nullptr ));
154     updateRepr();
155     document->_emitModified();
156 }
157 
isHidden() const158 bool SPItem::isHidden() const {
159     if (!isEvaluated())
160         return true;
161     return style->display.computed == SP_CSS_DISPLAY_NONE;
162 }
163 
setHidden(bool hide)164 void SPItem::setHidden(bool hide) {
165     style->display.set = TRUE;
166     style->display.value = ( hide ? SP_CSS_DISPLAY_NONE : SP_CSS_DISPLAY_INLINE );
167     style->display.computed = style->display.value;
168     style->display.inherit = FALSE;
169     updateRepr();
170 }
171 
isHidden(unsigned display_key) const172 bool SPItem::isHidden(unsigned display_key) const {
173     if (!isEvaluated())
174         return true;
175     for ( SPItemView *view(display) ; view ; view = view->next ) {
176         if ( view->key == display_key ) {
177             g_assert(view->arenaitem != nullptr);
178             for ( Inkscape::DrawingItem *arenaitem = view->arenaitem ;
179                   arenaitem ; arenaitem = arenaitem->parent() )
180             {
181                 if (!arenaitem->visible()) {
182                     return true;
183                 }
184             }
185             return false;
186         }
187     }
188     return true;
189 }
190 
isHighlightSet() const191 bool SPItem::isHighlightSet() const {
192     return _highlightColor != nullptr;
193 }
194 
highlight_color() const195 guint32 SPItem::highlight_color() const {
196     if (_highlightColor)
197     {
198         return atoi(_highlightColor) | 0x00000000;
199     }
200     else {
201         SPItem const *item = dynamic_cast<SPItem const *>(parent);
202         if (parent && (parent != this) && item)
203         {
204             return item->highlight_color();
205         }
206         else
207         {
208             static Inkscape::Preferences *prefs = Inkscape::Preferences::get();
209             return prefs->getInt("/tools/nodes/highlight_color", 0xff0000ff);
210         }
211     }
212 }
213 
setEvaluated(bool evaluated)214 void SPItem::setEvaluated(bool evaluated) {
215     _is_evaluated = evaluated;
216     _evaluated_status = StatusSet;
217 }
218 
resetEvaluated()219 void SPItem::resetEvaluated() {
220     if ( StatusCalculated == _evaluated_status ) {
221         _evaluated_status = StatusUnknown;
222         bool oldValue = _is_evaluated;
223         if ( oldValue != isEvaluated() ) {
224             requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
225         }
226     } if ( StatusSet == _evaluated_status ) {
227         SPSwitch *switchItem = dynamic_cast<SPSwitch *>(parent);
228         if (switchItem) {
229             switchItem->resetChildEvaluated();
230         }
231     }
232 }
233 
isEvaluated() const234 bool SPItem::isEvaluated() const {
235     if ( StatusUnknown == _evaluated_status ) {
236         _is_evaluated = sp_item_evaluate(this);
237         _evaluated_status = StatusCalculated;
238     }
239     return _is_evaluated;
240 }
241 
isExplicitlyHidden() const242 bool SPItem::isExplicitlyHidden() const
243 {
244     return (style->display.set
245             && style->display.value == SP_CSS_DISPLAY_NONE);
246 }
247 
setExplicitlyHidden(bool val)248 void SPItem::setExplicitlyHidden(bool val) {
249     style->display.set = val;
250     style->display.value = ( val ? SP_CSS_DISPLAY_NONE : SP_CSS_DISPLAY_INLINE );
251     style->display.computed = style->display.value;
252     updateRepr();
253 }
254 
setCenter(Geom::Point const & object_centre)255 void SPItem::setCenter(Geom::Point const &object_centre) {
256     document->ensureUpToDate();
257 
258     // Copied from DocumentProperties::onDocUnitChange()
259     gdouble viewscale = 1.0;
260     Geom::Rect vb = this->document->getRoot()->viewBox;
261     if ( !vb.hasZeroArea() ) {
262         gdouble viewscale_w = this->document->getWidth().value("px") / vb.width();
263         gdouble viewscale_h = this->document->getHeight().value("px")/ vb.height();
264         viewscale = std::min(viewscale_h, viewscale_w);
265     }
266 
267     // FIXME this is seriously wrong
268     Geom::OptRect bbox = desktopGeometricBounds();
269     if (bbox) {
270         // object centre is document coordinates (i.e. in pixels), so we need to consider the viewbox
271         // to translate to user units; transform_center_x/y is in user units
272         transform_center_x = (object_centre[Geom::X] - bbox->midpoint()[Geom::X])/viewscale;
273         if (Geom::are_near(transform_center_x, 0)) // rounding error
274             transform_center_x = 0;
275         transform_center_y = (object_centre[Geom::Y] - bbox->midpoint()[Geom::Y])/viewscale;
276         if (Geom::are_near(transform_center_y, 0)) // rounding error
277             transform_center_y = 0;
278     }
279 }
280 
281 void
unsetCenter()282 SPItem::unsetCenter() {
283     transform_center_x = 0;
284     transform_center_y = 0;
285 }
286 
isCenterSet() const287 bool SPItem::isCenterSet() const {
288     return (transform_center_x != 0 || transform_center_y != 0);
289 }
290 
291 // Get the item's transformation center in desktop coordinates (i.e. in pixels)
getCenter() const292 Geom::Point SPItem::getCenter() const {
293     document->ensureUpToDate();
294 
295     // Copied from DocumentProperties::onDocUnitChange()
296     gdouble viewscale = 1.0;
297     Geom::Rect vb = this->document->getRoot()->viewBox;
298     if ( !vb.hasZeroArea() ) {
299         gdouble viewscale_w = this->document->getWidth().value("px") / vb.width();
300         gdouble viewscale_h = this->document->getHeight().value("px")/ vb.height();
301         viewscale = std::min(viewscale_h, viewscale_w);
302     }
303 
304     // FIXME this is seriously wrong
305     Geom::OptRect bbox = desktopGeometricBounds();
306     if (bbox) {
307         // transform_center_x/y are stored in user units, so we have to take the viewbox into account to translate to document coordinates
308         return bbox->midpoint() + Geom::Point (transform_center_x*viewscale, transform_center_y*viewscale);
309 
310     } else {
311         return Geom::Point(0, 0); // something's wrong!
312     }
313 
314 }
315 
316 void
scaleCenter(Geom::Scale const & sc)317 SPItem::scaleCenter(Geom::Scale const &sc) {
318     transform_center_x *= sc[Geom::X];
319     transform_center_y *= sc[Geom::Y];
320 }
321 
322 namespace {
323 
is_item(SPObject const & object)324 bool is_item(SPObject const &object) {
325     return dynamic_cast<SPItem const *>(&object) != nullptr;
326 }
327 
328 }
329 
raiseToTop()330 void SPItem::raiseToTop() {
331     using Inkscape::Algorithms::find_last_if;
332 
333     auto topmost = find_last_if(++parent->children.iterator_to(*this), parent->children.end(), &is_item);
334     if (topmost != parent->children.end()) {
335         getRepr()->parent()->changeOrder( getRepr(), topmost->getRepr() );
336     }
337 }
338 
raiseOne()339 bool SPItem::raiseOne() {
340     auto next_higher = std::find_if(++parent->children.iterator_to(*this), parent->children.end(), &is_item);
341     if (next_higher != parent->children.end()) {
342         Inkscape::XML::Node *ref = next_higher->getRepr();
343         getRepr()->parent()->changeOrder(getRepr(), ref);
344         return true;
345     }
346     return false;
347 }
348 
lowerOne()349 bool SPItem::lowerOne() {
350     using Inkscape::Algorithms::find_last_if;
351 
352     auto next_lower = find_last_if(parent->children.begin(), parent->children.iterator_to(*this), &is_item);
353     if (next_lower != parent->children.iterator_to(*this)) {
354         Inkscape::XML::Node *ref = nullptr;
355         if (next_lower != parent->children.begin()) {
356             next_lower--;
357             ref = next_lower->getRepr();
358         }
359         getRepr()->parent()->changeOrder(getRepr(), ref);
360         return true;
361     }
362     return false;
363 }
364 
lowerToBottom()365 void SPItem::lowerToBottom() {
366     auto bottom = std::find_if(parent->children.begin(), parent->children.iterator_to(*this), &is_item);
367     if (bottom != parent->children.iterator_to(*this)) {
368         Inkscape::XML::Node *ref = nullptr;
369         if (bottom != parent->children.begin()) {
370             bottom--;
371             ref = bottom->getRepr();
372         }
373         parent->getRepr()->changeOrder(getRepr(), ref);
374     }
375 }
376 
moveTo(SPItem * target,bool intoafter)377 void SPItem::moveTo(SPItem *target, bool intoafter) {
378 
379     Inkscape::XML::Node *target_ref = ( target ? target->getRepr() : nullptr );
380     Inkscape::XML::Node *our_ref = getRepr();
381 
382     if (!target_ref) {
383         // Assume move to the "first" in the top node, find the top node
384         intoafter = false;
385         SPObject* bottom = this->document->getObjectByRepr(our_ref->root())->firstChild();
386         while(!dynamic_cast<SPItem*>(bottom->getNext())){
387         	bottom = bottom->getNext();
388         }
389         target_ref = bottom->getRepr();
390     }
391 
392     if (target_ref == our_ref) {
393         // Move to ourself ignore
394         return;
395     }
396 
397     if (intoafter) {
398         // Move this inside of the target at the end
399         our_ref->parent()->removeChild(our_ref);
400         target_ref->addChild(our_ref, nullptr);
401     } else if (target_ref->parent() != our_ref->parent()) {
402         // Change in parent, need to remove and add
403         our_ref->parent()->removeChild(our_ref);
404         target_ref->parent()->addChild(our_ref, target_ref);
405     } else {
406         // Same parent, just move
407         our_ref->parent()->changeOrder(our_ref, target_ref);
408     }
409 }
410 
build(SPDocument * document,Inkscape::XML::Node * repr)411 void SPItem::build(SPDocument *document, Inkscape::XML::Node *repr) {
412 	SPItem* object = this;
413 
414     object->readAttr(SPAttr::STYLE);
415     object->readAttr(SPAttr::TRANSFORM);
416     object->readAttr(SPAttr::CLIP_PATH);
417     object->readAttr(SPAttr::MASK);
418     object->readAttr(SPAttr::SODIPODI_INSENSITIVE);
419     object->readAttr(SPAttr::TRANSFORM_CENTER_X);
420     object->readAttr(SPAttr::TRANSFORM_CENTER_Y);
421     object->readAttr(SPAttr::CONNECTOR_AVOID);
422     object->readAttr(SPAttr::CONNECTION_POINTS);
423     object->readAttr(SPAttr::INKSCAPE_HIGHLIGHT_COLOR);
424 
425     SPObject::build(document, repr);
426 }
427 
release()428 void SPItem::release() {
429 	SPItem* item = this;
430 
431     // Note: do this here before the clip_ref is deleted, since calling
432     // ensureUpToDate() for triggered routing may reference
433     // the deleted clip_ref.
434     delete item->avoidRef;
435 
436     // we do NOT disconnect from the changed signal of those before deletion.
437     // The destructor will call *_ref_changed with NULL as the new value,
438     // which will cause the hide() function to be called.
439     delete item->clip_ref;
440     delete item->mask_ref;
441 
442     SPObject::release();
443 
444     SPPaintServer *fill_ps = style->getFillPaintServer();
445     SPPaintServer *stroke_ps = style->getStrokePaintServer();
446     while (item->display) {
447         if (fill_ps) {
448             fill_ps->hide(item->display->arenaitem->key());
449         }
450         if (stroke_ps) {
451             stroke_ps->hide(item->display->arenaitem->key());
452         }
453         item->display = sp_item_view_list_remove(item->display, item->display);
454     }
455 
456     //item->_transformed_signal.~signal();
457 }
458 
set(SPAttr key,gchar const * value)459 void SPItem::set(SPAttr key, gchar const* value) {
460     SPItem *item = this;
461     SPItem* object = item;
462 
463     switch (key) {
464         case SPAttr::TRANSFORM: {
465             Geom::Affine t;
466             if (value && sp_svg_transform_read(value, &t)) {
467                 item->set_item_transform(t);
468             } else {
469                 item->set_item_transform(Geom::identity());
470             }
471             break;
472         }
473         case SPAttr::CLIP_PATH: {
474             auto uri = extract_uri(value);
475             if (!uri.empty() || item->clip_ref) {
476                 item->getClipRef().try_attach(uri.c_str());
477             }
478             break;
479         }
480         case SPAttr::MASK: {
481             auto uri = extract_uri(value);
482             if (!uri.empty() || item->mask_ref) {
483                 item->getMaskRef().try_attach(uri.c_str());
484             }
485             break;
486         }
487         case SPAttr::SODIPODI_INSENSITIVE:
488         {
489             item->sensitive = !value;
490             for (SPItemView *v = item->display; v != nullptr; v = v->next) {
491                 v->arenaitem->setSensitive(item->sensitive);
492             }
493             break;
494         }
495         case SPAttr::INKSCAPE_HIGHLIGHT_COLOR:
496         {
497             g_free(item->_highlightColor);
498             if (value) {
499                 item->_highlightColor = g_strdup(value);
500             } else {
501                 item->_highlightColor = nullptr;
502             }
503             break;
504         }
505         case SPAttr::CONNECTOR_AVOID:
506             if (value || item->avoidRef) {
507                 item->getAvoidRef().setAvoid(value);
508             }
509             break;
510         case SPAttr::TRANSFORM_CENTER_X:
511             if (value) {
512                 item->transform_center_x = g_strtod(value, nullptr);
513             } else {
514                 item->transform_center_x = 0;
515             }
516             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
517             break;
518         case SPAttr::TRANSFORM_CENTER_Y:
519             if (value) {
520                 item->transform_center_y = g_strtod(value, nullptr);
521                 item->transform_center_y *= -document->yaxisdir();
522             } else {
523                 item->transform_center_y = 0;
524             }
525             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
526             break;
527         case SPAttr::SYSTEM_LANGUAGE:
528         case SPAttr::REQUIRED_FEATURES:
529         case SPAttr::REQUIRED_EXTENSIONS:
530             {
531                 item->resetEvaluated();
532                 // pass to default handler
533             }
534         default:
535             if (SP_ATTRIBUTE_IS_CSS(key)) {
536                 // Propergate the property change to all clones
537                 style->readFromObject(object);
538                 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
539             } else {
540                 SPObject::set(key, value);
541             }
542             break;
543     }
544 }
545 
clip_ref_changed(SPObject * old_clip,SPObject * clip,SPItem * item)546 void SPItem::clip_ref_changed(SPObject *old_clip, SPObject *clip, SPItem *item)
547 {
548     item->bbox_valid = FALSE; // force a re-evaluation
549     if (old_clip) {
550         SPItemView *v;
551         /* Hide clippath */
552         for (v = item->display; v != nullptr; v = v->next) {
553             SPClipPath *oldPath = dynamic_cast<SPClipPath *>(old_clip);
554             g_assert(oldPath != nullptr);
555             oldPath->hide(v->arenaitem->key());
556         }
557     }
558     SPClipPath *clipPath = dynamic_cast<SPClipPath *>(clip);
559     if (clipPath) {
560         Geom::OptRect bbox = item->geometricBounds();
561         for (SPItemView *v = item->display; v != nullptr; v = v->next) {
562             if (!v->arenaitem->key()) {
563                 v->arenaitem->setKey(SPItem::display_key_new(3));
564             }
565             Inkscape::DrawingItem *ai = clipPath->show(
566                                                v->arenaitem->drawing(),
567                                                v->arenaitem->key());
568             v->arenaitem->setClip(ai);
569             clipPath->setBBox(v->arenaitem->key(), bbox);
570             clip->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
571         }
572     }
573     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
574 }
575 
mask_ref_changed(SPObject * old_mask,SPObject * mask,SPItem * item)576 void SPItem::mask_ref_changed(SPObject *old_mask, SPObject *mask, SPItem *item)
577 {
578     item->bbox_valid = FALSE; // force a re-evaluation
579     if (old_mask) {
580         /* Hide mask */
581         for (SPItemView *v = item->display; v != nullptr; v = v->next) {
582             SPMask *maskItem = dynamic_cast<SPMask *>(old_mask);
583             g_assert(maskItem != nullptr);
584             maskItem->sp_mask_hide(v->arenaitem->key());
585         }
586     }
587     SPMask *maskItem = dynamic_cast<SPMask *>(mask);
588     if (maskItem) {
589         Geom::OptRect bbox = item->geometricBounds();
590         for (SPItemView *v = item->display; v != nullptr; v = v->next) {
591             if (!v->arenaitem->key()) {
592                 v->arenaitem->setKey(SPItem::display_key_new(3));
593             }
594             Inkscape::DrawingItem *ai = maskItem->sp_mask_show(
595                                            v->arenaitem->drawing(),
596                                            v->arenaitem->key());
597             v->arenaitem->setMask(ai);
598             maskItem->sp_mask_set_bbox(v->arenaitem->key(), bbox);
599             mask->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
600         }
601     }
602 }
603 
fill_ps_ref_changed(SPObject * old_ps,SPObject * ps,SPItem * item)604 void SPItem::fill_ps_ref_changed(SPObject *old_ps, SPObject *ps, SPItem *item) {
605     SPPaintServer *old_fill_ps = dynamic_cast<SPPaintServer *>(old_ps);
606     if (old_fill_ps) {
607         for (SPItemView *v =item->display; v != nullptr; v = v->next) {
608             old_fill_ps->hide(v->arenaitem->key());
609         }
610     }
611 
612     SPPaintServer *new_fill_ps = dynamic_cast<SPPaintServer *>(ps);
613     if (new_fill_ps) {
614         Geom::OptRect bbox = item->geometricBounds();
615         for (SPItemView *v = item->display; v != nullptr; v = v->next) {
616             if (!v->arenaitem->key()) {
617                 v->arenaitem->setKey(SPItem::display_key_new(3));
618             }
619             Inkscape::DrawingPattern *pi = new_fill_ps->show(
620                     v->arenaitem->drawing(), v->arenaitem->key(), bbox);
621             v->arenaitem->setFillPattern(pi);
622             if (pi) {
623                 new_fill_ps->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
624             }
625         }
626     }
627 }
628 
stroke_ps_ref_changed(SPObject * old_ps,SPObject * ps,SPItem * item)629 void SPItem::stroke_ps_ref_changed(SPObject *old_ps, SPObject *ps, SPItem *item) {
630     SPPaintServer *old_stroke_ps = dynamic_cast<SPPaintServer *>(old_ps);
631     if (old_stroke_ps) {
632         for (SPItemView *v =item->display; v != nullptr; v = v->next) {
633             old_stroke_ps->hide(v->arenaitem->key());
634         }
635     }
636 
637     SPPaintServer *new_stroke_ps = dynamic_cast<SPPaintServer *>(ps);
638     if (new_stroke_ps) {
639         Geom::OptRect bbox = item->geometricBounds();
640         for (SPItemView *v = item->display; v != nullptr; v = v->next) {
641             if (!v->arenaitem->key()) {
642                 v->arenaitem->setKey(SPItem::display_key_new(3));
643             }
644             Inkscape::DrawingPattern *pi = new_stroke_ps->show(
645                     v->arenaitem->drawing(), v->arenaitem->key(), bbox);
646             v->arenaitem->setStrokePattern(pi);
647             if (pi) {
648                 new_stroke_ps->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
649             }
650         }
651     }
652 }
653 
update(SPCtx * ctx,guint flags)654 void SPItem::update(SPCtx* ctx, guint flags) {
655 
656     SPItemCtx const *ictx = reinterpret_cast<SPItemCtx const *>(ctx);
657 
658     // Any of the modifications defined in sp-object.h might change bbox,
659     // so we invalidate it unconditionally
660     bbox_valid = FALSE;
661 
662     viewport = ictx->viewport; // Cache viewport
663 
664     if (flags & (SP_OBJECT_CHILD_MODIFIED_FLAG |
665                  SP_OBJECT_MODIFIED_FLAG |
666                  SP_OBJECT_STYLE_MODIFIED_FLAG) ) {
667         if (flags & SP_OBJECT_MODIFIED_FLAG) {
668             for (SPItemView *v = display; v != nullptr; v = v->next) {
669                 v->arenaitem->setTransform(transform);
670             }
671         }
672 
673         SPClipPath *clip_path = clip_ref ? clip_ref->getObject() : nullptr;
674         SPMask *mask = mask_ref ? mask_ref->getObject() : nullptr;
675 
676         if ( clip_path || mask ) {
677             Geom::OptRect bbox = geometricBounds();
678             if (clip_path) {
679                 for (SPItemView *v = display; v != nullptr; v = v->next) {
680                     clip_path->setBBox(v->arenaitem->key(), bbox);
681                 }
682             }
683             if (mask) {
684                 for (SPItemView *v = display; v != nullptr; v = v->next) {
685                     mask->sp_mask_set_bbox(v->arenaitem->key(), bbox);
686                 }
687             }
688         }
689 
690         if (flags & SP_OBJECT_STYLE_MODIFIED_FLAG) {
691             for (SPItemView *v = display; v != nullptr; v = v->next) {
692                 v->arenaitem->setOpacity(SP_SCALE24_TO_FLOAT(style->opacity.value));
693                 v->arenaitem->setAntialiasing(style->shape_rendering.computed == SP_CSS_SHAPE_RENDERING_CRISPEDGES ? 0 : 2);
694                 v->arenaitem->setIsolation( style->isolation.value );
695                 v->arenaitem->setBlendMode( style->mix_blend_mode.value );
696                 v->arenaitem->setVisible(!isHidden());
697             }
698         }
699     }
700     /* Update bounding box in user space, used for filter and objectBoundingBox units */
701     if (style->filter.set && display) {
702         Geom::OptRect item_bbox = geometricBounds();
703         SPItemView *itemview = display;
704         do {
705             if (itemview->arenaitem)
706                 itemview->arenaitem->setItemBounds(item_bbox);
707         } while ( (itemview = itemview->next) );
708     }
709 
710     // Update libavoid with item geometry (for connector routing).
711     if (avoidRef && document) {
712         avoidRef->handleSettingChange();
713     }
714 }
715 
modified(unsigned int)716 void SPItem::modified(unsigned int /*flags*/)
717 {
718 #ifdef OBJECT_TRACE
719     objectTrace( "SPItem::modified" );
720     objectTrace( "SPItem::modified", false );
721 #endif
722 }
723 
write(Inkscape::XML::Document * xml_doc,Inkscape::XML::Node * repr,guint flags)724 Inkscape::XML::Node* SPItem::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) {
725     SPItem *item = this;
726     SPItem* object = item;
727 
728     // in the case of SP_OBJECT_WRITE_BUILD, the item should always be newly created,
729     // so we need to add any children from the underlying object to the new repr
730     if (flags & SP_OBJECT_WRITE_BUILD) {
731         std::vector<Inkscape::XML::Node *>l;
732         for (auto& child: object->children) {
733             if (dynamic_cast<SPTitle *>(&child) || dynamic_cast<SPDesc *>(&child)) {
734                 Inkscape::XML::Node *crepr = child.updateRepr(xml_doc, nullptr, flags);
735                 if (crepr) {
736                     l.push_back(crepr);
737                 }
738             }
739         }
740         for (auto i = l.rbegin(); i!= l.rend(); ++i) {
741             repr->addChild(*i, nullptr);
742             Inkscape::GC::release(*i);
743         }
744     } else {
745         for (auto& child: object->children) {
746             if (dynamic_cast<SPTitle *>(&child) || dynamic_cast<SPDesc *>(&child)) {
747                 child.updateRepr(flags);
748             }
749         }
750     }
751 
752     repr->setAttributeOrRemoveIfEmpty("transform", sp_svg_transform_write(item->transform));
753 
754     if (flags & SP_OBJECT_WRITE_EXT) {
755         repr->setAttribute("sodipodi:insensitive", ( item->sensitive ? nullptr : "true" ));
756         if (item->transform_center_x != 0)
757             sp_repr_set_svg_double (repr, "inkscape:transform-center-x", item->transform_center_x);
758         else
759             repr->removeAttribute("inkscape:transform-center-x");
760         if (item->transform_center_y != 0) {
761             auto y = item->transform_center_y;
762             y *= -document->yaxisdir();
763             sp_repr_set_svg_double (repr, "inkscape:transform-center-y", y);
764         } else
765             repr->removeAttribute("inkscape:transform-center-y");
766     }
767 
768     if (item->clip_ref){
769         if (item->clip_ref->getObject()) {
770             auto value = item->clip_ref->getURI()->cssStr();
771             repr->setAttributeOrRemoveIfEmpty("clip-path", value);
772         }
773     }
774     if (item->mask_ref){
775         if (item->mask_ref->getObject()) {
776             auto value = item->mask_ref->getURI()->cssStr();
777             repr->setAttributeOrRemoveIfEmpty("mask", value);
778         }
779     }
780     if (item->_highlightColor){
781         repr->setAttribute("inkscape:highlight-color", item->_highlightColor);
782     } else {
783         repr->removeAttribute("inkscape:highlight-color");
784     }
785 
786     SPObject::write(xml_doc, repr, flags);
787 
788     return repr;
789 }
790 
791 // CPPIFY: make pure virtual
bbox(Geom::Affine const &,SPItem::BBoxType) const792 Geom::OptRect SPItem::bbox(Geom::Affine const & /*transform*/, SPItem::BBoxType /*type*/) const {
793 	//throw;
794 	return Geom::OptRect();
795 }
796 
geometricBounds(Geom::Affine const & transform) const797 Geom::OptRect SPItem::geometricBounds(Geom::Affine const &transform) const
798 {
799     Geom::OptRect bbox;
800 
801     // call the subclass method
802     // CPPIFY
803     //bbox = this->bbox(transform, SPItem::GEOMETRIC_BBOX);
804     bbox = const_cast<SPItem*>(this)->bbox(transform, SPItem::GEOMETRIC_BBOX);
805 
806     return bbox;
807 }
808 
visualBounds(Geom::Affine const & transform,bool wfilter,bool wclip,bool wmask) const809 Geom::OptRect SPItem::visualBounds(Geom::Affine const &transform, bool wfilter, bool wclip, bool wmask) const
810 {
811     using Geom::X;
812     using Geom::Y;
813 
814     Geom::OptRect bbox;
815 
816 
817     SPFilter *filter = style ? style->getFilter() : nullptr;
818     if (filter && wfilter) {
819         // call the subclass method
820     	// CPPIFY
821     	//bbox = this->bbox(Geom::identity(), SPItem::VISUAL_BBOX);
822     	bbox = const_cast<SPItem*>(this)->bbox(Geom::identity(), SPItem::GEOMETRIC_BBOX); // see LP Bug 1229971
823 
824         // default filer area per the SVG spec:
825         SVGLength x, y, w, h;
826         Geom::Point minp, maxp;
827         x.set(SVGLength::PERCENT, -0.10, 0);
828         y.set(SVGLength::PERCENT, -0.10, 0);
829         w.set(SVGLength::PERCENT, 1.20, 0);
830         h.set(SVGLength::PERCENT, 1.20, 0);
831 
832         // if area is explicitly set, override:
833         if (filter->x._set)
834             x = filter->x;
835         if (filter->y._set)
836             y = filter->y;
837         if (filter->width._set)
838             w = filter->width;
839         if (filter->height._set)
840             h = filter->height;
841 
842         double len_x = bbox ? bbox->width() : 0;
843         double len_y = bbox ? bbox->height() : 0;
844 
845         x.update(12, 6, len_x);
846         y.update(12, 6, len_y);
847         w.update(12, 6, len_x);
848         h.update(12, 6, len_y);
849 
850         if (filter->filterUnits == SP_FILTER_UNITS_OBJECTBOUNDINGBOX && bbox) {
851             minp[X] = bbox->left() + x.computed * (x.unit == SVGLength::PERCENT ? 1.0 : len_x);
852             maxp[X] = minp[X] + w.computed * (w.unit == SVGLength::PERCENT ? 1.0 : len_x);
853             minp[Y] = bbox->top() + y.computed * (y.unit == SVGLength::PERCENT ? 1.0 : len_y);
854             maxp[Y] = minp[Y] + h.computed * (h.unit == SVGLength::PERCENT ? 1.0 : len_y);
855         } else if (filter->filterUnits == SP_FILTER_UNITS_USERSPACEONUSE) {
856             minp[X] = x.computed;
857             maxp[X] = minp[X] + w.computed;
858             minp[Y] = y.computed;
859             maxp[Y] = minp[Y] + h.computed;
860         }
861         bbox = Geom::OptRect(minp, maxp);
862         *bbox *= transform;
863     } else {
864         // call the subclass method
865     	// CPPIFY
866     	//bbox = this->bbox(transform, SPItem::VISUAL_BBOX);
867     	bbox = const_cast<SPItem*>(this)->bbox(transform, SPItem::VISUAL_BBOX);
868     }
869     if (clip_ref && clip_ref->getObject() && wclip) {
870         SPItem *ownerItem = dynamic_cast<SPItem *>(clip_ref->getOwner());
871         g_assert(ownerItem != nullptr);
872         ownerItem->bbox_valid = FALSE;  // LP Bug 1349018
873         bbox.intersectWith(clip_ref->getObject()->geometricBounds(transform));
874     }
875     if (mask_ref && mask_ref->getObject() && wmask) {
876         bbox_valid = false;  // LP Bug 1349018
877         bbox.intersectWith(mask_ref->getObject()->visualBounds(transform));
878     }
879 
880     return bbox;
881 }
882 
bounds(BBoxType type,Geom::Affine const & transform) const883 Geom::OptRect SPItem::bounds(BBoxType type, Geom::Affine const &transform) const
884 {
885     if (type == GEOMETRIC_BBOX) {
886         return geometricBounds(transform);
887     } else {
888         return visualBounds(transform);
889     }
890 }
891 
documentPreferredBounds() const892 Geom::OptRect SPItem::documentPreferredBounds() const
893 {
894     if (Inkscape::Preferences::get()->getInt("/tools/bounding_box") == 0) {
895         return documentBounds(SPItem::VISUAL_BBOX);
896     } else {
897         return documentBounds(SPItem::GEOMETRIC_BBOX);
898     }
899 }
900 
901 
902 
documentGeometricBounds() const903 Geom::OptRect SPItem::documentGeometricBounds() const
904 {
905     return geometricBounds(i2doc_affine());
906 }
907 
documentVisualBounds() const908 Geom::OptRect SPItem::documentVisualBounds() const
909 {
910     if (!bbox_valid) {
911         doc_bbox = visualBounds(i2doc_affine());
912         bbox_valid = true;
913     }
914     return doc_bbox;
915 }
documentBounds(BBoxType type) const916 Geom::OptRect SPItem::documentBounds(BBoxType type) const
917 {
918     if (type == GEOMETRIC_BBOX) {
919         return documentGeometricBounds();
920     } else {
921         return documentVisualBounds();
922     }
923 }
924 
desktopGeometricBounds() const925 Geom::OptRect SPItem::desktopGeometricBounds() const
926 {
927     return geometricBounds(i2dt_affine());
928 }
929 
desktopVisualBounds() const930 Geom::OptRect SPItem::desktopVisualBounds() const
931 {
932     Geom::OptRect ret = documentVisualBounds();
933     if (ret) {
934         *ret *= document->doc2dt();
935     }
936     return ret;
937 }
938 
desktopPreferredBounds() const939 Geom::OptRect SPItem::desktopPreferredBounds() const
940 {
941     if (Inkscape::Preferences::get()->getInt("/tools/bounding_box") == 0) {
942         return desktopBounds(SPItem::VISUAL_BBOX);
943     } else {
944         return desktopBounds(SPItem::GEOMETRIC_BBOX);
945     }
946 }
947 
desktopBounds(BBoxType type) const948 Geom::OptRect SPItem::desktopBounds(BBoxType type) const
949 {
950     if (type == GEOMETRIC_BBOX) {
951         return desktopGeometricBounds();
952     } else {
953         return desktopVisualBounds();
954     }
955 }
956 
pos_in_parent() const957 unsigned int SPItem::pos_in_parent() const {
958     g_assert(parent != nullptr);
959     g_assert(SP_IS_OBJECT(parent));
960 
961     unsigned int pos = 0;
962 
963     for (auto& iter: parent->children) {
964         if (&iter == this) {
965             return pos;
966         }
967 
968         if (dynamic_cast<SPItem *>(&iter)) {
969             pos++;
970         }
971     }
972 
973     g_assert_not_reached();
974     return 0;
975 }
976 
977 // CPPIFY: make pure virtual, see below!
snappoints(std::vector<Inkscape::SnapCandidatePoint> &,Inkscape::SnapPreferences const *) const978 void SPItem::snappoints(std::vector<Inkscape::SnapCandidatePoint> & /*p*/, Inkscape::SnapPreferences const */*snapprefs*/) const {
979 	//throw;
980 }
981     /* This will only be called if the derived class doesn't override this.
982      * see for example sp_genericellipse_snappoints in sp-ellipse.cpp
983      * We don't know what shape we could be dealing with here, so we'll just
984      * do nothing
985      */
986 
getSnappoints(std::vector<Inkscape::SnapCandidatePoint> & p,Inkscape::SnapPreferences const * snapprefs) const987 void SPItem::getSnappoints(std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs) const
988 {
989     // Get the snappoints of the item
990 	// CPPIFY
991 	//this->snappoints(p, snapprefs);
992 	const_cast<SPItem*>(this)->snappoints(p, snapprefs);
993 
994     // Get the snappoints at the item's center
995     if (snapprefs != nullptr && snapprefs->isTargetSnappable(Inkscape::SNAPTARGET_ROTATION_CENTER)) {
996         p.emplace_back(getCenter(), Inkscape::SNAPSOURCE_ROTATION_CENTER, Inkscape::SNAPTARGET_ROTATION_CENTER);
997     }
998 
999     // Get the snappoints of clipping paths and mask, if any
1000     std::list<SPObject const *> clips_and_masks;
1001 
1002     if (clip_ref) clips_and_masks.push_back(clip_ref->getObject());
1003     if (mask_ref) clips_and_masks.push_back(mask_ref->getObject());
1004 
1005     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1006     for (std::list<SPObject const *>::const_iterator o = clips_and_masks.begin(); o != clips_and_masks.end(); ++o) {
1007         if (*o) {
1008             // obj is a group object, the children are the actual clippers
1009             for(auto& child: (*o)->children) {
1010                 SPItem *item = dynamic_cast<SPItem *>(const_cast<SPObject*>(&child));
1011                 if (item) {
1012                     std::vector<Inkscape::SnapCandidatePoint> p_clip_or_mask;
1013                     // Please note the recursive call here!
1014                     item->getSnappoints(p_clip_or_mask, snapprefs);
1015                     // Take into account the transformation of the item being clipped or masked
1016                     for (const auto & p_orig : p_clip_or_mask) {
1017                         // All snappoints are in desktop coordinates, but the item's transformation is
1018                         // in document coordinates. Hence the awkward construction below
1019                         Geom::Point pt = desktop->dt2doc(p_orig.getPoint()) * i2dt_affine();
1020                         p.emplace_back(pt, p_orig.getSourceType(), p_orig.getTargetType());
1021                     }
1022                 }
1023             }
1024         }
1025     }
1026 }
1027 
1028 // CPPIFY: make pure virtual
print(SPPrintContext *)1029 void SPItem::print(SPPrintContext* /*ctx*/) {
1030 	//throw;
1031 }
1032 
invoke_print(SPPrintContext * ctx)1033 void SPItem::invoke_print(SPPrintContext *ctx)
1034 {
1035     if ( !isHidden() ) {
1036     	if (!transform.isIdentity() || style->opacity.value != SP_SCALE24_MAX) {
1037 			ctx->bind(transform, SP_SCALE24_TO_FLOAT(style->opacity.value));
1038 			this->print(ctx);
1039 			ctx->release();
1040     	} else {
1041     		this->print(ctx);
1042     	}
1043     }
1044 }
1045 
displayName() const1046 const char* SPItem::displayName() const {
1047     return _("Object");
1048 }
1049 
description() const1050 gchar* SPItem::description() const {
1051     return g_strdup("");
1052 }
1053 
detailedDescription() const1054 gchar *SPItem::detailedDescription() const {
1055         gchar* s = g_strdup_printf("<b>%s</b> %s",
1056                     this->displayName(), this->description());
1057 
1058 	if (s && clip_ref && clip_ref->getObject()) {
1059 		gchar *snew = g_strdup_printf (_("%s; <i>clipped</i>"), s);
1060 		g_free (s);
1061 		s = snew;
1062 	}
1063 
1064 	if (s && mask_ref && mask_ref->getObject()) {
1065 		gchar *snew = g_strdup_printf (_("%s; <i>masked</i>"), s);
1066 		g_free (s);
1067 		s = snew;
1068 	}
1069 
1070 	if ( style && style->filter.href && style->filter.href->getObject() ) {
1071 		const gchar *label = style->filter.href->getObject()->label();
1072 		gchar *snew = nullptr;
1073 
1074 		if (label) {
1075 			snew = g_strdup_printf (_("%s; <i>filtered (%s)</i>"), s, _(label));
1076 		} else {
1077 			snew = g_strdup_printf (_("%s; <i>filtered</i>"), s);
1078 		}
1079 
1080 		g_free (s);
1081 		s = snew;
1082 	}
1083 
1084 	return s;
1085 }
1086 
isFiltered() const1087 bool SPItem::isFiltered() const {
1088 	return (style && style->filter.href && style->filter.href->getObject());
1089 }
1090 
1091 
isInMask() const1092 SPObject* SPItem::isInMask() const {
1093     SPObject* parent = this->parent;
1094     while (parent && !dynamic_cast<SPMask *>(parent)) {
1095         parent = parent->parent;
1096     }
1097     return parent;
1098 }
1099 
isInClipPath() const1100 SPObject* SPItem::isInClipPath() const {
1101     SPObject* parent = this->parent;
1102     while (parent && !dynamic_cast<SPClipPath *>(parent)) {
1103         parent = parent->parent;
1104     }
1105     return parent;
1106 }
1107 
display_key_new(unsigned numkeys)1108 unsigned SPItem::display_key_new(unsigned numkeys)
1109 {
1110     static unsigned dkey = 0;
1111 
1112     dkey += numkeys;
1113 
1114     return dkey - numkeys;
1115 }
1116 
1117 // CPPIFY: make pure virtual
show(Inkscape::Drawing &,unsigned int,unsigned int)1118 Inkscape::DrawingItem* SPItem::show(Inkscape::Drawing& /*drawing*/, unsigned int /*key*/, unsigned int /*flags*/) {
1119 	//throw;
1120 	return nullptr;
1121 }
1122 
invoke_show(Inkscape::Drawing & drawing,unsigned key,unsigned flags)1123 Inkscape::DrawingItem *SPItem::invoke_show(Inkscape::Drawing &drawing, unsigned key, unsigned flags)
1124 {
1125     Inkscape::DrawingItem *ai = nullptr;
1126 
1127     ai = this->show(drawing, key, flags);
1128 
1129     if (ai != nullptr) {
1130         Geom::OptRect item_bbox = geometricBounds();
1131 
1132         display = sp_item_view_new_prepend(display, this, flags, key, ai);
1133         ai->setTransform(transform);
1134         ai->setOpacity(SP_SCALE24_TO_FLOAT(style->opacity.value));
1135         ai->setIsolation( style->isolation.value );
1136         ai->setBlendMode( style->mix_blend_mode.value );
1137         //ai->setCompositeOperator( style->composite_op.value );
1138         ai->setVisible(!isHidden());
1139         ai->setSensitive(sensitive);
1140         if (clip_ref && clip_ref->getObject()) {
1141             SPClipPath *cp = clip_ref->getObject();
1142 
1143             if (!display->arenaitem->key()) {
1144                 display->arenaitem->setKey(display_key_new(3));
1145             }
1146             int clip_key = display->arenaitem->key();
1147 
1148             // Show and set clip
1149             Inkscape::DrawingItem *ac = cp->show(drawing, clip_key);
1150             ai->setClip(ac);
1151 
1152             // Update bbox, in case the clip uses bbox units
1153             cp->setBBox(clip_key, item_bbox);
1154             cp->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1155         }
1156         if (mask_ref && mask_ref->getObject()) {
1157             SPMask *mask = mask_ref->getObject();
1158 
1159             if (!display->arenaitem->key()) {
1160                 display->arenaitem->setKey(display_key_new(3));
1161             }
1162             int mask_key = display->arenaitem->key();
1163 
1164             // Show and set mask
1165             Inkscape::DrawingItem *ac = mask->sp_mask_show(drawing, mask_key);
1166             ai->setMask(ac);
1167 
1168             // Update bbox, in case the mask uses bbox units
1169             mask->sp_mask_set_bbox(mask_key, item_bbox);
1170             mask->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1171         }
1172 
1173         SPPaintServer *fill_ps = style->getFillPaintServer();
1174         if (fill_ps) {
1175             if (!display->arenaitem->key()) {
1176                 display->arenaitem->setKey(display_key_new(3));
1177             }
1178             int fill_key = display->arenaitem->key();
1179 
1180             Inkscape::DrawingPattern *ap = fill_ps->show(drawing, fill_key, item_bbox);
1181             ai->setFillPattern(ap);
1182             if (ap) {
1183                 fill_ps->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1184             }
1185         }
1186         SPPaintServer *stroke_ps = style->getStrokePaintServer();
1187         if (stroke_ps) {
1188             if (!display->arenaitem->key()) {
1189                 display->arenaitem->setKey(display_key_new(3));
1190             }
1191             int stroke_key = display->arenaitem->key();
1192 
1193             Inkscape::DrawingPattern *ap = stroke_ps->show(drawing, stroke_key, item_bbox);
1194             ai->setStrokePattern(ap);
1195             if (ap) {
1196                 stroke_ps->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1197             }
1198         }
1199         ai->setItem(this);
1200         ai->setItemBounds(geometricBounds());
1201     }
1202 
1203     return ai;
1204 }
1205 
1206 // CPPIFY: make pure virtual
hide(unsigned int)1207 void SPItem::hide(unsigned int /*key*/) {
1208 	//throw;
1209 }
1210 
invoke_hide(unsigned key)1211 void SPItem::invoke_hide(unsigned key)
1212 {
1213     this->hide(key);
1214 
1215     SPItemView *ref = nullptr;
1216     SPItemView *v = display;
1217     while (v != nullptr) {
1218         SPItemView *next = v->next;
1219         if (v->key == key) {
1220             if (clip_ref && clip_ref->getObject()) {
1221                 (clip_ref->getObject())->hide(v->arenaitem->key());
1222                 v->arenaitem->setClip(nullptr);
1223             }
1224             if (mask_ref && mask_ref->getObject()) {
1225                 mask_ref->getObject()->sp_mask_hide(v->arenaitem->key());
1226                 v->arenaitem->setMask(nullptr);
1227             }
1228             SPPaintServer *fill_ps = style->getFillPaintServer();
1229             if (fill_ps) {
1230                 fill_ps->hide(v->arenaitem->key());
1231             }
1232             SPPaintServer *stroke_ps = style->getStrokePaintServer();
1233             if (stroke_ps) {
1234                 stroke_ps->hide(v->arenaitem->key());
1235             }
1236             if (!ref) {
1237                 display = v->next;
1238             } else {
1239                 ref->next = v->next;
1240             }
1241             delete v->arenaitem;
1242             g_free(v);
1243         } else {
1244             ref = v;
1245         }
1246         v = next;
1247     }
1248 }
1249 
1250 // Adjusters
1251 
adjust_pattern(Geom::Affine const & postmul,bool set,PaintServerTransform pt)1252 void SPItem::adjust_pattern(Geom::Affine const &postmul, bool set, PaintServerTransform pt)
1253 {
1254     bool fill = (pt == TRANSFORM_FILL || pt == TRANSFORM_BOTH);
1255     if (fill && style && (style->fill.isPaintserver())) {
1256         SPObject *server = style->getFillPaintServer();
1257         SPPattern *serverPatt = dynamic_cast<SPPattern *>(server);
1258         if ( serverPatt ) {
1259             SPPattern *pattern = serverPatt->clone_if_necessary(this, "fill");
1260             pattern->transform_multiply(postmul, set);
1261         }
1262     }
1263 
1264     bool stroke = (pt == TRANSFORM_STROKE || pt == TRANSFORM_BOTH);
1265     if (stroke && style && (style->stroke.isPaintserver())) {
1266         SPObject *server = style->getStrokePaintServer();
1267         SPPattern *serverPatt = dynamic_cast<SPPattern *>(server);
1268         if ( serverPatt ) {
1269             SPPattern *pattern = serverPatt->clone_if_necessary(this, "stroke");
1270             pattern->transform_multiply(postmul, set);
1271         }
1272     }
1273 }
1274 
adjust_hatch(Geom::Affine const & postmul,bool set,PaintServerTransform pt)1275 void SPItem::adjust_hatch(Geom::Affine const &postmul, bool set, PaintServerTransform pt)
1276 {
1277     bool fill = (pt == TRANSFORM_FILL || pt == TRANSFORM_BOTH);
1278     if (fill && style && (style->fill.isPaintserver())) {
1279         SPObject *server = style->getFillPaintServer();
1280         SPHatch *serverHatch = dynamic_cast<SPHatch *>(server);
1281         if (serverHatch) {
1282             SPHatch *hatch = serverHatch->clone_if_necessary(this, "fill");
1283             hatch->transform_multiply(postmul, set);
1284         }
1285     }
1286 
1287     bool stroke = (pt == TRANSFORM_STROKE || pt == TRANSFORM_BOTH);
1288     if (stroke && style && (style->stroke.isPaintserver())) {
1289         SPObject *server = style->getStrokePaintServer();
1290         SPHatch *serverHatch = dynamic_cast<SPHatch *>(server);
1291         if (serverHatch) {
1292             SPHatch *hatch = serverHatch->clone_if_necessary(this, "stroke");
1293             hatch->transform_multiply(postmul, set);
1294         }
1295     }
1296 }
1297 
adjust_gradient(Geom::Affine const & postmul,bool set)1298 void SPItem::adjust_gradient( Geom::Affine const &postmul, bool set )
1299 {
1300     if ( style && style->fill.isPaintserver() ) {
1301         SPPaintServer *server = style->getFillPaintServer();
1302         SPGradient *serverGrad = dynamic_cast<SPGradient *>(server);
1303         if ( serverGrad ) {
1304 
1305             /**
1306              * \note Bbox units for a gradient are generally a bad idea because
1307              * with them, you cannot preserve the relative position of the
1308              * object and its gradient after rotation or skew. So now we
1309              * convert them to userspace units which are easy to keep in sync
1310              * just by adding the object's transform to gradientTransform.
1311              * \todo FIXME: convert back to bbox units after transforming with
1312              * the item, so as to preserve the original units.
1313              */
1314             SPGradient *gradient = sp_gradient_convert_to_userspace( serverGrad, this, "fill" );
1315 
1316             sp_gradient_transform_multiply( gradient, postmul, set );
1317         }
1318     }
1319 
1320     if ( style && style->stroke.isPaintserver() ) {
1321         SPPaintServer *server = style->getStrokePaintServer();
1322         SPGradient *serverGrad = dynamic_cast<SPGradient *>(server);
1323         if ( serverGrad ) {
1324             SPGradient *gradient = sp_gradient_convert_to_userspace( serverGrad, this, "stroke");
1325             sp_gradient_transform_multiply( gradient, postmul, set );
1326         }
1327     }
1328 }
1329 
adjust_stroke(gdouble ex)1330 void SPItem::adjust_stroke( gdouble ex )
1331 {
1332     if (freeze_stroke_width) {
1333         return;
1334     }
1335 
1336     SPStyle *style = this->style;
1337 
1338     if (style && !Geom::are_near(ex, 1.0, Geom::EPSILON)) {
1339         style->stroke_width.computed *= ex;
1340         style->stroke_width.set = TRUE;
1341 
1342         if ( !style->stroke_dasharray.values.empty() ) {
1343             for (auto & value : style->stroke_dasharray.values) {
1344                 value.value    *= ex;
1345                 value.computed *= ex;
1346             }
1347             style->stroke_dashoffset.value    *= ex;
1348             style->stroke_dashoffset.computed *= ex;
1349         }
1350 
1351         updateRepr();
1352     }
1353 }
1354 
1355 /**
1356  * Find out the inverse of previous transform of an item (from its repr)
1357  */
sp_item_transform_repr(SPItem * item)1358 Geom::Affine sp_item_transform_repr (SPItem *item)
1359 {
1360     Geom::Affine t_old(Geom::identity());
1361     gchar const *t_attr = item->getRepr()->attribute("transform");
1362     if (t_attr) {
1363         Geom::Affine t;
1364         if (sp_svg_transform_read(t_attr, &t)) {
1365             t_old = t;
1366         }
1367     }
1368 
1369     return t_old;
1370 }
1371 
1372 
adjust_stroke_width_recursive(double expansion)1373 void SPItem::adjust_stroke_width_recursive(double expansion)
1374 {
1375     adjust_stroke (expansion);
1376 
1377 // A clone's child is the ghost of its original - we must not touch it, skip recursion
1378     if ( !dynamic_cast<SPUse *>(this) ) {
1379         for (auto& o: children) {
1380             SPItem *item = dynamic_cast<SPItem *>(&o);
1381             if (item) {
1382                 item->adjust_stroke_width_recursive(expansion);
1383             }
1384         }
1385     }
1386 }
1387 
freeze_stroke_width_recursive(bool freeze)1388 void SPItem::freeze_stroke_width_recursive(bool freeze)
1389 {
1390     freeze_stroke_width = freeze;
1391 
1392 // A clone's child is the ghost of its original - we must not touch it, skip recursion
1393     if ( !dynamic_cast<SPUse *>(this) ) {
1394         for (auto& o: children) {
1395             SPItem *item = dynamic_cast<SPItem *>(&o);
1396             if (item) {
1397                 item->freeze_stroke_width_recursive(freeze);
1398             }
1399         }
1400     }
1401 }
1402 
1403 /**
1404  * Recursively adjust rx and ry of rects.
1405  */
1406 static void
sp_item_adjust_rects_recursive(SPItem * item,Geom::Affine advertized_transform)1407 sp_item_adjust_rects_recursive(SPItem *item, Geom::Affine advertized_transform)
1408 {
1409     SPRect *rect = dynamic_cast<SPRect *>(item);
1410     if (rect) {
1411     	rect->compensateRxRy(advertized_transform);
1412     }
1413 
1414     for(auto& o: item->children) {
1415         SPItem *itm = dynamic_cast<SPItem *>(&o);
1416         if (itm) {
1417             sp_item_adjust_rects_recursive(itm, advertized_transform);
1418         }
1419     }
1420 }
1421 
adjust_paint_recursive(Geom::Affine advertized_transform,Geom::Affine t_ancestors,PaintServerType type)1422 void SPItem::adjust_paint_recursive(Geom::Affine advertized_transform, Geom::Affine t_ancestors, PaintServerType type)
1423 {
1424 // _Before_ full pattern/gradient transform: t_paint * t_item * t_ancestors
1425 // _After_ full pattern/gradient transform: t_paint_new * t_item * t_ancestors * advertised_transform
1426 // By equating these two expressions we get t_paint_new = t_paint * paint_delta, where:
1427     Geom::Affine t_item = sp_item_transform_repr (this);
1428     Geom::Affine paint_delta = t_item * t_ancestors * advertized_transform * t_ancestors.inverse() * t_item.inverse();
1429 
1430 // Within text, we do not fork gradients, and so must not recurse to avoid double compensation;
1431 // also we do not recurse into clones, because a clone's child is the ghost of its original -
1432 // we must not touch it
1433     if (!(dynamic_cast<SPText *>(this) || dynamic_cast<SPUse *>(this))) {
1434         for (auto& o: children) {
1435             SPItem *item = dynamic_cast<SPItem *>(&o);
1436             if (item) {
1437                 // At the level of the transformed item, t_ancestors is identity;
1438                 // below it, it is the accumulated chain of transforms from this level to the top level
1439                 item->adjust_paint_recursive(advertized_transform, t_item * t_ancestors, type);
1440             }
1441         }
1442     }
1443 
1444 // We recursed into children first, and are now adjusting this object second;
1445 // this is so that adjustments in a tree are done from leaves up to the root,
1446 // and paintservers on leaves inheriting their values from ancestors could adjust themselves properly
1447 // before ancestors themselves are adjusted, probably differently (bug 1286535)
1448 
1449     switch (type) {
1450         case PATTERN: {
1451             adjust_pattern(paint_delta);
1452             break;
1453         }
1454         case HATCH: {
1455             adjust_hatch(paint_delta);
1456             break;
1457         }
1458         default: {
1459             adjust_gradient(paint_delta);
1460         }
1461     }
1462 }
1463 
1464 // CPPIFY:: make pure virtual?
1465 // Not all SPItems must necessarily have a set transform method!
set_transform(Geom::Affine const & transform)1466 Geom::Affine SPItem::set_transform(Geom::Affine const &transform) {
1467 //	throw;
1468 	return transform;
1469 }
1470 
doWriteTransform(Geom::Affine const & transform,Geom::Affine const * adv,bool compensate)1471 void SPItem::doWriteTransform(Geom::Affine const &transform, Geom::Affine const *adv, bool compensate)
1472 {
1473     // calculate the relative transform, if not given by the adv attribute
1474     Geom::Affine advertized_transform;
1475     if (adv != nullptr) {
1476         advertized_transform = *adv;
1477     } else {
1478         advertized_transform = sp_item_transform_repr (this).inverse() * transform;
1479     }
1480 
1481     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1482     if (compensate) {
1483         // recursively compensating for stroke scaling will not always work, because it can be scaled to zero or infinite
1484         // from which we cannot ever recover by applying an inverse scale; therefore we temporarily block any changes
1485         // to the strokewidth in such a case instead, and unblock these after the transformation
1486         // (as reported in https://bugs.launchpad.net/inkscape/+bug/825840/comments/4)
1487         if (!prefs->getBool("/options/transform/stroke", true)) {
1488             double const expansion = 1. / advertized_transform.descrim();
1489             if (expansion < 1e-9 || expansion > 1e9) {
1490                 freeze_stroke_width_recursive(true);
1491                 // This will only work if the item has a set_transform method (in this method adjust_stroke() will be called)
1492                 // We will still have to apply the inverse scaling to other items, not having a set_transform method
1493                 // such as ellipses and stars
1494                 // PS: We cannot use this freeze_stroke_width_recursive() trick in all circumstances. For example, it will
1495                 // break pasting objects within their group (because in such a case the transformation of the group will affect
1496                 // the strokewidth, and has to be compensated for. See https://bugs.launchpad.net/inkscape/+bug/959223/comments/10)
1497             } else {
1498                 adjust_stroke_width_recursive(expansion);
1499             }
1500         }
1501 
1502         // recursively compensate rx/ry of a rect if requested
1503         if (!prefs->getBool("/options/transform/rectcorners", true)) {
1504             sp_item_adjust_rects_recursive(this, advertized_transform);
1505         }
1506 
1507         // recursively compensate pattern fill if it's not to be transformed
1508         if (!prefs->getBool("/options/transform/pattern", true)) {
1509             adjust_paint_recursive(advertized_transform.inverse(), Geom::identity(), PATTERN);
1510         }
1511         if (!prefs->getBool("/options/transform/hatch", true)) {
1512             adjust_paint_recursive(advertized_transform.inverse(), Geom::identity(), HATCH);
1513         }
1514 
1515         /// \todo FIXME: add the same else branch as for gradients below, to convert patterns to userSpaceOnUse as well
1516         /// recursively compensate gradient fill if it's not to be transformed
1517         if (!prefs->getBool("/options/transform/gradient", true)) {
1518             adjust_paint_recursive(advertized_transform.inverse(), Geom::identity(), GRADIENT);
1519         } else {
1520             // this converts the gradient/pattern fill/stroke, if any, to userSpaceOnUse; we need to do
1521             // it here _before_ the new transform is set, so as to use the pre-transform bbox
1522             adjust_paint_recursive(Geom::identity(), Geom::identity(), GRADIENT);
1523         }
1524 
1525     } // endif(compensate)
1526 
1527     gint preserve = prefs->getBool("/options/preservetransform/value", false);
1528     Geom::Affine transform_attr (transform);
1529 
1530     // CPPIFY: check this code.
1531     // If onSetTransform is not overridden, CItem::onSetTransform will return the transform it was given as a parameter.
1532     // onSetTransform cannot be pure due to the fact that not all visible Items are transformable.
1533     SPLPEItem * lpeitem = SP_LPE_ITEM(this);
1534     if (lpeitem) {
1535         lpeitem->notifyTransform(transform);
1536     }
1537     if ( // run the object's set_transform (i.e. embed transform) only if:
1538         (dynamic_cast<SPText *>(this) && firstChild() && dynamic_cast<SPTextPath *>(firstChild())) ||
1539              (!preserve && // user did not chose to preserve all transforms
1540              (!clip_ref || !clip_ref->getObject()) && // the object does not have a clippath
1541              (!mask_ref || !mask_ref->getObject()) && // the object does not have a mask
1542              !(!transform.isTranslation() && style && style->getFilter())) // the object does not have a filter, or the transform is translation (which is supposed to not affect filters)
1543         )
1544     {
1545         transform_attr = this->set_transform(transform);
1546     }
1547     if (freeze_stroke_width) {
1548         freeze_stroke_width_recursive(false);
1549         if (compensate) {
1550             if (!prefs->getBool("/options/transform/stroke", true)) {
1551                 // Recursively compensate for stroke scaling, depending on user preference
1552                 // (As to why we need to do this, see the comment a few lines above near the freeze_stroke_width_recursive(true) call)
1553                 double const expansion = 1. / advertized_transform.descrim();
1554                 adjust_stroke_width_recursive(expansion);
1555             }
1556         }
1557     }
1558     // this avoid temporary scaling issues on display when near identity
1559     // this must be a bit grater than EPSILON * transform.descrim()
1560     double e = 1e-5 * transform.descrim();
1561     if (transform_attr.isIdentity(e)) {
1562         transform_attr = Geom::Affine();
1563     }
1564     set_item_transform(transform_attr);
1565 
1566     // Note: updateRepr comes before emitting the transformed signal since
1567     // it causes clone SPUse's copy of the original object to brought up to
1568     // date with the original.  Otherwise, sp_use_bbox returns incorrect
1569     // values if called in code handling the transformed signal.
1570     updateRepr();
1571 
1572     if (lpeitem && lpeitem->hasPathEffectRecursive()) {
1573         sp_lpe_item_update_patheffect(lpeitem, true, false);
1574     }
1575 
1576     // send the relative transform with a _transformed_signal
1577     _transformed_signal.emit(&advertized_transform, this);
1578 }
1579 
1580 // CPPIFY: see below, do not make pure?
event(SPEvent *)1581 gint SPItem::event(SPEvent* /*event*/) {
1582 	return FALSE;
1583 }
1584 
emitEvent(SPEvent & event)1585 gint SPItem::emitEvent(SPEvent &event)
1586 {
1587 	return this->event(&event);
1588 }
1589 
set_item_transform(Geom::Affine const & transform_matrix)1590 void SPItem::set_item_transform(Geom::Affine const &transform_matrix)
1591 {
1592     if (!Geom::are_near(transform_matrix, transform, 1e-18)) {
1593         transform = transform_matrix;
1594         /* The SP_OBJECT_USER_MODIFIED_FLAG_B is used to mark the fact that it's only a
1595            transformation.  It's apparently not used anywhere else. */
1596         requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_USER_MODIFIED_FLAG_B);
1597         sp_item_rm_unsatisfied_cns(*this);
1598     }
1599 }
1600 
1601 //void SPItem::convert_to_guides() const {
1602 //	// CPPIFY: If not overridden, call SPItem::convert_to_guides() const, see below!
1603 //	this->convert_to_guides();
1604 //}
1605 
1606 
i2anc_affine(SPObject const * object,SPObject const * const ancestor)1607 Geom::Affine i2anc_affine(SPObject const *object, SPObject const *const ancestor) {
1608     Geom::Affine ret(Geom::identity());
1609     g_return_val_if_fail(object != nullptr, ret);
1610 
1611     /* stop at first non-renderable ancestor */
1612     while ( object != ancestor && dynamic_cast<SPItem const *>(object) ) {
1613         SPRoot const *root = dynamic_cast<SPRoot const *>(object);
1614         if (root) {
1615             ret *= root->c2p;
1616         } else {
1617             SPItem const *item = dynamic_cast<SPItem const *>(object);
1618             g_assert(item != nullptr);
1619             ret *= item->transform;
1620         }
1621         object = object->parent;
1622     }
1623     return ret;
1624 }
1625 
1626 Geom::Affine
i2i_affine(SPObject const * src,SPObject const * dest)1627 i2i_affine(SPObject const *src, SPObject const *dest) {
1628     g_return_val_if_fail(src != nullptr && dest != nullptr, Geom::identity());
1629     SPObject const *ancestor = src->nearestCommonAncestor(dest);
1630     return i2anc_affine(src, ancestor) * i2anc_affine(dest, ancestor).inverse();
1631 }
1632 
getRelativeTransform(SPObject const * dest) const1633 Geom::Affine SPItem::getRelativeTransform(SPObject const *dest) const {
1634     return i2i_affine(this, dest);
1635 }
1636 
i2doc_affine() const1637 Geom::Affine SPItem::i2doc_affine() const
1638 {
1639     return i2anc_affine(this, nullptr);
1640 }
1641 
i2dt_affine() const1642 Geom::Affine SPItem::i2dt_affine() const
1643 {
1644     Geom::Affine ret(i2doc_affine());
1645     ret *= document->doc2dt();
1646     return ret;
1647 }
1648 
1649 // TODO should be named "set_i2dt_affine"
set_i2d_affine(Geom::Affine const & i2dt)1650 void SPItem::set_i2d_affine(Geom::Affine const &i2dt)
1651 {
1652     Geom::Affine dt2p; /* desktop to item parent transform */
1653     if (parent) {
1654         dt2p = static_cast<SPItem *>(parent)->i2dt_affine().inverse();
1655     } else {
1656         dt2p = document->dt2doc();
1657     }
1658 
1659     Geom::Affine const i2p( i2dt * dt2p );
1660     set_item_transform(i2p);
1661 }
1662 
1663 
dt2i_affine() const1664 Geom::Affine SPItem::dt2i_affine() const
1665 {
1666     /* fixme: Implement the right way (Lauris) */
1667     return i2dt_affine().inverse();
1668 }
1669 
1670 /* Item views */
1671 
sp_item_view_new_prepend(SPItemView * list,SPItem * item,unsigned flags,unsigned key,Inkscape::DrawingItem * drawing_item)1672 SPItemView *SPItem::sp_item_view_new_prepend(SPItemView *list, SPItem *item, unsigned flags, unsigned key, Inkscape::DrawingItem *drawing_item)
1673 {
1674     g_assert(item != nullptr);
1675     g_assert(dynamic_cast<SPItem *>(item) != nullptr);
1676     g_assert(drawing_item != nullptr);
1677 
1678     SPItemView *new_view = g_new(SPItemView, 1);
1679 
1680     new_view->next = list;
1681     new_view->flags = flags;
1682     new_view->key = key;
1683     new_view->arenaitem = drawing_item;
1684 
1685     return new_view;
1686 }
1687 
1688 static SPItemView*
sp_item_view_list_remove(SPItemView * list,SPItemView * view)1689 sp_item_view_list_remove(SPItemView *list, SPItemView *view)
1690 {
1691     SPItemView *ret = list;
1692     if (view == list) {
1693         ret = list->next;
1694     } else {
1695         SPItemView *prev;
1696         prev = list;
1697         while (prev->next != view) prev = prev->next;
1698         prev->next = view->next;
1699     }
1700 
1701     delete view->arenaitem;
1702     g_free(view);
1703 
1704     return ret;
1705 }
1706 
get_arenaitem(unsigned key)1707 Inkscape::DrawingItem *SPItem::get_arenaitem(unsigned key)
1708 {
1709     for ( SPItemView *iv = display ; iv ; iv = iv->next ) {
1710         if ( iv->key == key ) {
1711             return iv->arenaitem;
1712         }
1713     }
1714 
1715     return nullptr;
1716 }
1717 
sp_item_repr_compare_position(SPItem const * first,SPItem const * second)1718 int sp_item_repr_compare_position(SPItem const *first, SPItem const *second)
1719 {
1720     return sp_repr_compare_position(first->getRepr(),
1721                                     second->getRepr());
1722 }
1723 
sp_item_first_item_child(SPObject const * obj)1724 SPItem const *sp_item_first_item_child(SPObject const *obj)
1725 {
1726     return sp_item_first_item_child( const_cast<SPObject *>(obj) );
1727 }
1728 
sp_item_first_item_child(SPObject * obj)1729 SPItem *sp_item_first_item_child(SPObject *obj)
1730 {
1731     SPItem *child = nullptr;
1732     for (auto& iter: obj->children) {
1733         SPItem *tmp = dynamic_cast<SPItem *>(&iter);
1734         if ( tmp ) {
1735             child = tmp;
1736             break;
1737         }
1738     }
1739     return child;
1740 }
1741 
convert_to_guides() const1742 void SPItem::convert_to_guides() const {
1743     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1744     int prefs_bbox = prefs->getInt("/tools/bounding_box", 0);
1745 
1746     Geom::OptRect bbox = (prefs_bbox == 0) ? desktopVisualBounds() : desktopGeometricBounds();
1747     if (!bbox) {
1748         g_warning ("Cannot determine item's bounding box during conversion to guides.\n");
1749         return;
1750     }
1751 
1752     std::list<std::pair<Geom::Point, Geom::Point> > pts;
1753 
1754     Geom::Point A((*bbox).min());
1755     Geom::Point C((*bbox).max());
1756     Geom::Point B(A[Geom::X], C[Geom::Y]);
1757     Geom::Point D(C[Geom::X], A[Geom::Y]);
1758 
1759     pts.emplace_back(A, B);
1760     pts.emplace_back(B, C);
1761     pts.emplace_back(C, D);
1762     pts.emplace_back(D, A);
1763 
1764     sp_guide_pt_pairs_to_guides(document, pts);
1765 }
1766 
rotate_rel(Geom::Rotate const & rotation)1767 void SPItem::rotate_rel(Geom::Rotate const &rotation)
1768 {
1769     Geom::Point center = getCenter();
1770     Geom::Translate const s(getCenter());
1771     Geom::Affine affine = Geom::Affine(s).inverse() * Geom::Affine(rotation) * Geom::Affine(s);
1772 
1773     // Rotate item.
1774     set_i2d_affine(i2dt_affine() * (Geom::Affine)affine);
1775     // Use each item's own transform writer, consistent with sp_selection_apply_affine()
1776     doWriteTransform(transform);
1777 
1778     // Restore the center position (it's changed because the bbox center changed)
1779     if (isCenterSet()) {
1780         setCenter(center * affine);
1781         updateRepr();
1782     }
1783 }
1784 
scale_rel(Geom::Scale const & scale)1785 void SPItem::scale_rel(Geom::Scale const &scale)
1786 {
1787     Geom::OptRect bbox = desktopVisualBounds();
1788     if (bbox) {
1789         Geom::Translate const s(bbox->midpoint()); // use getCenter?
1790         set_i2d_affine(i2dt_affine() * s.inverse() * scale * s);
1791         doWriteTransform(transform);
1792     }
1793 }
1794 
skew_rel(double skewX,double skewY)1795 void SPItem::skew_rel(double skewX, double skewY)
1796 {
1797     Geom::Point center = getCenter();
1798     Geom::Translate const s(getCenter());
1799 
1800     Geom::Affine const skew(1, skewY, skewX, 1, 0, 0);
1801     Geom::Affine affine = Geom::Affine(s).inverse() * skew * Geom::Affine(s);
1802 
1803     set_i2d_affine(i2dt_affine() * affine);
1804     doWriteTransform(transform);
1805 
1806     // Restore the center position (it's changed because the bbox center changed)
1807     if (isCenterSet()) {
1808         setCenter(center * affine);
1809         updateRepr();
1810     }
1811 }
1812 
move_rel(Geom::Translate const & tr)1813 void SPItem::move_rel( Geom::Translate const &tr)
1814 {
1815     set_i2d_affine(i2dt_affine() * tr);
1816 
1817     doWriteTransform(transform);
1818 }
1819 
1820 /*
1821   Local Variables:
1822   mode:c++
1823   c-file-style:"stroustrup"
1824   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1825   indent-tabs-mode:nil
1826   fill-column:99
1827   End:
1828 */
1829 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
1830