diff --git a/src/object-snapper.cpp b/src/object-snapper.cpp index 6b3532dbff5de3324361fd5c77ad8c64c0474b7f..17776629766055463f0ca1a99471ceba34d9cb5a 100644 --- a/src/object-snapper.cpp +++ b/src/object-snapper.cpp @@ -123,13 +123,7 @@ void Inkscape::ObjectSnapper::_collectNodes(SnapSourceType const &t, } for (const auto & _candidate : *_snapmanager->_obj_snapper_candidates) { - //Geom::Affine i2doc(Geom::identity()); SPItem *root_item = _candidate.item; - - auto use = cast(_candidate.item); - if (use) { - root_item = use->root(); - } g_return_if_fail(root_item); //Collect all nodes so we can snap to them @@ -350,8 +344,9 @@ void Inkscape::ObjectSnapper::_collectPaths(Geom::Point /*p*/, // Snap to the text baseline Text::Layout const *layout = te_get_layout(static_cast(root_item)); if (layout != nullptr && layout->outputExists()) { + Geom::Affine transform = root_item->i2dt_affine() * _candidate.additional_affine * _snapmanager->getDesktop()->doc2dt(); auto pv = Geom::PathVector(); - pv.push_back(layout->baseline() * root_item->i2dt_affine() * _candidate.additional_affine * _snapmanager->getDesktop()->doc2dt()); + pv.push_back(layout->baseline() * transform); _paths_to_snap_to->push_back(SnapCandidatePath(std::move(pv), SNAPTARGET_TEXT_BASELINE, Geom::OptRect())); } } @@ -368,9 +363,13 @@ void Inkscape::ObjectSnapper::_collectPaths(Geom::Point /*p*/, if (!very_complex_path && root_item && _snapmanager->snapprefs.isTargetSnappable(SNAPTARGET_PATH, SNAPTARGET_PATH_INTERSECTION)) { if (auto const shape = cast(root_item)) { if (auto const curve = shape->curve()) { + Geom::Affine transform = use ? use->get_xy_offset(): Geom::Affine(); // If we're dealing with an SPUse, then account for any X/Y offset + transform *= root_item->i2dt_affine(); // Because all snapping calculations are done in desktop coordinates + transform *= _candidate.additional_affine; // Only used for snapping to masks or clips; see SnapManager::_findCandidates() + transform *= _snapmanager->getDesktop()->doc2dt(); // Account for inverted y-axis auto pv = curve->get_pathvector(); - pv *= root_item->i2dt_affine() * _candidate.additional_affine * _snapmanager->getDesktop()->doc2dt(); // (_edit_transform * _i2d_transform); - _paths_to_snap_to->push_back(SnapCandidatePath(std::move(pv), SNAPTARGET_PATH, Geom::OptRect())); // Perhaps for speed, get a reference to the Geom::pathvector, and store the transformation besides it. + pv *= transform; + _paths_to_snap_to->push_back(SnapCandidatePath(std::move(pv), SNAPTARGET_PATH, Geom::OptRect())); // Perhaps for speed, get a reference to the Geom::pathvector, and store the transformation besides it } } } diff --git a/src/object/sp-use.cpp b/src/object/sp-use.cpp index ef1197e8e5c81f7994999012d42a888aa16cbf1f..dc6be94c03bc47908ac5e3f43c0a399baa0706a9 100644 --- a/src/object/sp-use.cpp +++ b/src/object/sp-use.cpp @@ -35,6 +35,7 @@ #include "sp-shape.h" #include "sp-symbol.h" #include "sp-text.h" +#include "snap-candidate.h" #include "sp-use-reference.h" #include "sp-use.h" #include "style.h" @@ -238,19 +239,15 @@ std::optional SPUse::documentExactBounds() const } void SPUse::print(SPPrintContext* ctx) { - bool translated = false; - - if ((this->x._set && this->x.computed != 0) || (this->y._set && this->y.computed != 0)) { - Geom::Affine tp(Geom::Translate(this->x.computed, this->y.computed)); - ctx->bind(tp, 1.0); - translated = true; + if (has_xy_offset()) { + ctx->bind(Geom::Translate(this->x.computed, this->y.computed), 1.0); } if (this->child) { this->child->invoke_print(ctx); } - if (translated) { + if (has_xy_offset()) { ctx->release(); } } @@ -466,10 +463,8 @@ Geom::Affine SPUse::get_root_transform() const // right-side) of the transform attribute on the generated 'g', where x and y // represent the values of the x and y attributes on the 'use' element." - http://www.w3.org/TR/SVG11/struct.html#UseElement auto *i_use = cast(i_tem); - if (i_use) { - if ((i_use->x._set && i_use->x.computed != 0) || (i_use->y._set && i_use->y.computed != 0)) { - t = t * Geom::Translate(i_use->x._set ? i_use->x.computed : 0, i_use->y._set ? i_use->y.computed : 0); - } + if (i_use && i_use->has_xy_offset()) { + t = t * i_use->get_xy_offset(); } t *= i_tem->transform; @@ -485,14 +480,22 @@ Geom::Affine SPUse::get_parent_transform() const { Geom::Affine t(Geom::identity()); - if ((this->x._set && this->x.computed != 0) || (this->y._set && this->y.computed != 0)) { - t *= Geom::Translate(this->x._set ? this->x.computed : 0, this->y._set ? this->y.computed : 0); + if (has_xy_offset()) { + t *= get_xy_offset(); } t *= this->transform; return t; } +bool SPUse::has_xy_offset() const { + return (this->x._set && this->x.computed != 0) || (this->y._set && this->y.computed != 0); +} + +Geom::Translate SPUse::get_xy_offset() const { + return Geom::Translate(this->x._set ? this->x.computed : 0, this->y._set ? this->y.computed : 0); +} + /** * Sensing a movement of the original, this function attempts to compensate for it in such a way * that the clone stays unmoved or moves in parallel (depending on user setting) regardless of the @@ -832,21 +835,36 @@ SPItem *SPUse::get_original() const { SPItem *ref = nullptr; - if (this->ref){ - ref = this->ref->getObject(); - } + if (this->ref){ + ref = this->ref->getObject(); + } return ref; } void SPUse::snappoints(std::vector &p, Inkscape::SnapPreferences const *snapprefs) const { - SPItem const *root = this->root(); + SPItem const *child = this->child; - if (!root) { + if (!child) { return; } - root->snappoints(p, snapprefs); + // Collect the candidate snap points from the original item (i.e. from the child) + std::vector vec_pts; + child->snappoints(vec_pts, snapprefs); + + // Offset these snap candidate points if the X/Y attributes have been set for this item + // (see https://gitlab.com/inkscape/inkscape/-/issues/2765) + if (has_xy_offset()) { + // All snappoints are in desktop coordinates, but the item's transformation is in document coordinates + Geom::Point offset_dt = get_xy_offset().vector() * i2dt_affine().withoutTranslation(); + for (auto& it: vec_pts) { + it.movePoint(offset_dt); + } + } + + // Return the offsetted snap candidate points in vector p + std::move(vec_pts.begin(), vec_pts.end(), std::back_inserter(p)); } void SPUse::getLinked(std::vector &objects, LinkedObjectNature direction) const diff --git a/src/object/sp-use.h b/src/object/sp-use.h index bc42c988afc2da88c2e21ad3c3a63371527f1400..d52aec7441f0915be21a13ea29b041cca1d6ad92 100644 --- a/src/object/sp-use.h +++ b/src/object/sp-use.h @@ -70,6 +70,8 @@ public: SPItem *get_original() const; Geom::Affine get_parent_transform() const; Geom::Affine get_root_transform() const; + bool has_xy_offset() const; + Geom::Translate get_xy_offset() const; SPItem *trueOriginal() const; bool anyInChain(bool (*predicate)(SPItem const *)) const; diff --git a/src/snap-candidate.h b/src/snap-candidate.h index e7f1e84ee5aef002a1f6bde5500eea538ef9a163..db87179f096efb0683991a443247c49dd9cfab3f 100644 --- a/src/snap-candidate.h +++ b/src/snap-candidate.h @@ -85,6 +85,10 @@ public: inline Geom::OptRect const getTargetBBox() const {return _target_bbox;} inline bool considerForAlignment() const {return _alignment;} + + inline void setPoint(const Geom::Point &pt) {_point = pt;} + inline void movePoint(const Geom::Point &pt) {_point += pt;} + private: // Coordinates of the point Geom::Point _point;